@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
@@ -38,6 +38,11 @@
38
38
  * the root as its `replaces` says, or whole where it states none. A
39
39
  * participle closing on an agreement ending agrees.
40
40
  *
41
+ * A word's `variants` are known forms as written, and each takes the rules
42
+ * its word takes: the plural rule, the verb pattern, the suffixes, agreement
43
+ * and participles. A word's own stated `plural` belongs to its citation form
44
+ * alone.
45
+ *
41
46
  * The mapped-run word check ({@link module:engine/run-words}) and
42
47
  * `package-build lexicon --derived` both read {@link derivedForms}, so what
43
48
  * the command lists is what the check accepts.
@@ -279,8 +284,9 @@ function participleForms(citation, participles) {
279
284
  */
280
285
 
281
286
  /**
282
- * Every form the language's rules derive from its lexicon words, in
283
- * `data.words` order, each once, leaving out a form that is a lexicon word.
287
+ * Every form the language's rules derive from its lexicon words, and each
288
+ * declared variant of a word, in `data.words` order, each once, leaving out a
289
+ * form that is a lexicon word.
284
290
  *
285
291
  * @param {Record<string, any>|undefined} data - The language note's `data`.
286
292
  * @returns {DerivedForm[]} The forms.
@@ -304,30 +310,39 @@ export function derivedForms(data) {
304
310
  seen.add(folded);
305
311
  out.push({ form: folded, word });
306
312
  };
307
- const folded = foldWord(word.form);
308
313
  const base = parseWordClass(word.class).base;
309
- const plural = pluralOf(word, grammar);
310
- if (plural) add(plural);
311
- if (verbs && base === "v")
312
- for (const { ending } of verbs.endings)
313
- add(verbForm(folded, foldWord(ending), replaces));
314
- const attaching = suffixes.filter(
315
- ({ classes }) => classes === undefined || (base !== undefined && classes.has(base)),
314
+ const variants = listOf(word.variants).filter(
315
+ (variant) => typeof variant === "string" && variant.trim() !== "",
316
316
  );
317
- if (attaching.length)
318
- for (const [stem] of word.form.matchAll(WORD)) {
319
- const one = foldWord(stem);
320
- for (const { suffix, replaces: closing } of attaching)
321
- add(attach(one, suffix, closing));
322
- }
323
- if (agreement && base !== undefined && agreement.classes.has(base))
324
- for (const form of agreeingForms(folded, agreement, grammar)) add(form);
325
- if (participles && base === "v")
326
- for (const participle of participleForms(folded, participles)) {
327
- add(participle);
328
- if (agreement)
329
- for (const form of agreeingForms(participle, agreement, grammar)) add(form);
330
- }
317
+ for (const [at, written] of [word.form, ...variants].entries()) {
318
+ const folded = foldWord(written);
319
+ if (at > 0) add(written);
320
+ const plural =
321
+ at === 0 ? pluralOf(word, grammar)
322
+ : base === "n" ? pluralByRule(written, grammar)
323
+ : null;
324
+ if (plural) add(plural);
325
+ if (verbs && base === "v")
326
+ for (const { ending } of verbs.endings)
327
+ add(verbForm(folded, foldWord(ending), replaces));
328
+ const attaching = suffixes.filter(
329
+ ({ classes }) => classes === undefined || (base !== undefined && classes.has(base)),
330
+ );
331
+ if (attaching.length)
332
+ for (const [stem] of written.matchAll(WORD)) {
333
+ const one = foldWord(stem);
334
+ for (const { suffix, replaces: closing } of attaching)
335
+ add(attach(one, suffix, closing));
336
+ }
337
+ if (agreement && base !== undefined && agreement.classes.has(base))
338
+ for (const form of agreeingForms(folded, agreement, grammar)) add(form);
339
+ if (participles && base === "v")
340
+ for (const participle of participleForms(folded, participles)) {
341
+ add(participle);
342
+ if (agreement)
343
+ for (const form of agreeingForms(participle, agreement, grammar)) add(form);
344
+ }
345
+ }
331
346
  }
332
347
  return out;
333
348
  }
@@ -69,8 +69,18 @@ export const WORD_COLUMNS = Object.freeze([
69
69
  /** Every column of the `glossary` relation, in order. */
70
70
  export const GLOSSARY_COLUMNS = Object.freeze(["note", "position", ...GLOSSARY_ENTRY_KEYS]);
71
71
 
72
+ /**
73
+ * The affix's own keys an affix row carries: all but `native`, its writings
74
+ * under a logographic map, which `package-build lexicon` resolves.
75
+ */
76
+ const AFFIX_ROW_KEYS = Object.freeze(AFFIX_KEYS.filter((key) => key !== "native"));
77
+
72
78
  /** Every column of the `affixes` relation, in order. */
73
- export const AFFIX_COLUMNS = Object.freeze([...WORD_IDENTITY_COLUMNS, ...AFFIX_KEYS, "written"]);
79
+ export const AFFIX_COLUMNS = Object.freeze([
80
+ ...WORD_IDENTITY_COLUMNS,
81
+ ...AFFIX_ROW_KEYS,
82
+ "written",
83
+ ]);
74
84
 
75
85
  /** Every column of the `fields` relation, in order. */
76
86
  export const FIELD_COLUMNS = Object.freeze([...WORD_IDENTITY_COLUMNS, ...FIELD_KEYS]);
@@ -322,7 +332,7 @@ function languageListRows(records, list, keys) {
322
332
  * @returns {object[]} One row per affix, in record then entry order.
323
333
  */
324
334
  export function affixRows(records) {
325
- return languageListRows(records, "affixes", AFFIX_KEYS).map((row) => ({
335
+ return languageListRows(records, "affixes", AFFIX_ROW_KEYS).map((row) => ({
326
336
  ...row,
327
337
  written: row.zero === true ? ZERO_AFFIX : row.form,
328
338
  }));
@@ -126,7 +126,8 @@ export const WORD_NATIVE_FIELDS = Object.freeze([
126
126
  required: true,
127
127
  shape: "the word in the script, as the map's characters",
128
128
  describe:
129
- "The word as the script writes it, where the map's rows would write it otherwise.",
129
+ "The word as the script writes it, where the map's rows would write it otherwise, " +
130
+ "or as a logographic map writes it.",
130
131
  }),
131
132
  ]);
132
133
 
@@ -150,7 +151,7 @@ export const WORD_FIELDS = Object.freeze([
150
151
  entries: Object.freeze({ kind: "string", shape: "a form, bare" }),
151
152
  describe:
152
153
  "The word's other forms, such as a long form beside a short one; a marked use of " +
153
- "one cites the word.",
154
+ "one cites the word, and a mapped run accepts it and the forms the language's rules derive from it.",
154
155
  }),
155
156
  Object.freeze({
156
157
  name: "name_sort",
@@ -191,6 +192,15 @@ export const WORD_FIELDS = Object.freeze([
191
192
  describe:
192
193
  "The meaning, in brief; a list states one sense per entry, which a dictionary numbers.",
193
194
  }),
195
+ Object.freeze({
196
+ name: "concept",
197
+ kind: "string-or-list",
198
+ shape: "a core concept's id, or a list of them",
199
+ entries: Object.freeze({ kind: "string", shape: "a core concept's id" }),
200
+ describe:
201
+ "The core concepts the word serves, each an `id` of a `data.concepts` list. The " +
202
+ "core report counts the word toward them.",
203
+ }),
194
204
  Object.freeze({
195
205
  name: "source",
196
206
  kind: "map",
@@ -250,8 +260,10 @@ export const WORD_FIELDS = Object.freeze([
250
260
  fields: WORD_NATIVE_FIELDS,
251
261
  }),
252
262
  describe:
253
- "The word's native form under a map, stated only where the map's rows spell it " +
254
- "differently. Its derived forms are spelled by the rows.",
263
+ "The word's native form under a map. Under a map that spells by its rows it is " +
264
+ "stated only where the rows spell the word differently, and the word's derived " +
265
+ "forms are spelled by the rows; under a logographic map it is the word's writing, " +
266
+ "which its derived forms and the words built on it are written from.",
255
267
  }),
256
268
  ]);
257
269
 
@@ -411,6 +423,19 @@ export const AFFIX_FIELDS = Object.freeze([
411
423
  shape: "a tone pattern, such as `L`",
412
424
  describe: "The tone pattern the affix carries, written as a word's `tones` is.",
413
425
  }),
426
+ Object.freeze({
427
+ name: "native",
428
+ kind: "list",
429
+ shape: "a list of `{ map, font?, text }`",
430
+ entries: Object.freeze({
431
+ kind: "map",
432
+ shape: "a map — `{ map, font?, text }`",
433
+ fields: WORD_NATIVE_FIELDS,
434
+ }),
435
+ describe:
436
+ "The affix's writing under a logographic map, which a word whose `built` names the " +
437
+ "affix is written with.",
438
+ }),
414
439
  ]);
415
440
 
416
441
  /** The keys of {@link WORD_FIELD_FIELDS}, in declared order. */
package/manifest.mjs CHANGED
@@ -121,10 +121,9 @@ export const HOMEPAGE_ORIGIN = "https://www.heroiclands.org";
121
121
  * to drift — and the one that drifts is this one, because nothing fetches `url`
122
122
  * the way Foundry fetches `manifest`, so a wrong value is never reported.
123
123
  *
124
- * The site's own `baseURL` says the same thing, and is deliberately not the
125
- * source: it sits at `site/hugo.toml` in most repositories and `kb/hugo.toml`
126
- * in the system, so reading it would mean the packaging half knowing where each
127
- * repository keeps its site configuration.
124
+ * `package.json`'s `homepage` says the same thing, and is deliberately not the
125
+ * source: reading it would mean the packaging half depending on the site's
126
+ * configuration.
128
127
  *
129
128
  * @param {string} contentPackage - The package's content-package name.
130
129
  * @returns {string} The homepage URL, with its trailing slash.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "23.13.0",
3
+ "version": "24.0.0",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -123,17 +123,18 @@
123
123
  "engine",
124
124
  "githooks",
125
125
  "hm3",
126
- "hugo-theme",
127
126
  "index.mjs",
128
127
  "lang.mjs",
129
128
  "manifest.mjs",
130
129
  "markdownlint-config.mjs",
131
130
  "prettier-config.mjs",
132
131
  "release.mjs",
132
+ "site-theme",
133
133
  "sohl",
134
134
  "stage.mjs",
135
135
  "templates.mjs",
136
- "types"
136
+ "types",
137
+ "vendor"
137
138
  ],
138
139
  "dependencies": {
139
140
  "@duckdb/node-api": "^1.5.5-r.4",
@@ -152,6 +153,7 @@
152
153
  "flesch": "^2.0.1",
153
154
  "glob": "^13.0.6",
154
155
  "handlebars": "^4.7.10",
156
+ "highlight.js": "^11.12.0",
155
157
  "ignore": "^7.0.12",
156
158
  "js-yaml": "^5.5.0",
157
159
  "jsonc-parser": "^3.3.1",
@@ -160,7 +162,6 @@
160
162
  "markdown-it": "^15.0.0",
161
163
  "markdown-it-deflist": "^4.0.0",
162
164
  "markdown-it-footnote": "^4.0.0",
163
- "markdownlint": "^0.42.0",
164
165
  "minimatch": "^10.2.6",
165
166
  "pagefind": "^1.5.2",
166
167
  "prettier": "^3.9.6",
@@ -173,7 +174,6 @@
173
174
  "retext-simplify": "^8.0.0",
174
175
  "retext-stringify": "^4.0.0",
175
176
  "sharp": "^0.35.4",
176
- "smol-toml": "^1.8.0",
177
177
  "ssh2-sftp-client": "^12.1.1",
178
178
  "syllable": "^5.0.1",
179
179
  "typescript": "^6.0.3",
@@ -185,6 +185,8 @@
185
185
  "devDependencies": {
186
186
  "@changesets/cli": "^3.0.0",
187
187
  "@types/node": "^26.2.0",
188
+ "esbuild": "^0.28.2",
189
+ "markdownlint": "^0.42.0",
188
190
  "npm-run-all": "^4.1.5",
189
191
  "vitest": "^5.0.0"
190
192
  },
@@ -193,7 +195,8 @@
193
195
  "test:watch": "vitest",
194
196
  "build": "node bin/package-build.mjs ci",
195
197
  "deploy:npm": "node ci/publish-npm.mjs",
196
- "prepack": "run-s build:types build:asset-index",
198
+ "prepack": "run-s build:vendor build:types build:asset-index",
199
+ "build:vendor": "node scripts/vendor-markdownlint.mjs",
197
200
  "build:types": "tsc -p tsconfig.dts.json",
198
201
  "build:asset-index": "node ci/emit-asset-index.mjs",
199
202
  "format": "node ci/prettier.mjs --write",
@@ -210,7 +213,7 @@
210
213
  "lint:content-format:fields": "node bin/package-build.mjs content-format fields --fields sohl && node bin/package-build.mjs content-format fields --fields hm3",
211
214
  "changeset": "changeset",
212
215
  "changeset:version": "changeset version && node bin/package-build.mjs changelog group && npm install --package-lock-only",
213
- "prepare": "git config core.hooksPath .githooks || true",
216
+ "prepare": "(git config core.hooksPath .githooks || true) && (test ! -e scripts/vendor-markdownlint.mjs || npm run build:vendor)",
214
217
  "build:deps": "node bin/package-build.mjs deps fetch"
215
218
  },
216
219
  "engines": {
@@ -1,16 +1,16 @@
1
1
  # Which banner images exist in the shared artwork set.
2
2
  #
3
- # `partials/hero-banner.html` resolves a page's hero image to a path under
3
+ # The hero banner resolves a page's hero image to a path under
4
4
  # `images/banners/`, and that path is emitted into a `background-image` with no
5
5
  # way to test it: the artwork is served from the consuming site's
6
- # `params.cdnBaseURL`, and Hugo cannot ask a remote host whether a URL exists.
7
- # So a banner that was never drawn would 404 in silence — the band renders
8
- # with its title, palette and gradient intact and a dead image URL behind it,
9
- # and nothing fails: not the build, not Hugo, not the deploy guard.
6
+ # `params.cdnBaseURL`, and the build cannot ask a remote host whether a URL
7
+ # exists. So a banner that was never drawn would 404 in silence — the band
8
+ # renders with its title, palette and gradient intact and a dead image URL
9
+ # behind it, and nothing fails: not the build, not the deploy guard.
10
10
  #
11
11
  # This file is the declaration that replaces that missing test. A name listed
12
12
  # here is asserted to exist; a resolved banner whose name is absent falls back
13
- # to `default.webp` and Hugo warns once for it during the consumer's build.
13
+ # to `default.webp` and the build warns once for it.
14
14
  #
15
15
  # It speaks for the names this theme resolves itself — a hand-authored
16
16
  # `banner:` and the subtype defaults below. A note's own `data.banner` arrives
@@ -21,10 +21,6 @@
21
21
  # (`ci/check-banners.mjs`, part of `npm run lint`) fetches every name below
22
22
  # from a real asset host and fails on any non-200. Add a name here only after
23
23
  # the file is published, or the check will say so.
24
- #
25
- # A consumer publishing its own artwork set overrides the whole list by
26
- # shipping its own `data/banners.yaml` — Hugo reads the site's data directory
27
- # in preference to the theme's.
28
24
 
29
25
  # The asset root every name below is resolved under, and the name used when a
30
26
  # resolved banner is not in `available`. Both are relative to the consuming
@@ -0,0 +1,31 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>{{head.title}}</title>
8
+ <meta name="description" content="{{head.description}}">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="{{site.urls.style}}">
13
+ {{#if site.urls.scriptFonts}}
14
+ <link rel="stylesheet" href="{{site.urls.scriptFonts}}">
15
+ {{/if}}
16
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.3.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
17
+ <link rel="stylesheet" href="{{site.urls.icons}}">
18
+ <link rel="stylesheet" href="{{site.urls.code}}">
19
+ {{#if site.feed}}
20
+ <link rel="alternate" type="application/atom+xml" title="{{site.feed.title}}" href="{{site.feed.url}}">
21
+ {{/if}}
22
+ </head>
23
+ <body>
24
+ {{> header site}}
25
+ <main>
26
+ {{{body}}}
27
+ </main>
28
+ {{> footer site}}
29
+ {{> copy-buttons}}
30
+ </body>
31
+ </html>
@@ -0,0 +1,30 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ {{#if hero}}
3
+ {{> hero-banner hero}}
4
+ {{/if}}
5
+ <article class="single">
6
+ <div class="single-content">
7
+ {{#if body}}
8
+ <div class="single-body">
9
+ {{{body}}}
10
+ </div>
11
+ {{/if}}
12
+ {{#if posts}}
13
+ <ul class="blog-list">
14
+ {{#each posts}}
15
+ <li class="blog-list-item">
16
+ {{#if date}}
17
+ <time class="blog-list-date" datetime="{{date.iso}}">{{date.text}}</time>
18
+ {{/if}}
19
+ <a class="blog-list-title" href="{{url}}">{{title}}</a>
20
+ {{#if preview}}
21
+ <p class="blog-list-desc">{{preview}}</p>
22
+ {{/if}}
23
+ </li>
24
+ {{/each}}
25
+ </ul>
26
+ {{else}}
27
+ <p class="page-list-empty">No posts yet.</p>
28
+ {{/if}}
29
+ </div>
30
+ </article>
@@ -0,0 +1,40 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ {{#if hero}}
3
+ {{> hero-banner hero}}
4
+ {{/if}}
5
+ {{#if article}}
6
+ <article class="single">
7
+ {{> search-meta article.search}}
8
+ <div class="single-content">
9
+ {{#if article.meta}}
10
+ <div class="single-meta blog-meta">
11
+ {{#if article.meta.series}}
12
+ <div class="blog-series">
13
+ <span class="blog-series-label">Series</span>
14
+ <span class="blog-series-name">{{article.meta.series}}</span>
15
+ </div>
16
+ {{/if}}
17
+ {{#if article.meta.date}}
18
+ <time class="single-date" datetime="{{article.meta.date.iso}}">{{article.meta.date.text}}</time>
19
+ {{/if}}
20
+ {{#if article.meta.tags}}
21
+ {{> tags article.meta.tags}}
22
+ {{/if}}
23
+ </div>
24
+ {{/if}}
25
+
26
+ {{#if article.content}}
27
+ <div class="single-body" data-pagefind-body>
28
+ {{{article.content}}}
29
+ </div>
30
+ {{/if}}
31
+
32
+ {{#if article.related}}
33
+ {{> related article.related}}
34
+ {{/if}}
35
+ {{#if article.nav}}
36
+ {{> nav-links article.nav}}
37
+ {{/if}}
38
+ </div>
39
+ </article>
40
+ {{/if}}
@@ -0,0 +1,35 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ {{#if isHomepage}}
3
+ {{#if hero}}
4
+ {{> hero-banner hero}}
5
+ {{/if}}
6
+ {{#if article}}
7
+ {{> page-body article}}
8
+ {{/if}}
9
+ {{else if grid}}
10
+ {{#if grid.hero}}
11
+ {{> hero-banner grid.hero}}
12
+ {{/if}}
13
+ {{#if grid.hasItems}}
14
+ <section class="home-sections">
15
+ <div class="home-featured">
16
+ {{#each grid.headings}}
17
+ <h2 class="home-section-title{{#if class}} home-section-title--{{class}}{{/if}}">{{title}}</h2>
18
+ {{/each}}
19
+ {{#each grid.cards}}
20
+
21
+ <div class="home-card{{#if key}} home-card--{{key}}{{/if}}">
22
+ {{#if imageUrl}}
23
+ <a href="{{url}}">
24
+ <img src="{{imageUrl}}" alt="{{alt}}" class="card-image">
25
+ </a>
26
+ {{/if}}
27
+ <h2><a href="{{url}}">{{title}}</a></h2>
28
+ <p>{{text}}</p>
29
+ <a href="{{url}}" class="card-button">More Info →</a>
30
+ </div>
31
+ {{/each}}
32
+ </div>
33
+ </section>
34
+ {{/if}}
35
+ {{/if}}
@@ -0,0 +1,20 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ {{#if hero}}
3
+ {{> hero-banner hero}}
4
+ {{/if}}
5
+ {{#if hasBody}}
6
+ <article class="single">
7
+ <div class="single-content">
8
+ <div class="single-body" data-pagefind-body>
9
+ {{{body}}}
10
+ </div>
11
+ {{> list-members}}
12
+ </div>
13
+ </article>
14
+ {{else if hasMembers}}
15
+ <article class="single">
16
+ <div class="single-content">
17
+ {{> list-members}}
18
+ </div>
19
+ </article>
20
+ {{/if}}
@@ -0,0 +1,31 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ <section class="hero hero-with-image" style="background-image: url('{{cssUrl heroUrl}}');">
3
+ <div class="hero-inner" data-pagefind-ignore>
4
+ <h1>Page not found</h1>
5
+ <p class="hero-tagline">{{tagline}} <code id="missing-path">the address you followed</code>.</p>
6
+ </div>
7
+ </section>
8
+
9
+ <article class="single">
10
+ <div class="single-content">
11
+ <div class="single-body">
12
+ <p>
13
+ The page may have been moved to another part of the
14
+ {{noun}}, or withdrawn along with the concept it described. Links
15
+ from older discussions, issues, and search results can outlive the
16
+ pages they point at.
17
+ </p>
18
+ {{#if links}}
19
+ <p>Start again from one of these:</p>
20
+ {{> page-list links}}
21
+ {{/if}}
22
+ </div>
23
+ </div>
24
+ </article>
25
+
26
+ <script>
27
+ (function () {
28
+ var el = document.getElementById("missing-path");
29
+ if (el) el.textContent = window.location.pathname;
30
+ })();
31
+ </script>
@@ -0,0 +1,7 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ {{#if hero}}
3
+ {{> hero-banner hero}}
4
+ {{/if}}
5
+ {{#if article}}
6
+ {{> page-body article}}
7
+ {{/if}}
@@ -0,0 +1,37 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ <script>
3
+ // Auto-enhance <span data-copy="URL"> elements with a clipboard copy button
4
+ document.querySelectorAll('[data-copy]').forEach(function(el) {
5
+ var url = el.getAttribute('data-copy');
6
+ el.classList.add('exturl');
7
+
8
+ // Wrap existing text in a code element if not already
9
+ if (!el.querySelector('.exturl-text')) {
10
+ var code = document.createElement('code');
11
+ code.className = 'exturl-text';
12
+ code.textContent = el.textContent;
13
+ el.textContent = '';
14
+ el.appendChild(code);
15
+ }
16
+
17
+ var btn = document.createElement('button');
18
+ btn.className = 'exturl-copy';
19
+ btn.title = 'Copy URL to clipboard';
20
+ btn.setAttribute('aria-label', 'Copy URL to clipboard');
21
+ btn.innerHTML =
22
+ '<svg class="exturl-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
23
+ '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>' +
24
+ '<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/>' +
25
+ '</svg>' +
26
+ '<svg class="exturl-check" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">' +
27
+ '<polyline points="20 6 9 17 4 12"/>' +
28
+ '</svg>';
29
+ btn.addEventListener('click', function() {
30
+ navigator.clipboard.writeText(url).then(function() {
31
+ btn.classList.add('copied');
32
+ setTimeout(function() { btn.classList.remove('copied'); }, 1500);
33
+ });
34
+ });
35
+ el.appendChild(btn);
36
+ });
37
+ </script>
@@ -0,0 +1,5 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ <aside class="draft-notice">
3
+ <i class="fa-solid fa-circle-exclamation draft-notice-icon" aria-hidden="true"></i>
4
+ <p class="draft-notice-text"><strong class="draft-notice-label">Draft.</strong> <em>{{notice}}</em></p>
5
+ </aside>
@@ -0,0 +1,2 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ <span data-copy="{{url}}">{{url}}</span>
@@ -1,31 +1,34 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
1
2
  <footer class="site-footer" data-pagefind-ignore>
2
3
  <div class="footer-inner">
3
4
  <p class="footer-text">
4
- &copy; 2020&ndash;{{ now.Year }} Tom Rodriguez. Content under The World of Thalorna and Song of Heroic Lands is licensed under
5
- <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a>; all other content is &copy; Tom Rodriguez, all rights reserved.{{ with .Site.Params.brand }}{{ with .licenseURL }} See
6
- <a href="{{ . }}">License</a>.{{ end }}{{ end }}
5
+ &copy; 2020&ndash;{{year}} Tom Rodriguez. Content under The World of Thalorna and Song of Heroic Lands is licensed under
6
+ <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a>; all other content is &copy; Tom Rodriguez, all rights reserved.{{#if brand.licenseUrl}} See
7
+ <a href="{{brand.licenseUrl}}">License</a>.{{/if}}
7
8
  </p>
8
9
  <div class="footer-links">
9
10
  <a href="https://creativecommons.org/licenses/by-sa/4.0/">
10
11
  <img src="https://licensebuttons.net/l/by-sa/4.0/88x31.png" alt="CC BY-SA 4.0" class="cc-badge">
11
12
  </a>
12
- {{- with .Site.Params.brand }}{{ with .discordURL }}
13
- <a href="{{ . }}">
13
+ {{#if brand.discordUrl}}
14
+ <a href="{{brand.discordUrl}}">
14
15
  <svg width="24" height="19" viewBox="0 0 71 55" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
15
16
  <path d="M60.1 4.9A58.5 58.5 0 0045.4.2a.2.2 0 00-.2.1 40.8 40.8 0 00-1.8 3.7 54 54 0 00-16.2 0A39 39 0 0025.4.3a.2.2 0 00-.2-.1A58.4 58.4 0 0010.5 5a.2.2 0 00-.1 0A60 60 0 00.3 45.3a.2.2 0 000 .2 58.7 58.7 0 0017.7 9 .2.2 0 00.3-.1 42 42 0 003.6-5.9.2.2 0 00-.1-.3 38.6 38.6 0 01-5.5-2.6.2.2 0 01 0-.4c.4-.3.7-.6 1.1-.8a.2.2 0 01.2 0 41.9 41.9 0 0035.6 0 .2.2 0 01.2 0l1 .9a.2.2 0 010 .3 36.2 36.2 0 01-5.5 2.6.2.2 0 00-.1.4 47.1 47.1 0 003.6 5.8.2.2 0 00.2.1A58.5 58.5 0 0070.3 45.5a.2.2 0 000-.2A59.7 59.7 0 0060.2 5a.2.2 0 00-.1 0zM23.7 37.3c-3.5 0-6.4-3.2-6.4-7.1s2.8-7.1 6.4-7.1 6.5 3.2 6.4 7.1c0 3.9-2.8 7.1-6.4 7.1zm23.7 0c-3.5 0-6.4-3.2-6.4-7.1s2.8-7.1 6.4-7.1 6.5 3.2 6.4 7.1c0 3.9-2.8 7.1-6.4 7.1z"/>
16
17
  </svg>
17
18
  Discord
18
19
  </a>
19
- {{- end }}{{ end }}
20
+ {{/if}}
20
21
  </div>
21
22
  </div>
22
23
  </footer>
23
24
 
25
+ {{#if hasMenu}}
24
26
  <script>
25
27
  document.querySelector('.nav-toggle')?.addEventListener('click', function() {
26
28
  document.querySelector('.site-nav').classList.toggle('open');
27
29
  this.classList.toggle('open');
28
30
  });
29
31
  </script>
32
+ {{/if}}
30
33
 
31
- <script src="{{ "js/inline-spoilers.js" | relURL }}" defer></script>
34
+ <script src="{{urls.spoilers}}" defer></script>
@@ -0,0 +1,44 @@
1
+ {{!-- SPDX-License-Identifier: GPL-3.0-or-later --}}
2
+ <header class="site-header" data-pagefind-ignore>
3
+ <div class="header-inner">
4
+ <a href="{{homeUrl}}" class="site-title">
5
+ {{#if brand.logoUrl}}
6
+ <img src="{{brand.logoUrl}}" alt="Heroic Lands" class="site-logo">
7
+ {{/if}}
8
+ <span class="title-main">Heroic Lands</span>
9
+ </a>
10
+ {{#if hasActions}}
11
+ <div class="header-actions">
12
+ {{#if hasMenu}}
13
+ <nav class="site-nav">
14
+ {{#each menu}}
15
+ {{#if hasChildren}}
16
+ <div class="nav-dropdown">
17
+ <a href="{{url}}" class="nav-link{{#if active}} active{{/if}}">
18
+ {{name}}
19
+ <svg class="dropdown-arrow" width="10" height="6" viewBox="0 0 10 6" fill="currentColor"><path d="M1 1l4 4 4-4"/></svg>
20
+ </a>
21
+ <div class="dropdown-menu">
22
+ {{#each children}}
23
+ <a href="{{url}}" class="dropdown-item">{{name}}</a>
24
+ {{/each}}
25
+ </div>
26
+ </div>
27
+ {{else}}
28
+ <a href="{{url}}" class="nav-link{{#if active}} active{{/if}}">{{name}}</a>
29
+ {{/if}}
30
+ {{/each}}
31
+ </nav>
32
+ {{/if}}
33
+ {{#if search}}
34
+ {{> search}}
35
+ {{/if}}
36
+ {{#if hasMenu}}
37
+ <button class="nav-toggle" aria-label="Toggle navigation">
38
+ <span></span><span></span><span></span>
39
+ </button>
40
+ {{/if}}
41
+ </div>
42
+ {{/if}}
43
+ </div>
44
+ </header>