@heroiclands/package-build 21.0.0 → 21.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 21.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7577722: **Headings in the book**
8
+
9
+ - A heading is set by its depth rather than every heading alike: a note's top-level heading opens large, keeps its underline and takes a clear run of space above it, and each level beneath is smaller and separated by space alone.
10
+ - Headings set in the sans face and in the case they were written in, so a section reads as words rather than as tracked capitals. A book that names no sans face still sets them in its book face, exactly as before.
11
+ - A parent section and the sections inside it can be told apart at a glance, without reading the words.
12
+ - 57fee4c: **Infobox panels in the book**
13
+
14
+ - Each panel is drawn as a closed box — a light border on all four sides over a tint a step below the paper — so a page carrying Profile, system and appearance panels reads as a stack of cards rather than a run of rules.
15
+ - Skill and equipment groups stand apart from one another, so a group that wraps onto a second line is no longer mistaken for the start of the next one.
16
+
17
+ **Naming the systems**
18
+
19
+ - The system panels are titled _Song of Heroic Lands_ and _HârnMaster 3_ in full, in the book and on the web, in place of the abbreviations.
20
+
21
+ ### Patch Changes
22
+
23
+ - 705cdc8: **The book** — an entry's first paragraph sets like any other. A raised opening
24
+ letter is something a note asks for, not something the book decides on its
25
+ own, so nothing raises one.
26
+
3
27
  ## 21.0.0
4
28
 
5
29
  ### Major Changes
package/CONTENT.md CHANGED
@@ -2467,10 +2467,8 @@ alone — a scrolling page has no fixed viewport, so the website keeps one
2467
2467
  measure with a side rail.
2468
2468
 
2469
2469
  A note's `description:` sets as an epigraph between short rules under the
2470
- plate, and the first paragraph of the prose opens on a raised capital. Both
2471
- disappear rather than leave a shell: a note with no description has no
2472
- epigraph, and a body opening on a link or a number keeps its own first
2473
- character. The running foot carries the section's name, an ornament and the
2470
+ plate. It disappears rather than leave a shell: a note with no description has
2471
+ no epigraph. The running foot carries the section's name, an ornament and the
2474
2472
  folio.
2475
2473
 
2476
2474
  A table of **more than three columns** is given an explicit span rather than
@@ -360,9 +360,10 @@ export function infoboxTypstPreamble() {
360
360
  '#let infobox-rule = rgb("#7c3b1e")',
361
361
  '#let infobox-faint = rgb("#6b6357")',
362
362
  '#let infobox-hairline = rgb("#c9bfa8")',
363
+ '#let infobox-fill = rgb("#eae2d0")',
363
364
  "#let infobox-panel(body) = block(width: 100%, breakable: true, " +
364
- "inset: (x: 9pt, y: 8pt), below: 0.8em, " +
365
- "stroke: (top: 1.4pt + infobox-rule, bottom: 1.4pt + infobox-rule))[" +
365
+ "inset: (x: 9pt, y: 8pt), below: 0.8em, radius: 2pt, " +
366
+ "fill: infobox-fill, stroke: 0.5pt + infobox-hairline)[" +
366
367
  "#set par(first-line-indent: 0em, justify: false, leading: 0.40em)\n#body]",
367
368
  '#let infobox-title(t) = { text(size: 9pt, weight: "bold", tracking: 1.3pt, ' +
368
369
  "fill: infobox-rule)[#upper(t)]; v(0.20em) }",
@@ -373,7 +374,7 @@ export function infoboxTypstPreamble() {
373
374
  "#let infobox-cell(lab, val) = align(center)[" +
374
375
  "#text(size: 7pt, tracking: 0.6pt, fill: infobox-faint)[#lab]#h(2.5pt)" +
375
376
  '#text(size: 8.6pt, weight: "bold")[#val]]',
376
- "#let infobox-runin(lab, body) = block(below: 0.28em, breakable: false)[" +
377
+ "#let infobox-runin(lab, body) = block(below: 0.78em, breakable: false)[" +
377
378
  "#set par(justify: false, first-line-indent: 0em, hanging-indent: 0.5cm, leading: 0.40em)\n" +
378
379
  '#text(size: 7.8pt)[#if lab != "" [ #text(weight: "bold")[#lab: ] ]#body]]',
379
380
  '#let infobox-statement(t) = text(size: 8pt, style: "italic", fill: infobox-faint)[#t]',
@@ -498,8 +498,6 @@ export async function buildPdf({ config, out, version = "", compile = true } = {
498
498
  images,
499
499
  headingOffset,
500
500
  anchorPrefix,
501
- // An entry opens a page, so its first paragraph opens the page too.
502
- dropCap: true,
503
501
  });
504
502
  // The infobox is generated content in document order — prepended,
505
503
  // before the prose. An image the note authored ahead of it still comes
@@ -168,7 +168,6 @@ export function createParser(registry) {
168
168
  * note's own `##` nests beneath the entry heading the book gave it.
169
169
  * @param {string} [opts.anchorPrefix] - The entry's anchor, which namespaces
170
170
  * every `{#slug}` the body declares.
171
- * @param {boolean} [opts.dropCap] - Whether to open the body with a raised
172
171
  * capital. Set for an entry, which begins a page; not for front matter or a
173
172
  * prose file, which carry headings of their own.
174
173
  * @returns {string} Typst markup.
@@ -181,7 +180,6 @@ export function markdownToTypst(markdown, opts = {}) {
181
180
  images = new Map(),
182
181
  headingOffset = 0,
183
182
  anchorPrefix = "",
184
- dropCap = false,
185
183
  } = opts;
186
184
  const tokens = md.parse(String(markdown ?? ""), {});
187
185
  // One map for the whole body, not one per block: a heading inside a
@@ -194,7 +192,6 @@ export function markdownToTypst(markdown, opts = {}) {
194
192
  images,
195
193
  headingOffset,
196
194
  anchorPrefix,
197
- dropCap,
198
195
  seen: new Map(),
199
196
  });
200
197
  }
@@ -258,7 +255,7 @@ function renderBlock(tokens, i, out, ctx) {
258
255
  return 3;
259
256
  }
260
257
  case "paragraph_open": {
261
- out.push(`\n${openingParagraph(renderInline(tokens[i + 1], ctx), ctx)}\n\n`);
258
+ out.push(`\n${renderInline(tokens[i + 1], ctx)}\n\n`);
262
259
  return 3;
263
260
  }
264
261
  case "fence":
@@ -271,7 +268,7 @@ function renderBlock(tokens, i, out, ctx) {
271
268
  return 1;
272
269
  case "blockquote_open": {
273
270
  const end = matching(tokens, i, "blockquote_open", "blockquote_close");
274
- const inner = renderTokens(tokens.slice(i + 1, end), { ...ctx, dropCap: false });
271
+ const inner = renderTokens(tokens.slice(i + 1, end), { ...ctx });
275
272
  out.push(`\n#quote(block: true)[${inner}]\n\n`);
276
273
  return end - i + 1;
277
274
  }
@@ -298,29 +295,6 @@ function renderBlock(tokens, i, out, ctx) {
298
295
  }
299
296
  }
300
297
 
301
- /**
302
- * The first paragraph of an entry, opened with a raised capital.
303
- *
304
- * Only the first, and only when it begins with a letter: a paragraph opening
305
- * on a link, a bold run or a number has no character to raise, and raising
306
- * whatever happened to be first would put a 26pt accent on a bracket. The
307
- * chance is spent either way — a body opens once — so a paragraph that cannot
308
- * take the capital simply sets as itself.
309
- *
310
- * @param {string} rendered - The paragraph's Typst markup.
311
- * @param {object} ctx - Render context.
312
- * @returns {string} The same markup, or it with a raised capital.
313
- */
314
- function openingParagraph(rendered, ctx) {
315
- if (!ctx.dropCap) return rendered;
316
- ctx.dropCap = false;
317
- // A letter is never escaped, so the first character of the markup is the
318
- // first character of the prose whenever the prose starts with one.
319
- const match = /^(\p{L})([\s\S]*)$/u.exec(rendered);
320
- if (!match) return rendered;
321
- return `#book-dropcap[${match[1]}]#h(1pt)${match[2]}`;
322
- }
323
-
324
298
  /**
325
299
  * A heading's text, and the `{#slug}` it may end with.
326
300
  *
@@ -438,7 +412,7 @@ function listItems(tokens, start, end, ctx) {
438
412
  continue;
439
413
  }
440
414
  const close = matching(tokens, i, "list_item_open", "list_item_close");
441
- items.push(renderTokens(tokens.slice(i + 1, close), { ...ctx, dropCap: false }));
415
+ items.push(renderTokens(tokens.slice(i + 1, close), { ...ctx }));
442
416
  i = close + 1;
443
417
  }
444
418
  return items;
@@ -825,6 +799,7 @@ export function bookTypstPreamble() {
825
799
  '#let book-ink = rgb("#241f1a")',
826
800
  '#let book-paper = rgb("#f4efe4")',
827
801
  '#let book-accent = rgb("#7c3b1e")',
802
+ '#let book-head = rgb("#5e2b14")',
828
803
  '#let book-faint = rgb("#6b6357")',
829
804
  "#let book-ornament = box(baseline: 1pt, " +
830
805
  "rotate(45deg, rect(width: 3pt, height: 3pt, fill: book-accent)))",
@@ -835,16 +810,34 @@ export function bookTypstPreamble() {
835
810
  "}",
836
811
  // A heading justifies and hyphenates like body text unless told
837
812
  // otherwise, and both make a display line look broken.
838
- "#let book-sechead(it) = block(width: 100%, above: 0.9em, below: 0.5em, " +
839
- "breakable: false)[\n" +
840
- " #set par(justify: false, first-line-indent: 0em)\n" +
841
- " #set text(hyphenate: false)\n" +
842
- ' #text(size: 11pt, weight: "bold", tracking: 1.6pt, fill: book-accent)[#upper(it.body)]\n' +
843
- " #v(-0.35em)\n" +
844
- " #line(length: 100%, stroke: 0.5pt + book-accent)\n" +
845
- "]",
846
- '#let book-dropcap(letter) = text(size: 26pt, weight: "bold", fill: book-accent, ' +
847
- "baseline: 5pt)[#letter]",
813
+ //
814
+ // Size, weight and the space above follow the level, so a parent reads
815
+ // as one without the words being read. The rule belongs to the note's own
816
+ // top level alone; every level under it is set apart by space. Headings set
817
+ // in the sans face and in the case they were authored in, so neither
818
+ // capitals nor tracking is carrying the distinction. The scale starts
819
+ // at 3: `book-plate` shadows this rule while it draws the title, so
820
+ // the section landing and the entry title never arrive here, and the
821
+ // headings that do are the note's own.
822
+ "#let book-sechead(it) = {\n" +
823
+ " let lv = it.level\n" +
824
+ " let size = if lv <= 3 { 26pt } else if lv == 4 { 19pt }\n" +
825
+ " else if lv == 5 { 14pt } else { 11pt }\n" +
826
+ " let above = if lv <= 3 { 2.5em } else if lv == 4 { 2.0em }\n" +
827
+ " else { 1.0em }\n" +
828
+ " let below = if lv <= 3 { 0.5em } else if lv == 4 { 0.36em }\n" +
829
+ " else { 0.28em }\n" +
830
+ ' let weight = if lv <= 5 { "bold" } else { "regular" }\n' +
831
+ " block(width: 100%, above: above, below: below, breakable: false)[\n" +
832
+ " #set par(justify: false, first-line-indent: 0em)\n" +
833
+ " #set text(hyphenate: false)\n" +
834
+ " #text(size: size, weight: weight, fill: book-head)[#it.body]\n" +
835
+ " #if lv <= 3 {\n" +
836
+ " v(-0.30em)\n" +
837
+ " line(length: 100%, stroke: 0.5pt + book-accent)\n" +
838
+ " }\n" +
839
+ " ]\n" +
840
+ "}",
848
841
  // The plate bleeds off the paper: the placed panel is the full width of
849
842
  // the sheet and starts a margin above and to the left of wherever the
850
843
  // flow has reached, which on an entry's first page is the top corner.
package/hm3/infobox.mjs CHANGED
@@ -33,7 +33,7 @@ import { GEAR_UNITS, defineInfobox } from "../engine/infobox.mjs";
33
33
  import { HM3_ITEM_FIELDS } from "./item-fields.mjs";
34
34
 
35
35
  /** What this system's box is called. @type {string} */
36
- export const HM3_INFOBOX_TITLE = "HM3";
36
+ export const HM3_INFOBOX_TITLE = "HârnMaster 3";
37
37
 
38
38
  /**
39
39
  * HM3's presentation overlay: what one of this system's fields is called where
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "21.0.0",
3
+ "version": "21.1.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",
package/sohl/infobox.mjs CHANGED
@@ -67,7 +67,7 @@ import { NOTE_SCHEMAS } from "./note-schemas.mjs";
67
67
  import { GEAR_TYPE_TO_KEY } from "./being-info.mjs";
68
68
 
69
69
  /** What this system's box is called. @type {string} */
70
- export const SOHL_INFOBOX_TITLE = "SoHL";
70
+ export const SOHL_INFOBOX_TITLE = "Song of Heroic Lands";
71
71
 
72
72
  /**
73
73
  * What a value nobody stated is shown as, where showing nothing would be the
@@ -55,7 +55,6 @@ export function createParser(registry?: object): object;
55
55
  * note's own `##` nests beneath the entry heading the book gave it.
56
56
  * @param {string} [opts.anchorPrefix] - The entry's anchor, which namespaces
57
57
  * every `{#slug}` the body declares.
58
- * @param {boolean} [opts.dropCap] - Whether to open the body with a raised
59
58
  * capital. Set for an entry, which begins a page; not for front matter or a
60
59
  * prose file, which carry headings of their own.
61
60
  * @returns {string} Typst markup.
@@ -68,7 +67,6 @@ export function markdownToTypst(markdown: string, opts?: {
68
67
  images?: Map<string, string> | undefined;
69
68
  headingOffset?: number | undefined;
70
69
  anchorPrefix?: string | undefined;
71
- dropCap?: boolean | undefined;
72
70
  }): string;
73
71
  /**
74
72
  * The Typst definitions the book's page furniture is drawn with.