@heroiclands/package-build 20.7.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 +183 -0
- package/CONTENT.md +132 -44
- package/bin/content-build.mjs +37 -5
- package/bin/package-build.mjs +77 -0
- package/content-config.mjs +59 -1
- package/docs/api.md +149 -19
- package/docs/commands.md +75 -0
- package/docs/configuration.md +37 -10
- package/docs/content-format.md +450 -49
- package/engine/content-format.mjs +52 -3
- package/engine/content-images.mjs +699 -0
- package/engine/dependency-bump.mjs +218 -0
- package/engine/frontmatter-lint.mjs +89 -2
- package/engine/helpers.mjs +81 -142
- package/engine/index.mjs +15 -0
- package/engine/infobox-registry.mjs +81 -0
- package/engine/infobox-render.mjs +382 -0
- package/engine/infobox.mjs +963 -0
- package/engine/item-registry.mjs +5 -5
- package/engine/journals.mjs +22 -1
- package/engine/map-notes.mjs +11 -5
- package/engine/metadata-index.mjs +5 -0
- package/engine/note-vocabulary.mjs +57 -2
- package/engine/pathnames.mjs +374 -0
- package/engine/pdf-build.mjs +206 -9
- package/engine/pdf-render.mjs +453 -20
- package/engine/pdf-toc.mjs +77 -5
- package/engine/scenes.mjs +2 -1
- package/engine/site-build.mjs +106 -7
- package/engine/site-index.mjs +93 -4
- package/engine/wikilinks.mjs +93 -0
- package/hm3/default-item-art.mjs +14 -15
- package/hm3/index.mjs +3 -0
- package/hm3/infobox.mjs +64 -0
- package/package.json +1 -1
- package/sohl/default-item-art.mjs +18 -16
- package/sohl/index.mjs +3 -0
- package/sohl/infobox.mjs +499 -0
- package/types/content-config.d.mts +7 -0
- package/types/engine/content-format.d.mts +36 -0
- package/types/engine/content-images.d.mts +281 -0
- package/types/engine/dependency-bump.d.mts +89 -0
- package/types/engine/frontmatter-lint.d.mts +23 -0
- package/types/engine/helpers.d.mts +30 -72
- package/types/engine/index.d.mts +5 -0
- package/types/engine/infobox-registry.d.mts +36 -0
- package/types/engine/infobox-render.d.mts +87 -0
- package/types/engine/infobox.d.mts +443 -0
- package/types/engine/item-registry.d.mts +5 -5
- package/types/engine/journals.d.mts +9 -1
- package/types/engine/note-vocabulary.d.mts +51 -0
- package/types/engine/pathnames.d.mts +189 -0
- package/types/engine/pdf-build.d.mts +46 -0
- package/types/engine/pdf-render.d.mts +97 -1
- package/types/engine/pdf-toc.d.mts +10 -5
- package/types/engine/site-build.d.mts +11 -3
- package/types/engine/site-index.d.mts +35 -3
- package/types/engine/wikilinks.d.mts +22 -0
- package/types/hm3/default-item-art.d.mts +5 -6
- package/types/hm3/index.d.mts +1 -0
- package/types/hm3/infobox.d.mts +22 -0
- package/types/sohl/index.d.mts +1 -0
- package/types/sohl/infobox.d.mts +145 -0
package/docs/content-format.md
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
All markdown will end up generating a JournalNote document: If anything appears before the first H1 header, that will be placed in an "Introduction" page, and then every H1 header will become a subsequent page. The link id of this document will be `<package>-none-<note_type>-<shortcode>`
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
An **infobox** is prepended to the entry's first page, before the prose. It is
|
|
8
|
+
not a page of its own: a page is what a Foundry UUID addresses, and a summary a
|
|
9
|
+
reader has to navigate to is a summary they do not see. What the box holds, and
|
|
10
|
+
where it sits in every other medium, is [the infobox](#the-infobox) below.
|
|
8
11
|
|
|
9
12
|
**There are two kinds of infobox.**
|
|
10
13
|
|
|
@@ -50,6 +53,221 @@ rather than something nobody notices.
|
|
|
50
53
|
|
|
51
54
|
For Web Pages, the entire markdown content will be converted into an HTML page, with appropriate infoboxes.
|
|
52
55
|
|
|
56
|
+
### The infobox
|
|
57
|
+
|
|
58
|
+
**The infobox is generated content at a known position — prepended, before the
|
|
59
|
+
prose.** It is not a floating sidebar and it is not defined in a rendering
|
|
60
|
+
template. The toolchain settles what each box holds; each medium lays that out
|
|
61
|
+
its own way.
|
|
62
|
+
|
|
63
|
+
| medium | placement | collapsing |
|
|
64
|
+
| ------- | ------------------------------------------------------ | ------------------------- |
|
|
65
|
+
| book | flows in the column measure, breaking between sections | none, and none needed |
|
|
66
|
+
| website | side rail on wide screens, inline on narrow | `<details>`, default open |
|
|
67
|
+
| Foundry | inlined, the page being narrow | `<details>`, default open |
|
|
68
|
+
|
|
69
|
+
Two columns are print-only. A scrolling page has no fixed viewport, so columns
|
|
70
|
+
make a reader travel down and back up, and a narrow screen collapses to one
|
|
71
|
+
column regardless.
|
|
72
|
+
|
|
73
|
+
**Six rules hold in every medium.**
|
|
74
|
+
|
|
75
|
+
1. **It is content in document order**, prepended before the prose. An image
|
|
76
|
+
authored before it appears before it.
|
|
77
|
+
2. **It contains no image.** A picture is authored in the text with its own
|
|
78
|
+
directive, and its position governs.
|
|
79
|
+
3. **A section is the unit that flows.** Sections are whole and unbreakable and
|
|
80
|
+
the panel breaks _between_ them, which is what lets a long box cross a
|
|
81
|
+
column or page boundary without splitting a stat grid down the middle.
|
|
82
|
+
4. **An absent field is absent, not empty.** A row with no value is not
|
|
83
|
+
emitted, and neither is a section with no rows: a creature carrying no
|
|
84
|
+
equipment has no `EQUIPMENT` heading rather than an empty one. A placeholder
|
|
85
|
+
asserts a fact that is not there, and an empty heading asserts that something
|
|
86
|
+
was expected here and is missing. A corpus writes an absence three ways and
|
|
87
|
+
all three count: the key is omitted, it holds the field's own default, or it
|
|
88
|
+
holds a **sentinel** — `na`, `none`, `not applicable` — which is the same
|
|
89
|
+
absence written as a word. `Potency: Na` is not a fact about a potion.
|
|
90
|
+
5. **A system box is never an empty panel; it says which silence it is.** Rule
|
|
91
|
+
4 governs rows, and a box is not a row — a heading over nothing asserts that
|
|
92
|
+
something should have been there. See
|
|
93
|
+
[the three states of a system box](#the-three-states-of-a-system-box).
|
|
94
|
+
6. **Order is the toolchain's.** A medium renders boxes, sections and rows in
|
|
95
|
+
the order given.
|
|
96
|
+
|
|
97
|
+
#### What the note box holds
|
|
98
|
+
|
|
99
|
+
**The note box's fields are the type's own `data:` vocabulary, in its declared
|
|
100
|
+
order** — the very table each `### type:` section below states. There is no
|
|
101
|
+
second list, so a key added to a type appears in the book, on the website and
|
|
102
|
+
in a compendium journal with no further edit. A `Name` row stands first on
|
|
103
|
+
every note, because every note has one.
|
|
104
|
+
|
|
105
|
+
A few keys carry no row, and the reason in each case is one of exactly two —
|
|
106
|
+
the key is **machinery**, steering a build or an interface rather than
|
|
107
|
+
describing the subject, or it is an **image**, which rule 2 keeps out of the
|
|
108
|
+
box:
|
|
109
|
+
|
|
110
|
+
| key | why it carries no row |
|
|
111
|
+
| ---------------------------- | ------------------------------------------------ |
|
|
112
|
+
| `templatePriority` | template machinery, not a fact about the subject |
|
|
113
|
+
| `color` | sidebar machinery, not a fact about the subject |
|
|
114
|
+
| `portrait`, `img`, `overlay` | an image, which rule 2 keeps out of the box |
|
|
115
|
+
|
|
116
|
+
A field whose value is a **mapping** carries no row either — a governance
|
|
117
|
+
ladder or a wall layout has no summary shape — and neither does one the note
|
|
118
|
+
left empty.
|
|
119
|
+
|
|
120
|
+
A being's `age`, `height`, `weight` and `appearance.*` compose into a single
|
|
121
|
+
**Appearance** clause rather than taking six rows of their own: _Age 34, 6′ 1″,
|
|
122
|
+
181 lbs, medium frame, brown eyes_.
|
|
123
|
+
|
|
124
|
+
#### What a system box holds
|
|
125
|
+
|
|
126
|
+
**A system box's fields are that system's own field declaration**, in its
|
|
127
|
+
order. There is no second list here either, so a field added to a type reaches
|
|
128
|
+
the box with no further edit.
|
|
129
|
+
|
|
130
|
+
Two things keep a fact off it:
|
|
131
|
+
|
|
132
|
+
- **A value the declaration would have supplied anyway.** A field answered by
|
|
133
|
+
its own default is a fact about the compiler, not about the note — and that is
|
|
134
|
+
true of the value, wherever it was written. A corpus writes its defaults out:
|
|
135
|
+
`improveFlag: false` and `combatCategory: none` are typed into hundreds of
|
|
136
|
+
notes that mean nothing by them.
|
|
137
|
+
- **A fact the note box already put on the page.** A gear item's weight is
|
|
138
|
+
system-agnostic and belongs to the note. What counts is what the note box
|
|
139
|
+
_shows_, not what its vocabulary declares — the same fact is written under
|
|
140
|
+
`data:` on one note and at its destination path on another, and a system box
|
|
141
|
+
that stood down on the declaration alone would leave it on no surface at all.
|
|
142
|
+
|
|
143
|
+
**A measured quantity carries its unit on its value** — `Price 160d`,
|
|
144
|
+
`Weight 1.1 lbs`. The unit belongs to the quantity rather than to the name of
|
|
145
|
+
the quantity, so it is not folded into the label: `Price (d)` beside `160` makes
|
|
146
|
+
a reader reassemble one fact from two cells, and reads worst in the book, whose
|
|
147
|
+
label column is a narrow small-caps rule. A medium cannot supply it either —
|
|
148
|
+
appending `d` to a price means knowing which row is the price, which is the one
|
|
149
|
+
thing a generic renderer must never know. A row carrying a unit is `text`,
|
|
150
|
+
because a number with a unit on it is no longer a number.
|
|
151
|
+
|
|
152
|
+
**What a field is called** is a **presentation overlay** on that same
|
|
153
|
+
declaration, per system. It carries two things a compiler's field list cannot,
|
|
154
|
+
because they are about a page rather than about a document: a reader's word
|
|
155
|
+
where the declaration's key is the compiler's — `assocSkillCode` is exactly
|
|
156
|
+
right in a DataModel and wrong in a panel somebody reads — and the handful of
|
|
157
|
+
keys that belong on no page at all, either because the box shows the same fact
|
|
158
|
+
whole somewhere else or because they steer a character sheet rather than
|
|
159
|
+
describe the subject. It is an overlay and not a list: a field it does not
|
|
160
|
+
mention still gets a row, under its own humanised name.
|
|
161
|
+
|
|
162
|
+
Four types are read differently, because their box is derived rather than read
|
|
163
|
+
field by field — a **being**, whose attributes, skills, mystical abilities and
|
|
164
|
+
carried gear are one flat `sohl.items` list; **armour**, whose protection is
|
|
165
|
+
shown for all four aspects with an unstated one rendered `0`; a **weapon**,
|
|
166
|
+
whose strike modes are shown one per line with an unstated value rendered `—`;
|
|
167
|
+
and a **projectile**, whose impact is three declared fields composing into the
|
|
168
|
+
one quantity a reader wants. Both of those placeholders are decided by the
|
|
169
|
+
toolchain and travel as data, so no renderer has to know which field it is
|
|
170
|
+
looking at.
|
|
171
|
+
|
|
172
|
+
A weapon's strike modes are authored either as a **list**, each mode carrying
|
|
173
|
+
its own `shortcode`, or as a **mapping** keyed by the mode's name. Both are
|
|
174
|
+
read, and both yield the same section.
|
|
175
|
+
|
|
176
|
+
#### The three states of a system box
|
|
177
|
+
|
|
178
|
+
A system box says something on every page that carries one. Which of the three
|
|
179
|
+
it says is decided here and travels as the box's `statement`, so a medium draws
|
|
180
|
+
one thing and decides none of it.
|
|
181
|
+
|
|
182
|
+
| state | when | what the box says |
|
|
183
|
+
| -------------- | ---------------------------------------------------------- | --------------------------------------- |
|
|
184
|
+
| content | the system holds something the note box has not shown | its sections, and no `statement` |
|
|
185
|
+
| not available | the system maps this note's type but produced no document | `statement: Not available` |
|
|
186
|
+
| nothing to add | the system produced a document holding nothing new to show | `statement: Nothing beyond the profile` |
|
|
187
|
+
|
|
188
|
+
The third state is not the second. _Not available_ is a statement about this
|
|
189
|
+
note — the system has no document for it, and a reader can act on that. A
|
|
190
|
+
system that _does_ compile the note and holds only facts the note box has
|
|
191
|
+
already given says so instead, because a reader told "not available" would go
|
|
192
|
+
looking for a document that exists, and an empty panel tells them nothing at
|
|
193
|
+
all.
|
|
194
|
+
|
|
195
|
+
A system that does not map the note's type gets **no box**. A box reading
|
|
196
|
+
_Not available_ on every affiliation page would suggest a gap in the note when
|
|
197
|
+
the truth is about the system's scope.
|
|
198
|
+
|
|
199
|
+
#### Four section layouts
|
|
200
|
+
|
|
201
|
+
A section says how it is arranged, and a renderer switches on that and on a
|
|
202
|
+
value's `kind` — never on a field name, which is what keeps a field list from
|
|
203
|
+
leaking back into a template.
|
|
204
|
+
|
|
205
|
+
| `layout` value | shape | what carries it | where it is used |
|
|
206
|
+
| -------------- | ------------------------------- | --------------- | -------------------------- |
|
|
207
|
+
| `rows` | label/value pairs, one per line | `rows` | a profile |
|
|
208
|
+
| `grid` | short label/value cells | `cells` | attributes, armour aspects |
|
|
209
|
+
| `runin` | groups of comma-joined entries | `groups` | skills, equipment |
|
|
210
|
+
| `list` | one entry per line | `entries` | mystical abilities |
|
|
211
|
+
|
|
212
|
+
| `kind` value | the value is |
|
|
213
|
+
| ------------ | ------------------------------- |
|
|
214
|
+
| `text` | a string |
|
|
215
|
+
| `number` | a number |
|
|
216
|
+
| `link` | `{text, url?, uuid?, address?}` |
|
|
217
|
+
| `links` | a list of those |
|
|
218
|
+
| `list` | a list of strings |
|
|
219
|
+
|
|
220
|
+
A website renders a link by its `url`, a compendium journal by its `uuid`, and
|
|
221
|
+
the book by the `address`, which is the entry's own label in the volume. A
|
|
222
|
+
reference the index could not reach keeps its words rather than being dropped.
|
|
223
|
+
|
|
224
|
+
**Emitted shape**, as a page's front matter carries it:
|
|
225
|
+
|
|
226
|
+
```yaml
|
|
227
|
+
infoboxes:
|
|
228
|
+
- id: note
|
|
229
|
+
kind: note
|
|
230
|
+
title: Profile
|
|
231
|
+
sections:
|
|
232
|
+
- id: profile
|
|
233
|
+
layout: rows
|
|
234
|
+
rows:
|
|
235
|
+
- label: Name
|
|
236
|
+
kind: text
|
|
237
|
+
value: Brànwâal Dôrgaar
|
|
238
|
+
- label: Affiliations
|
|
239
|
+
kind: links
|
|
240
|
+
value:
|
|
241
|
+
- text: The Silent Talon Company
|
|
242
|
+
url: /thalorna/affiliation-slntlncmpny/
|
|
243
|
+
- id: sohl
|
|
244
|
+
kind: system
|
|
245
|
+
system: sohl
|
|
246
|
+
title: SoHL
|
|
247
|
+
available: true
|
|
248
|
+
sections:
|
|
249
|
+
- id: attributes
|
|
250
|
+
label: Attributes
|
|
251
|
+
layout: grid
|
|
252
|
+
cells:
|
|
253
|
+
- label: STR
|
|
254
|
+
value: 14
|
|
255
|
+
- id: skills
|
|
256
|
+
label: Skills
|
|
257
|
+
layout: runin
|
|
258
|
+
groups:
|
|
259
|
+
- label: Combat
|
|
260
|
+
entries:
|
|
261
|
+
- text: Melee 75
|
|
262
|
+
- id: hm3
|
|
263
|
+
kind: system
|
|
264
|
+
system: hm3
|
|
265
|
+
title: HM3
|
|
266
|
+
available: false
|
|
267
|
+
sections: []
|
|
268
|
+
statement: Not available
|
|
269
|
+
```
|
|
270
|
+
|
|
53
271
|
### Frontmatter has three regions
|
|
54
272
|
|
|
55
273
|
A note's frontmatter divides into three parts, and the difference matters because
|
|
@@ -89,6 +307,7 @@ is declared, so a near miss is a finding that names what you probably meant.
|
|
|
89
307
|
| **place character** | `place` | `fortified`, `temple`, `market`, `trading`, `merchant`, `mining`, `fishing`, `naval`, `military`, `imperial`, `provincial`, `coastal`, `river`, `lakeside`, `hill`, `mountain`, `valley`, `forest`, `woodland`, `inland`, `island`, `frontier`, `border`, `craft`, `caravan`, `pilgrimage`, `holy`, `sacred`, `free` |
|
|
90
308
|
| **place scale** | `place` / `region` | `continent` |
|
|
91
309
|
| **being station** | `being` | `tradesfolk`, `common-folk`, `soldiery`, `administration`, `clergy`, `mages`, `underworld`, `dependents`, `guilded`, `unguilded` |
|
|
310
|
+
| **being kind** | `being` | `character`, `creature` |
|
|
92
311
|
| **state** | any | `draft` |
|
|
93
312
|
|
|
94
313
|
**`draft` is the one tag either build reads.** A note tagged `draft` exists so a
|
|
@@ -124,6 +343,50 @@ first because a person may be several at once and because nothing ranks
|
|
|
124
343
|
**A continent is a region carrying a tag, not a subtype**, because structurally it
|
|
125
344
|
is a region: the same fields, the same parent chain, everything but scale.
|
|
126
345
|
|
|
346
|
+
#### A being's kind is the one tag group that is a slot
|
|
347
|
+
|
|
348
|
+
A person and a beast carry very different amounts of data, and a being note says
|
|
349
|
+
which it is with a tag rather than leaving it to be guessed from how much the
|
|
350
|
+
note holds.
|
|
351
|
+
|
|
352
|
+
| `beingKind` value | the subject is |
|
|
353
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
354
|
+
| `character` | a person — someone with a name, a station, a history, and the attributes, skills and carried gear that go with living a life |
|
|
355
|
+
| `creature` | a beast, a monster or a made thing — statted for what it does rather than for who it is, and usually carrying no equipment and no affiliation |
|
|
356
|
+
|
|
357
|
+
**The slot is filled once, or not at all.** The two are alternative answers to
|
|
358
|
+
one question, so a note carrying both has answered it twice and every reader of
|
|
359
|
+
the tag gets to pick; that is refused as an error naming the note. A being
|
|
360
|
+
carrying **neither** is not a finding. The kind is authored deliberately, and
|
|
361
|
+
nothing can tell a being nobody has classified from one whose author means to
|
|
362
|
+
leave it unclassified — a tree part-way through tagging is a tree with untagged
|
|
363
|
+
beings in it, and failing its build would be a rule about the schedule rather
|
|
364
|
+
than about the content.
|
|
365
|
+
|
|
366
|
+
**Nothing else fills the slot, and nothing else is refused for failing to.** A
|
|
367
|
+
being tagged `undead` or `beast-of-burden` is describing the subject in the
|
|
368
|
+
author's own words, in a region that is open, and this build has no standing to
|
|
369
|
+
take that back. What is refused is a near miss of one of the two — `charcter`
|
|
370
|
+
drops the note out of every query for characters while the list still renders —
|
|
371
|
+
and the two of them together. Those are the only two refusals a closed tag
|
|
372
|
+
vocabulary can make here, and both are errors, so a build carrying one fails.
|
|
373
|
+
|
|
374
|
+
**The same two words name two other things, and neither is this.** `hm3.type`
|
|
375
|
+
takes `character` or `creature` as the HM3 _document_ a being compiles to, in a
|
|
376
|
+
system block rather than in `tags:`. Both are also **retired note types**, so a
|
|
377
|
+
note writing `type: character` is refused by name and told to write `being`.
|
|
378
|
+
Three namespaces, one pair of words; the tag is the system-agnostic one, and it
|
|
379
|
+
is the only one that describes the subject rather than a document.
|
|
380
|
+
|
|
381
|
+
**Neither the book nor the website branches on the kind.** One flow serves both
|
|
382
|
+
— the authored image, then the infobox, then the prose — and a creature's box is
|
|
383
|
+
simply shorter, because it has less in it. A section with nothing to put in it is
|
|
384
|
+
not emitted at all, so a creature with no equipment has no `EQUIPMENT` heading
|
|
385
|
+
rather than an empty one, exactly as rule 4 of [the infobox](#the-infobox) drops
|
|
386
|
+
a row with no value. The tag classifies the subject, which is what makes a list
|
|
387
|
+
of the creatures in a setting a thing a query can ask for; it is not an
|
|
388
|
+
instruction to a renderer.
|
|
389
|
+
|
|
127
390
|
The mapping tables below describe the **document** destinations. A key that
|
|
128
391
|
appears in no table still reaches the web page; it simply reaches no Foundry
|
|
129
392
|
document.
|
|
@@ -331,47 +594,91 @@ item compiled from a template note loses the fact that it is one
|
|
|
331
594
|
The row states the mapping the format makes;
|
|
332
595
|
the gap is in the pass, not in the table.
|
|
333
596
|
|
|
334
|
-
####
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
`
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
**
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
597
|
+
#### A pathname names the package that owns the file
|
|
598
|
+
|
|
599
|
+
A note names a file once, and four surfaces have to serve it: a Foundry
|
|
600
|
+
install, this repository's own working tree, the website, and the book. Each
|
|
601
|
+
addresses the same file differently, so an authored pathname is a **statement
|
|
602
|
+
of ownership** and every surface derives its own address from it.
|
|
603
|
+
|
|
604
|
+
**The first segment names the package, when an `assets/` follows it.**
|
|
605
|
+
Everything after that `assets/` is the _suffix_ — the path inside the package's
|
|
606
|
+
shipped tree, and the one piece every derived form is built from. A pathname
|
|
607
|
+
that does not open `<package>/assets/` belongs to the package being compiled,
|
|
608
|
+
and the whole of it is the suffix.
|
|
609
|
+
|
|
610
|
+
```yaml
|
|
611
|
+
img: images/beings/athlwvthrnd-portrait.webp # this package's
|
|
612
|
+
img: sohl/assets/icons/noun/shield.svg # the sohl package's
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
The four forms, for a `thalorna` note (the `thalorna` package ships as the
|
|
616
|
+
Foundry module `sohl-thalorna`) writing `images/map.webp`:
|
|
617
|
+
|
|
618
|
+
| Surface | Address |
|
|
619
|
+
| ----------- | -------------------------------------------------------- |
|
|
620
|
+
| **Foundry** | `modules/sohl-thalorna/assets/images/map.webp` |
|
|
621
|
+
| **Local** | `assets/images/map.webp` |
|
|
622
|
+
| **Web** | `https://cdn.heroiclands.org/thalorna/images/map.webp` |
|
|
623
|
+
| **Book** | `assets/images/map.webp`, staged beside the Typst source |
|
|
624
|
+
|
|
625
|
+
And for the same note writing `sohl/assets/icons/noun/shield.svg`, a file the
|
|
626
|
+
system ships and this repository does not hold:
|
|
627
|
+
|
|
628
|
+
| Surface | Address |
|
|
629
|
+
| ----------- | --------------------------------------------------------- |
|
|
630
|
+
| **Foundry** | `systems/sohl/assets/icons/noun/shield.svg` |
|
|
631
|
+
| **Local** | `assets/icons/noun/shield.svg`, in the `sohl` repository |
|
|
632
|
+
| **Web** | `https://cdn.heroiclands.org/sohl/icons/noun/shield.svg` |
|
|
633
|
+
| **Book** | not carried — a build stages only what this package ships |
|
|
634
|
+
|
|
635
|
+
**The package's name is not its Foundry id.** `thalorna` is what the content is
|
|
636
|
+
called, what a note writes, and what the website serves it under.
|
|
637
|
+
`sohl-thalorna` is what Foundry installs the module as, and it appears in the
|
|
638
|
+
Foundry form alone. The two words are the same for `sohl` and for `hm3`, which
|
|
639
|
+
is exactly why they are kept apart here.
|
|
640
|
+
|
|
641
|
+
Which packages a build can resolve is derived from its configuration: its own
|
|
642
|
+
`contentPackage`, every game system it compiles content for (`systems:`,
|
|
643
|
+
`packs[].system`, `requiresSystem`, `relationships.systems`), and every package
|
|
644
|
+
it declares under `relationships`, whose `contentPackage` names the content
|
|
645
|
+
where that differs from the Foundry id. A pathname naming a package the build
|
|
646
|
+
does not know still resolves on the website and in the book — those need only
|
|
647
|
+
the name and the suffix — and has no Foundry address, which is refused rather
|
|
648
|
+
than guessed.
|
|
649
|
+
|
|
650
|
+
**The host is configuration.** `site.assets` in `package-build.config.yaml` is
|
|
651
|
+
the root the web form is joined onto, and a package-owned image on a page with
|
|
652
|
+
none set is an error naming that key.
|
|
653
|
+
|
|
654
|
+
**A pathname naming no package at all passes through on every surface** — an
|
|
655
|
+
absolute URL, a `data:` URI, a protocol-relative `//host/…`, or a `/`-rooted
|
|
656
|
+
path, which Foundry serves from its data root. That is how a note addresses
|
|
657
|
+
core Foundry art (`/icons/svg/mystery-man.svg`) or a package this build knows
|
|
658
|
+
nothing of (`/systems/dnd5e/icons/spell.webp`).
|
|
659
|
+
|
|
660
|
+
**A `systems/…` or `modules/…` pathname is refused**, with a finding naming the
|
|
661
|
+
replacement. It is a Foundry address written where an ownership statement
|
|
662
|
+
belongs: it resolves for Foundry and for nothing else, because neither the
|
|
663
|
+
website nor the book has any such directory. `systems/sohl/assets/ui/logo.webp`
|
|
664
|
+
is written `sohl/assets/ui/logo.webp`, and `systems/hm3/images/svg/sword.svg` —
|
|
665
|
+
where the package serves its pictures from its own root — is written
|
|
666
|
+
`hm3/assets/images/svg/sword.svg`, because `assets/` is where a package's files
|
|
667
|
+
sit in every derived form.
|
|
668
|
+
|
|
669
|
+
`worlds/` is not a Foundry root for this purpose. A package may not ship files
|
|
670
|
+
out of a world, so a note writing one has made a different mistake and gets the
|
|
671
|
+
ordinary "this package owns it" reading — a plainly broken path rather than a
|
|
672
|
+
plausible one that 404s with nothing reporting it.
|
|
673
|
+
|
|
674
|
+
**The rule is about ownership, not a list of directories.** A package owns its
|
|
675
|
+
whole `assets/` tree and the directory names inside it are its own business:
|
|
676
|
+
`sohl-kethira-basic` keeps art under `assets/artwork/`, and `artwork/deity.webp`
|
|
677
|
+
is that package's by the same rule that claims `icons/…` and `images/…`.
|
|
678
|
+
|
|
679
|
+
**Every pathname a note carries follows it.** `img:` and `data.portrait:`; a
|
|
680
|
+
map note's background, overlay, tile textures and ambient sounds; and the
|
|
681
|
+
address of every image in a note's body.
|
|
375
682
|
|
|
376
683
|
#### `banner:` addresses the CDN, not the Foundry install
|
|
377
684
|
|
|
@@ -388,19 +695,20 @@ and **anything else is prefixed with `images/`** and joined onto
|
|
|
388
695
|
to a doubled path:
|
|
389
696
|
|
|
390
697
|
```text
|
|
391
|
-
banner:
|
|
392
|
-
→ <cdnBaseURL>/images/
|
|
698
|
+
banner: sohl/assets/images/banners/lore.webp
|
|
699
|
+
→ <cdnBaseURL>/images/sohl/assets/images/banners/lore.webp
|
|
393
700
|
```
|
|
394
701
|
|
|
395
702
|
That can be made to work by mirroring the path on the CDN, and one consumer
|
|
396
703
|
does exactly that — but it is not what the author meant.
|
|
397
704
|
|
|
398
705
|
**The two are not reconciled, because they are not two spellings of one thing.**
|
|
399
|
-
|
|
400
|
-
it
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
706
|
+
A pathname names a file a package ships, and four surfaces derive an address
|
|
707
|
+
from it. `banner:` names a hero image on the site's own asset host, reaches no
|
|
708
|
+
compiled document and no book, and is resolved by the theme rather than by this
|
|
709
|
+
toolchain — `banner: none` is not even a target. Write a `banner:` relative to
|
|
710
|
+
the CDN's `images/` root — `banners/lore.webp`, not `images/banners/lore.webp`
|
|
711
|
+
and not a package-qualified pathname.
|
|
404
712
|
|
|
405
713
|
#### The pack a note compiles into
|
|
406
714
|
|
|
@@ -1086,6 +1394,99 @@ Any header can include curly braces. Inside the curly braces:
|
|
|
1086
1394
|
- `.class1` represents a CSS class named `class1` (any number of classes allowed)
|
|
1087
1395
|
- `attr="value"` represents an HTML attribute named `attr` whose value is `value` (any number of attr/value pairs allowed)
|
|
1088
1396
|
|
|
1397
|
+
#### Images
|
|
1398
|
+
|
|
1399
|
+
An image is authored in the body, in the place it belongs, and every surface
|
|
1400
|
+
renders it there — the book, the website and a Foundry Journal Page alike. One
|
|
1401
|
+
authored statement, three renderers, which is the single-sourcing every other
|
|
1402
|
+
part of this format follows.
|
|
1403
|
+
|
|
1404
|
+
```markdown
|
|
1405
|
+
{float: top-left}
|
|
1406
|
+
|
|
1407
|
+
{.full-width}
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
**An image is a block.** It stands alone in its paragraph, with a blank line
|
|
1411
|
+
either side of it, and every surface renders it as a figure. An image sharing a
|
|
1412
|
+
paragraph with prose is refused, because a width and a position mean nothing
|
|
1413
|
+
applied to a word in the middle of a sentence.
|
|
1414
|
+
|
|
1415
|
+
**The alt text is the caption.** Print has no `alt` attribute and has to put
|
|
1416
|
+
those words somewhere a reader can see them, so every surface draws them under
|
|
1417
|
+
the picture; the two HTML surfaces carry them as `alt` as well. A title —
|
|
1418
|
+
`` — is refused rather than dropped in silence: there is one
|
|
1419
|
+
place for those words and this is it.
|
|
1420
|
+
|
|
1421
|
+
##### Width is a class, and the ordinary width carries no marker
|
|
1422
|
+
|
|
1423
|
+
| `class` value | book | website | Foundry journal |
|
|
1424
|
+
| ------------- | ------------- | ------------------ | ---------------- |
|
|
1425
|
+
| _(none)_ | one column | the text measure | the page measure |
|
|
1426
|
+
| `.full-width` | the full page | full content width | full page width |
|
|
1427
|
+
|
|
1428
|
+
The simple case needs no spelling, which is why the ordinary width has no name.
|
|
1429
|
+
A third width joins this table as a name.
|
|
1430
|
+
|
|
1431
|
+
**No pixel values, ever.** A number means something in a browser and nothing
|
|
1432
|
+
coherent in print, and a directive carrying a class and a dimension at once
|
|
1433
|
+
gives one question two answers with no rule for which wins. `{width=800}` is
|
|
1434
|
+
refused.
|
|
1435
|
+
|
|
1436
|
+
##### Position is `float:`
|
|
1437
|
+
|
|
1438
|
+
| `float` value | book | website and Foundry journal |
|
|
1439
|
+
| -------------- | -------------------- | ------------------------------- |
|
|
1440
|
+
| `top-left` | top of the column | floated left, text wraps |
|
|
1441
|
+
| `bottom-left` | bottom of the column | floated left, text wraps |
|
|
1442
|
+
| `top-right` | top of the column | floated right, text wraps |
|
|
1443
|
+
| `bottom-right` | bottom of the column | floated right, text wraps |
|
|
1444
|
+
| `center` | top of the column | centred, with no text beside it |
|
|
1445
|
+
|
|
1446
|
+
**Print cannot wrap text around an arbitrary shape.** A Typst float occupies
|
|
1447
|
+
the column measure, so in the book only the vertical half of a position has an
|
|
1448
|
+
effect — top of the column or bottom of it — while the horizontal half does
|
|
1449
|
+
not. The website and a Foundry journal get true CSS wrap from the same
|
|
1450
|
+
directive. Expect the same statement, not the same page.
|
|
1451
|
+
|
|
1452
|
+
An image with no `float:` is an ordinary block in the flow, where it was
|
|
1453
|
+
written. A `.full-width` image is placed at the top of the page it falls on,
|
|
1454
|
+
since a block cannot leave the column it is set in.
|
|
1455
|
+
|
|
1456
|
+
##### Both vocabularies are closed
|
|
1457
|
+
|
|
1458
|
+
`{.fullwidth}`, `{.full_width}`, `{width=800}` and `{float: middle}` are
|
|
1459
|
+
refused, located by file, line and column, and the build fails. An unrecognised
|
|
1460
|
+
value rendering as the ordinary width is the failure worth preventing, because
|
|
1461
|
+
it looks exactly like a directive that worked.
|
|
1462
|
+
|
|
1463
|
+
A directive holding anything this section does not name is not honoured at all,
|
|
1464
|
+
even in the part that parsed: a half-honoured directive is the same silent
|
|
1465
|
+
failure in a smaller costume. The image keeps its braces and renders them as
|
|
1466
|
+
literal text, so the mistake is visible on the page as well as in the log.
|
|
1467
|
+
|
|
1468
|
+
**Nothing else reaches emitted markup.** The two vocabularies and the address
|
|
1469
|
+
are all that leave a note; there is no `style`, no `id` and no arbitrary
|
|
1470
|
+
attribute, because data is never compiled into markup here. An address carries
|
|
1471
|
+
a scheme of `http:` or `https:`, or none at all.
|
|
1472
|
+
|
|
1473
|
+
##### Where an address resolves
|
|
1474
|
+
|
|
1475
|
+
An image's address follows the rule [`img:` follows](#an-asset-paths-first-segment-says-which-package-owns-it)
|
|
1476
|
+
— its first segment says which package owns the file — and each surface
|
|
1477
|
+
resolves it to what that surface serves:
|
|
1478
|
+
|
|
1479
|
+
- **Foundry** is handed the path inside the install, so `images/map.webp`
|
|
1480
|
+
reaches a journal page as `modules/<package>/assets/images/map.webp`.
|
|
1481
|
+
- **The book** is handed a copy of the file, staged out of this package's own
|
|
1482
|
+
asset tree into the build directory before the compiler runs. An address
|
|
1483
|
+
naming a file this package does not ship — another package's, or a URL —
|
|
1484
|
+
cannot be staged, and the book prints the caption alone and says so.
|
|
1485
|
+
- **The website** passes the address through exactly as authored, because a
|
|
1486
|
+
site serves its imagery from its own asset host and this package is not told
|
|
1487
|
+
what that host is. An address that has to resolve there is written as a full
|
|
1488
|
+
URL.
|
|
1489
|
+
|
|
1089
1490
|
#### Content tables
|
|
1090
1491
|
|
|
1091
1492
|
A fenced `dataview` block is replaced by the table its query selects:
|