@heroiclands/package-build 24.0.0 → 24.0.2

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 (61) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/assets/preview/gitea.ini +1 -1
  3. package/assets/styles/content-base.css +33 -0
  4. package/bin/content-commands.mjs +9 -7
  5. package/docs/api.md +6 -5
  6. package/docs/authoring/links-and-markup.md +164 -16
  7. package/docs/commands.md +23 -19
  8. package/docs/configuration.md +16 -0
  9. package/docs/content-format.md +4 -2
  10. package/docs/guides/site.md +43 -0
  11. package/docs/guides/writing-in-invented-scripts.md +68 -6
  12. package/docs/reference/format-details.md +143 -49
  13. package/docs/reference/note-types.md +47 -28
  14. package/docs/source-preview.md +2 -2
  15. package/engine/content-blocks.mjs +6 -2
  16. package/engine/content-format.yaml +2 -0
  17. package/engine/content-index.mjs +5 -1
  18. package/engine/content-render.mjs +2 -1
  19. package/engine/content-scripts.mjs +268 -21
  20. package/engine/content-scroll.mjs +389 -0
  21. package/engine/font-outlines.mjs +394 -0
  22. package/engine/language-data.mjs +154 -63
  23. package/engine/language-rows.mjs +32 -6
  24. package/engine/language-terms.mjs +32 -8
  25. package/engine/map-lexicon.mjs +31 -6
  26. package/engine/note-vocabulary.mjs +58 -6
  27. package/engine/pdf-fonts.mjs +1 -1
  28. package/engine/pdf-render.mjs +52 -11
  29. package/engine/run-rows.mjs +45 -3
  30. package/engine/script-direction.mjs +67 -0
  31. package/engine/script-emphasis-terms.mjs +11 -3
  32. package/engine/script-font-delivery.mjs +42 -9
  33. package/engine/script-fonts.mjs +7 -0
  34. package/engine/site-build.mjs +8 -1
  35. package/engine/site-reach.mjs +213 -0
  36. package/engine/site-render.mjs +120 -45
  37. package/engine/site-root.mjs +68 -26
  38. package/engine/site-serve.mjs +2 -1
  39. package/engine/site-theme.mjs +19 -6
  40. package/engine/sql-tables.mjs +9 -6
  41. package/engine/verse-metre.mjs +1 -0
  42. package/engine/written-in.mjs +208 -0
  43. package/package.json +1 -1
  44. package/site-theme/static/css/style.css +7 -0
  45. package/types/engine/content-scripts.d.mts +31 -6
  46. package/types/engine/content-scroll.d.mts +179 -0
  47. package/types/engine/font-outlines.d.mts +67 -0
  48. package/types/engine/language-data.d.mts +13 -0
  49. package/types/engine/language-rows.d.mts +4 -3
  50. package/types/engine/language-terms.d.mts +7 -0
  51. package/types/engine/map-lexicon.d.mts +4 -3
  52. package/types/engine/pdf-fonts.d.mts +10 -0
  53. package/types/engine/run-rows.d.mts +28 -6
  54. package/types/engine/script-direction.d.mts +10 -0
  55. package/types/engine/script-emphasis-terms.d.mts +10 -3
  56. package/types/engine/script-font-delivery.d.mts +5 -3
  57. package/types/engine/script-fonts.d.mts +9 -0
  58. package/types/engine/site-reach.d.mts +76 -0
  59. package/types/engine/site-render.d.mts +4 -2
  60. package/types/engine/site-root.d.mts +2 -0
  61. package/types/engine/written-in.d.mts +76 -0
@@ -483,7 +483,8 @@ These fields are accepted by every note type. A field's value can still be irrel
483
483
  | `data.negation` | a map — `{ particle, mutation? }` | How a verb is negated. A language skill note only. |
484
484
  | `data.naming` | a map — `{ shapes?, edges?, draws?, heldFrom?, nearName? }` | The shapes, edges and sources the language's names keep beyond its words' rules. A language skill note only. |
485
485
  | `data.emphasis` | `words`, `rubric` or `overline` | How the language marks a stressed word, over its script's `emphasis`: `rubric` or `overline` sets `*…*` and `**…**` so in a run of the language, and `words` marks it with a word, so either is a finding. Only a skill of `subType: language` states it. |
486
- | `data.script` | a map — `{ fonts, emphasis, strongEmphasis? }` | A script note's faces and how it marks emphasis. Only a skill of `subType: script` states it. |
486
+ | `data.writtenIn` | a map — `{ map, font }` | The script the language is written in, as the map and the face a run marked with its name alone takes; a run's own `map=` or `font=` overrides it. Only a skill of `subType: language` states it. |
487
+ | `data.script` | a map — `{ fonts, emphasis, strongEmphasis?, direction? }` | A script note's faces, how it marks emphasis and the direction it is written in. Only a skill of `subType: script` states it. |
487
488
 
488
489
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
489
490
 
@@ -577,12 +578,13 @@ These fields are accepted by every note type. A field's value can still be irrel
577
578
  | `data.scope.paths` | a list of content paths | no | Every note whose file sits below one of these paths of the content tree. |
578
579
  | `data.scope.types` | a list of note types | no | The note types the register covers; where stated, a note of another type is out of scope whatever else catches it. |
579
580
  | `data.scope.skip` | a list of Addresses | no | Notes out of scope whatever else catches them. |
580
- | `data.sounds.letters` | list of `{ letters, kind, like, tongue? }` | no | The letters the language writes, and the sounds they stand for. |
581
+ | `data.sounds.letters` | list of `{ letters, kind, like?, place?, ipa?, tongue? }` | no | The letters the language writes, and the sounds they stand for. |
581
582
  | `data.sounds.letters[].letters` | a list of letters | yes | The letters or digraphs the row describes, as written. |
582
583
  | `data.sounds.letters[].kind` | a string, such as `consonant` or `long vowel` | yes | What kind of sound they write. |
583
584
  | `data.sounds.letters[].like` | a string | no | What they sound like; absent where the row states only their kind and place. |
584
585
  | `data.sounds.letters[].place` | a string, such as `lips` or `tongue-tip` | no | Where in the mouth they are made. |
585
- | `data.sounds.letters[].tongue` | a tag, or a list of them | no | The tongues that write them, as tags `data.tongues` declares; every one where absent. |
586
+ | `data.sounds.letters[].ipa` | a string in the International Phonetic Alphabet, such as `ʃ` | no | The sounds they write, in the International Phonetic Alphabet. |
587
+ | `data.sounds.letters[].tongue` | a tag or a language note's Address, or a list of them | no | The tongues that write them, each a tag `data.tongues` declares or the Address of a language note; every one where absent. A `doc` note's rows name language notes only. |
586
588
  | `data.sounds.edges` | list of `{ edge, sounds }` | no | The sounds that may stand at each edge of a word, one entry per edge. |
587
589
  | `data.sounds.edges[].edge` | one of openConsonant, openCluster, closeConsonant, closeCluster, innerCluster, vowelPair, glide, syllabic | yes | Which edge of a word the sounds stand at. |
588
590
  | `data.sounds.edges[].sounds` | a list of sounds, or `any` | yes | The sounds, as written; `any` for every one, and an empty list for none. |
@@ -594,11 +596,12 @@ These fields are accepted by every note type. A field's value can still be irrel
594
596
  | `data.sounds.clusters.groups[].use` | one of common, sparing | yes | How often the group's clusters stand inside a word. |
595
597
  | `data.sounds.stress` | a map — `{ two, longer, mark? }` | no | Where a word's stress falls, as a rule a program reads. |
596
598
  | `data.sounds.stress.two` | one of first, last | yes | The stressed syllable of a word of two syllables. |
597
- | `data.sounds.stress.longer` | one of antepenultimate, penultimate, final | yes | The stressed syllable of a word of three or more, counted from its end. |
599
+ | `data.sounds.stress.longer` | one of first, antepenultimate, penultimate, final | yes | The stressed syllable of a word of three or more: `first`, or counted from its end. |
598
600
  | `data.sounds.stress.mark` | one of acute, grave, circumflex | no | The diacritic that marks a stress the rule would not choose; it moves the stress to the syllable it sits on. |
599
- | `data.sounds.finals` | list of `{ final, like }` | no | The syllable finals, for a language whose syllables are parsed by them. |
601
+ | `data.sounds.finals` | list of `{ final, like, ipa? }` | no | The syllable finals, for a language whose syllables are parsed by them. |
600
602
  | `data.sounds.finals[].final` | a final as written, such as `ang` | yes | The final. |
601
603
  | `data.sounds.finals[].like` | a string | yes | What it sounds like. |
604
+ | `data.sounds.finals[].ipa` | a string in the International Phonetic Alphabet, such as `ʃ` | no | Its sound, in the International Phonetic Alphabet. |
602
605
  | `data.sounds.syllables` | a map of syllable rules | no | What a syllable and a word may open and close on. |
603
606
  | `data.sounds.syllables.onsets` | one of single | no | What a syllable opens on: `single`, a single consonant. |
604
607
  | `data.sounds.syllables.codas` | a list of sounds, or `any` | no | The consonants that may close a syllable; `any` for every one. |
@@ -707,9 +710,12 @@ These fields are accepted by every note type. A field's value can still be irrel
707
710
  | `data.naming.nearName.examples` | list of `{ near, given }` | no | Worked near names, each beside its given name. |
708
711
  | `data.naming.nearName.examples[].near` | a near name, such as `Anlâ` | yes | The near name. |
709
712
  | `data.naming.nearName.examples[].given` | a given name | yes | The given name it is the near name of. |
713
+ | `data.writtenIn.map` | a mapping note's `data.map.name` | yes | The map the language's text is written by. |
714
+ | `data.writtenIn.font` | an installed face's slug | yes | The face the text is set in, one the map is drawn in. |
710
715
  | `data.script.fonts` | list of installed faces' slugs | yes | The faces the script is written in, by the slug `font=` names them by. A face belongs to one script. |
711
716
  | `data.script.emphasis` | `rubric` or `overline` | yes | How the script marks a stressed word: `rubric` sets it in red, `overline` draws a line above it. It sets `*…*` and `**…**` inside a run in one of its faces, unless the run's language states its own `emphasis`. |
712
717
  | `data.script.strongEmphasis` | `rubric` or `overline` | no | The convention `**…**` takes instead of `emphasis`'s. |
718
+ | `data.script.direction` | `vertical-rl` | no | How the script is written: `vertical-rl` is columns, top to bottom, read from the right, which a `scroll` fence sets. A script stating none is written in lines. |
713
719
 
714
720
  **System mappings**
715
721
 
@@ -999,34 +1005,47 @@ Lore records in-world knowledge. A `culture` describes a people; a `custom` desc
999
1005
  | `data.date` | string | The day a post was published, as `YYYY-MM-DD`. Required on a `post`; no other subType states it. Posts are listed and fed newest first by it. |
1000
1006
  | `data.author` | string | Who wrote a post, as the byline prints them. A post that names none carries the site's byline, the first entry of `authors`; no other subType states it. |
1001
1007
  | `data.culture` | an Address | The culture a setting guide introduces, as a culture lore note. Required on `settingguide`; no other subType states it. |
1008
+ | `data.sounds` | a map — `{ letters?, marks? }` | A spelling guide's letters and the marks written on them, for every tongue of the setting: each letter row's `tongue` names the language notes that write it. |
1002
1009
  | `data.map` | a map — `{ name, logographic?, vowels?, fonts, rows }` | A mapping, which text marked `map=` with its `name` is rewritten by before it is set. See [mapping notes](format-details.md#mapping-notes). |
1003
1010
  | `data.metres` | list of `{ name, beats, kind }` | The metres a verse fence's `scan=` names. A fence that states one is warned at each line that cannot have its beats. See [metres](../authoring/links-and-markup.md#metre). |
1004
1011
 
1005
1012
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
1006
1013
 
1007
- | Key | Shape | Required | Meaning |
1008
- | -------------------------------- | ---------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1009
- | `data.concepts[].id` | lowercase letters, digits and `-` | yes | The concept's id, which a word's `concept` names. Unique in the package. |
1010
- | `data.concepts[].gloss` | a short gloss, such as `a smell; a scent` | yes | What the concept means, in brief. |
1011
- | `data.concepts[].group` | a group name, such as `senses` | yes | The group the report lists the concept under. |
1012
- | `data.map.name` | string | yes | What `map=` names the map by: lowercase letters, digits and `-`, unique in the package. |
1013
- | `data.map.logographic` | `true` | no | The script writes meanings: a mapped run's words are written by the language's lexicon alone, and the rows write only the text between them. |
1014
- | `data.map.vowels` | string | no | The characters a row's `before: vowel` reads as vowels; any other letter is a consonant. |
1015
- | `data.map.rows` | list of `[from, to]` or `{ from, to, before?, at? }` | yes | The rewrites, each `[from, to]` or `{ from, to, before?, at? }`: `before` is `vowel` or `consonant`, `at` is `start` or `end`. The longest `from` wins, and a character no row matches passes through. |
1016
- | `data.map.rows[].from` | a string of one or more characters | yes | The one or more characters the row matches. |
1017
- | `data.map.rows[].to` | a string, which may be empty | yes | What the row writes in their place; empty to drop them. |
1018
- | `data.map.rows[].before` | string | no | `vowel` or `consonant`: what must follow the match for the row to apply. |
1019
- | `data.map.rows[].at` | string | no | `start` or `end`: where the match must sit for the row to apply. |
1020
- | `data.map.fonts` | list of `{ font, rows? }` | yes | The faces the map is drawn in, at least one, each with any rows of its own. A run in another face is refused, and each face must draw every character its rows and the lexicon's native forms write. |
1021
- | `data.map.fonts[].font` | an installed face's slug | yes | A face the map is drawn in, by the slug `font=` names it by. |
1022
- | `data.map.fonts[].rows` | list of `[from, to]` or `{ from, to, before?, at? }` | no | Rows for this face alone, tried before the shared `rows`; the same shapes and conditions. |
1023
- | `data.map.fonts[].rows[].from` | a string of one or more characters | yes | The one or more characters the row matches. |
1024
- | `data.map.fonts[].rows[].to` | a string, which may be empty | yes | What the row writes in their place; empty to drop them. |
1025
- | `data.map.fonts[].rows[].before` | string | no | `vowel` or `consonant`: what must follow the match for the row to apply. |
1026
- | `data.map.fonts[].rows[].at` | string | no | `start` or `end`: where the match must sit for the row to apply. |
1027
- | `data.metres[].name` | string | yes | What a verse fence's `scan=` names the metre by: lowercase letters, digits and `-`, unique in the package. |
1028
- | `data.metres[].beats` | a whole number above zero | yes | The beats of one line. |
1029
- | `data.metres[].kind` | one of accentual | yes | How the beats are counted: `accentual`, one beat for each stressed syllable the language's stress rule and the beat convention give. |
1014
+ | Key | Shape | Required | Meaning |
1015
+ | -------------------------------- | ------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1016
+ | `data.concepts[].id` | lowercase letters, digits and `-` | yes | The concept's id, which a word's `concept` names. Unique in the package. |
1017
+ | `data.concepts[].gloss` | a short gloss, such as `a smell; a scent` | yes | What the concept means, in brief. |
1018
+ | `data.concepts[].group` | a group name, such as `senses` | yes | The group the report lists the concept under. |
1019
+ | `data.sounds.letters` | list of `{ letters, kind, like?, place?, ipa?, tongue? }` | no | The letters the language writes, and the sounds they stand for. |
1020
+ | `data.sounds.letters[].letters` | a list of letters | yes | The letters or digraphs the row describes, as written. |
1021
+ | `data.sounds.letters[].kind` | a string, such as `consonant` or `long vowel` | yes | What kind of sound they write. |
1022
+ | `data.sounds.letters[].like` | a string | no | What they sound like; absent where the row states only their kind and place. |
1023
+ | `data.sounds.letters[].place` | a string, such as `lips` or `tongue-tip` | no | Where in the mouth they are made. |
1024
+ | `data.sounds.letters[].ipa` | a string in the International Phonetic Alphabet, such as `ʃ` | no | The sounds they write, in the International Phonetic Alphabet. |
1025
+ | `data.sounds.letters[].tongue` | a tag or a language note's Address, or a list of them | no | The tongues that write them, each a tag `data.tongues` declares or the Address of a language note; every one where absent. A `doc` note's rows name language notes only. |
1026
+ | `data.sounds.marks` | list of `{ mark, letters, kind, limit }` | no | The marks the language writes on its letters. |
1027
+ | `data.sounds.marks[].mark` | a string, such as `acute` | yes | The mark's name. |
1028
+ | `data.sounds.marks[].letters` | a list of letters | yes | The letters that carry it. |
1029
+ | `data.sounds.marks[].kind` | one of stress, length | yes | What the mark says of its letter. |
1030
+ | `data.sounds.marks[].limit` | one of one, any | yes | How many a word may carry: `one` at most, or `any`. |
1031
+ | `data.map.name` | string | yes | What `map=` names the map by: lowercase letters, digits and `-`, unique in the package. |
1032
+ | `data.map.logographic` | `true` | no | The script writes meanings: a mapped run's words are written by the language's lexicon alone, and the rows write only the text between them. |
1033
+ | `data.map.vowels` | string | no | The characters a row's `before: vowel` reads as vowels; any other letter is a consonant. |
1034
+ | `data.map.rows` | list of `[from, to]` or `{ from, to, before?, at? }` | yes | The rewrites, each `[from, to]` or `{ from, to, before?, at? }`: `before` is `vowel` or `consonant`, `at` is `start` or `end`. The longest `from` wins, and a character no row matches passes through. |
1035
+ | `data.map.rows[].from` | a string of one or more characters | yes | The one or more characters the row matches. |
1036
+ | `data.map.rows[].to` | a string, which may be empty | yes | What the row writes in their place; empty to drop them. |
1037
+ | `data.map.rows[].before` | string | no | `vowel` or `consonant`: what must follow the match for the row to apply. |
1038
+ | `data.map.rows[].at` | string | no | `start` or `end`: where the match must sit for the row to apply. |
1039
+ | `data.map.fonts` | list of `{ font, rows? }` | yes | The faces the map is drawn in, at least one, each with any rows of its own. A run in another face is refused, and each face must draw every character its rows and the lexicon's native forms write. |
1040
+ | `data.map.fonts[].font` | an installed face's slug | yes | A face the map is drawn in, by the slug `font=` names it by. |
1041
+ | `data.map.fonts[].rows` | list of `[from, to]` or `{ from, to, before?, at? }` | no | Rows for this face alone, tried before the shared `rows`; the same shapes and conditions. |
1042
+ | `data.map.fonts[].rows[].from` | a string of one or more characters | yes | The one or more characters the row matches. |
1043
+ | `data.map.fonts[].rows[].to` | a string, which may be empty | yes | What the row writes in their place; empty to drop them. |
1044
+ | `data.map.fonts[].rows[].before` | string | no | `vowel` or `consonant`: what must follow the match for the row to apply. |
1045
+ | `data.map.fonts[].rows[].at` | string | no | `start` or `end`: where the match must sit for the row to apply. |
1046
+ | `data.metres[].name` | string | yes | What a verse fence's `scan=` names the metre by: lowercase letters, digits and `-`, unique in the package. |
1047
+ | `data.metres[].beats` | a whole number above zero | yes | The beats of one line. |
1048
+ | `data.metres[].kind` | one of accentual | yes | How the beats are counted: `accentual`, one beat for each stressed syllable the language's stress rule and the beat convention give. |
1030
1049
 
1031
1050
  ### macro
1032
1051
 
@@ -27,7 +27,7 @@ options. Unsupported arguments exit 1. Findings go to stderr; success exits 0,
27
27
  and render errors exit 1. A partial preview may accompany error findings.
28
28
 
29
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
30
+ alert, a secret block, a verse, a scroll, a captioned item, a footnote section and a
31
31
  picture's size and position read as they do on those surfaces. Fenced code that
32
32
  names a language is highlighted. A tag written in a note is shown as text.
33
33
 
@@ -170,7 +170,7 @@ go run check-sanitizer.go /path/to/gitea.ini < preview.html > sanitized.html
170
170
  ```
171
171
 
172
172
  Check both light and dark fixtures containing all four alert types, a custom
173
- title, nesting, wikilinks and attributes, SQL/pagelist fences, infoboxes, verse
173
+ title, nesting, wikilinks and attributes, SQL/pagelist fences, infoboxes, verse, a scroll
174
174
  and footnotes. All wrapper/token classes and icon shapes should remain, while
175
175
  injected scripts and event-handler attributes should disappear. This replay
176
176
  checks the sanitizer primitives; also inspect the result in the actual Gitea
@@ -54,6 +54,7 @@ import { slugify } from "./content-slug.mjs";
54
54
  import MarkdownIt from "markdown-it";
55
55
  import { parseExtensionAttributes, refusedAttributes } from "./extension-attributes.mjs";
56
56
  import { scanVerse, renderVerse } from "./content-verse.mjs";
57
+ import { renderScroll, scanScrolls } from "./content-scroll.mjs";
57
58
  import { WITHHELD_CLASS, parseHeadingLine, withheldSections } from "./heading-attributes.mjs";
58
59
 
59
60
  /**
@@ -200,7 +201,10 @@ export function scanBlocks(source) {
200
201
  });
201
202
  }
202
203
  }
203
- return { blocks, errors: [...errors, ...scanVerse(source).errors] };
204
+ return {
205
+ blocks,
206
+ errors: [...errors, ...scanVerse(source).errors, ...scanScrolls(source).errors],
207
+ };
204
208
  }
205
209
 
206
210
  /**
@@ -256,7 +260,7 @@ function attributeText(block, target, classes) {
256
260
  */
257
261
  export function renderBlocks(source, target, { renderInline } = {}) {
258
262
  const { errors } = scanBlocks(source);
259
- source = renderVerse(source).markdown;
263
+ source = renderScroll(renderVerse(source).markdown).markdown;
260
264
  const renderedBlocks = scanBlocks(source).blocks;
261
265
  if (!renderedBlocks.length) return { markdown: String(source ?? ""), errors };
262
266
  const lines = String(source ?? "").split("\n");
@@ -226,6 +226,7 @@ types:
226
226
  - negation
227
227
  - naming
228
228
  - emphasis
229
+ - writtenIn
229
230
  - script
230
231
  subTypes:
231
232
  - social
@@ -369,6 +370,7 @@ types:
369
370
  - concepts
370
371
  - date
371
372
  - author
373
+ - sounds
372
374
  - map
373
375
  - metres
374
376
  subTypes:
@@ -106,7 +106,7 @@ import { AddressLink } from "./address-values.mjs";
106
106
  import { scanSpans } from "./content-spans.mjs";
107
107
  import { rebaseNameSpans } from "./name-markup.mjs";
108
108
  import { analyzeNameSpans } from "./names.mjs";
109
- import { recordRuns } from "./run-rows.mjs";
109
+ import { fillWrittenIn, recordRuns } from "./run-rows.mjs";
110
110
  import fs from "node:fs";
111
111
  import path from "node:path";
112
112
 
@@ -1250,6 +1250,10 @@ export function collectContentIndex(
1250
1250
  // are recorded; the index keeps the citing Addresses and no text.
1251
1251
  attachCitations(records, bodies);
1252
1252
 
1253
+ // A run marked with a language alone takes its language's script, which
1254
+ // is read once every language note is recorded.
1255
+ fillWrittenIn(records, { languages, contentPackage });
1256
+
1253
1257
  // Every note record exists and every Address in it is a tuple, so a parent
1254
1258
  // is found by its canonical address. Before the sort, which reads nothing
1255
1259
  // the pass writes. Calendars first: they read the culture a note states,
@@ -46,7 +46,8 @@
46
46
  * 2. **Captioned items** — each figure's wrapper and label. Its number, kind
47
47
  * and role come from the figure scan expansion made, so a figure is
48
48
  * numbered once; see {@link module:engine/content-figures.renderFigureBlocks}.
49
- * 3. **Named blocks and verse** — `:::` blocks and verse fences.
49
+ * 3. **Named blocks, verse and scrolls** — `:::` blocks, verse fences and
50
+ * `scroll` fences.
50
51
  * 4. **Alerts.**
51
52
  * 5. **Spans and withheld sections** — written here on the `web` target; the
52
53
  * other targets read spans and headings as tokens.