explico 0.13.1 → 0.15.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 (256) hide show
  1. package/CHANGELOG.md +990 -1
  2. package/README.md +200 -30
  3. package/dist/compiler/astro.d.ts +15 -2
  4. package/dist/compiler/astro.js +40 -11
  5. package/dist/compiler/cli-main.d.ts +100 -0
  6. package/dist/compiler/cli-main.js +433 -0
  7. package/dist/compiler/cli-reference.d.ts +50 -0
  8. package/dist/compiler/cli-reference.js +213 -0
  9. package/dist/compiler/cli-verbs.d.ts +45 -0
  10. package/dist/compiler/cli-verbs.js +224 -0
  11. package/dist/compiler/cli-verify.d.ts +51 -0
  12. package/dist/compiler/cli-verify.js +151 -0
  13. package/dist/compiler/cli.d.ts +408 -24
  14. package/dist/compiler/cli.js +1328 -8
  15. package/dist/compiler/collections.d.ts +88 -1
  16. package/dist/compiler/collections.js +170 -25
  17. package/dist/compiler/content-release.d.ts +49 -0
  18. package/dist/compiler/content-release.js +63 -0
  19. package/dist/compiler/content-schemas.d.ts +2 -2
  20. package/dist/compiler/course-config.d.ts +1 -1
  21. package/dist/compiler/course-config.js +105 -1
  22. package/dist/compiler/decks.d.ts +161 -0
  23. package/dist/compiler/decks.js +235 -0
  24. package/dist/compiler/exports/combined-html.d.ts +5 -1
  25. package/dist/compiler/exports/deck-fit.d.ts +55 -0
  26. package/dist/compiler/exports/deck-fit.js +90 -0
  27. package/dist/compiler/exports/export-chapters.d.ts +37 -6
  28. package/dist/compiler/exports/export-chapters.js +64 -5
  29. package/dist/compiler/exports/export-pages.d.ts +30 -3
  30. package/dist/compiler/exports/export-pages.js +24 -1
  31. package/dist/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  32. package/dist/compiler/exports/latex/cache.d.ts +78 -0
  33. package/dist/compiler/exports/latex/cache.js +129 -0
  34. package/dist/compiler/exports/latex/capture.d.ts +10 -1
  35. package/dist/compiler/exports/latex/capture.js +3 -1
  36. package/dist/compiler/exports/latex/compile.d.ts +13 -2
  37. package/dist/compiler/exports/latex/compile.js +12 -1
  38. package/dist/compiler/exports/latex/deck.d.ts +92 -0
  39. package/dist/compiler/exports/latex/deck.js +349 -0
  40. package/dist/compiler/exports/latex/document.d.ts +51 -6
  41. package/dist/compiler/exports/latex/document.js +71 -12
  42. package/dist/compiler/exports/latex/explico-common.sty +198 -0
  43. package/dist/compiler/exports/latex/explico.cls +13 -183
  44. package/dist/compiler/exports/latex/serialize.d.ts +60 -4
  45. package/dist/compiler/exports/latex/serialize.js +4 -2
  46. package/dist/compiler/exports/latex-export.d.ts +24 -2
  47. package/dist/compiler/exports/latex-export.js +107 -26
  48. package/dist/compiler/exports/markdown/serialize.d.ts +24 -4
  49. package/dist/compiler/exports/markdown/serialize.js +85 -8
  50. package/dist/compiler/exports/markdown-export.d.ts +6 -1
  51. package/dist/compiler/exports/markdown-export.js +50 -11
  52. package/dist/compiler/exports/standalone-html.d.ts +10 -5
  53. package/dist/compiler/exports/standalone-html.js +3 -3
  54. package/dist/compiler/exports/static-tree.d.ts +38 -1
  55. package/dist/compiler/exports/static-tree.js +120 -4
  56. package/dist/compiler/knowledge-version.d.ts +26 -2
  57. package/dist/compiler/knowledge-version.js +27 -17
  58. package/dist/compiler/lesson-derivation.d.ts +143 -6
  59. package/dist/compiler/lesson-derivation.js +145 -13
  60. package/dist/compiler/manifest.d.ts +133 -4
  61. package/dist/compiler/manifest.js +364 -58
  62. package/dist/compiler/page-versions.d.ts +184 -0
  63. package/dist/compiler/page-versions.js +217 -0
  64. package/dist/compiler/render-diagnostics.d.ts +5 -1
  65. package/dist/compiler/resolution-report.d.ts +24 -6
  66. package/dist/compiler/resolution-report.js +22 -4
  67. package/dist/compiler/validate-content.d.ts +7 -0
  68. package/dist/compiler/validate-content.js +160 -0
  69. package/dist/{course-config-rRtME5UV.d.ts → course-config-BLtIdkei.d.ts} +164 -1
  70. package/dist/curriculum/competency-graph.d.ts +54 -0
  71. package/dist/curriculum/competency-graph.js +41 -0
  72. package/dist/reference/base-library.d.mts +8 -0
  73. package/dist/reference/base-library.mjs +19 -0
  74. package/dist/reference/consistency.d.ts +8 -4
  75. package/dist/reference/consistency.js +82 -26
  76. package/dist/reference/deck-remark.d.mts +123 -0
  77. package/dist/reference/deck-remark.mjs +600 -0
  78. package/dist/reference/deck-source.d.ts +107 -0
  79. package/dist/reference/deck-source.js +140 -0
  80. package/dist/reference/decks-validate.d.ts +133 -0
  81. package/dist/reference/decks-validate.js +359 -0
  82. package/dist/reference/derived-entries.d.ts +5 -1
  83. package/dist/reference/diagram-fit.d.ts +6 -2
  84. package/dist/reference/diagram-fit.js +5 -1
  85. package/dist/reference/figure-symbols.d.ts +23 -6
  86. package/dist/reference/figure-symbols.js +51 -15
  87. package/dist/reference/float-name.d.ts +43 -0
  88. package/dist/reference/float-name.js +58 -0
  89. package/dist/reference/folds-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-indices.js +5 -4
  92. package/dist/reference/gate-math.d.ts +29 -2
  93. package/dist/reference/gate-math.js +99 -18
  94. package/dist/reference/gate-part-pages.d.ts +1 -1
  95. package/dist/reference/lab-katex-validate.d.ts +10 -6
  96. package/dist/reference/lab-katex-validate.js +17 -9
  97. package/dist/reference/lab-math-scope.js +29 -4
  98. package/dist/reference/math-glyphs.d.mts +25 -0
  99. package/dist/reference/math-glyphs.mjs +189 -25
  100. package/dist/reference/page-notation.d.ts +22 -3
  101. package/dist/reference/page-notation.js +32 -10
  102. package/dist/reference/references.d.ts +14 -1
  103. package/dist/reference/references.js +13 -0
  104. package/dist/reference/registry-scope.d.mts +33 -0
  105. package/dist/reference/registry-scope.mjs +34 -0
  106. package/dist/reference/registry.js +29 -10
  107. package/dist/reference/rehype-deck.d.mts +6 -0
  108. package/dist/reference/rehype-deck.mjs +286 -0
  109. package/dist/reference/rehype-heading-anchors.d.mts +20 -0
  110. package/dist/reference/rehype-heading-anchors.mjs +48 -6
  111. package/dist/reference/remark-notation.mjs +249 -40
  112. package/dist/reference/source-lines.d.mts +12 -0
  113. package/dist/reference/source-lines.mjs +65 -0
  114. package/dist/reference/types.d.ts +28 -0
  115. package/dist/rules/catalog.d.ts +21 -4
  116. package/dist/rules/catalog.js +237 -2
  117. package/dist/rules/course-rules.d.ts +5 -1
  118. package/dist/rules/course-rules.js +17 -3
  119. package/dist/rules/course-rules.schema.json +12 -0
  120. package/dist/rules/in-effect.d.ts +13 -3
  121. package/dist/rules/in-effect.js +34 -10
  122. package/dist/rules/rules.schema.json +280 -1
  123. package/dist/rules/settings.d.ts +1 -1
  124. package/dist/session/PreferenceStore.d.ts +6 -0
  125. package/dist/session/PreferenceStore.js +18 -1
  126. package/dist/session/deck-memory.d.ts +116 -0
  127. package/dist/session/deck-memory.js +143 -0
  128. package/dist/session/reading-memory.d.ts +96 -0
  129. package/dist/session/reading-memory.js +133 -0
  130. package/docs/VERSIONING.md +21 -18
  131. package/docs/authoring/README.md +31 -9
  132. package/docs/authoring/agents.md +72 -18
  133. package/docs/authoring/cli-reference.json +2172 -0
  134. package/docs/authoring/cli.md +887 -0
  135. package/docs/authoring/components.md +15 -0
  136. package/docs/authoring/deck.md +612 -0
  137. package/docs/authoring/migrating.md +1195 -16
  138. package/docs/authoring/notation.md +49 -6
  139. package/docs/authoring/structure.md +80 -1
  140. package/docs/authoring/versioning.md +56 -3
  141. package/docs/authoring/visualizations.md +7 -2
  142. package/docs/course-agent/AGENTS.md +40 -0
  143. package/docs/course-agent/agents/rules-reviewer.md +33 -0
  144. package/docs/course-agent/skills/draft-lesson/SKILL.md +38 -0
  145. package/docs/course-agent/skills/migrate-course/SKILL.md +32 -0
  146. package/docs/course-agent/skills/release-content/SKILL.md +26 -0
  147. package/docs/diagram-scaling.md +148 -0
  148. package/docs/privacy.md +59 -0
  149. package/package.json +8 -2
  150. package/src/compiler/astro.ts +60 -21
  151. package/src/compiler/cli-main.ts +557 -0
  152. package/src/compiler/cli-reference.ts +285 -0
  153. package/src/compiler/cli-verbs.ts +270 -0
  154. package/src/compiler/cli-verify.ts +241 -0
  155. package/src/compiler/cli.ts +2119 -170
  156. package/src/compiler/collections.ts +284 -25
  157. package/src/compiler/content-release.ts +91 -0
  158. package/src/compiler/course-config.ts +267 -0
  159. package/src/compiler/decks.ts +455 -0
  160. package/src/compiler/exports/deck-fit.ts +127 -0
  161. package/src/compiler/exports/export-chapters.ts +105 -6
  162. package/src/compiler/exports/export-pages.ts +46 -2
  163. package/src/compiler/exports/latex/beamerthemeexplico.sty +124 -0
  164. package/src/compiler/exports/latex/cache.ts +183 -0
  165. package/src/compiler/exports/latex/capture.ts +5 -2
  166. package/src/compiler/exports/latex/compile.ts +18 -0
  167. package/src/compiler/exports/latex/deck.ts +419 -0
  168. package/src/compiler/exports/latex/document.ts +117 -21
  169. package/src/compiler/exports/latex/explico-common.sty +198 -0
  170. package/src/compiler/exports/latex/explico.cls +13 -183
  171. package/src/compiler/exports/latex/serialize.ts +25 -21
  172. package/src/compiler/exports/latex/texlive-packages.txt +6 -2
  173. package/src/compiler/exports/latex-export.ts +187 -45
  174. package/src/compiler/exports/markdown/serialize.ts +118 -9
  175. package/src/compiler/exports/markdown-export.ts +56 -11
  176. package/src/compiler/exports/standalone-html.ts +8 -7
  177. package/src/compiler/exports/static-tree.ts +175 -3
  178. package/src/compiler/knowledge-version.ts +56 -21
  179. package/src/compiler/lesson-derivation.ts +282 -16
  180. package/src/compiler/manifest.ts +568 -63
  181. package/src/compiler/page-versions.ts +401 -0
  182. package/src/compiler/resolution-report.ts +38 -7
  183. package/src/compiler/validate-content.ts +215 -0
  184. package/src/components/CourseContents.astro +124 -6
  185. package/src/components/CurriculumMap.astro +49 -1
  186. package/src/components/FloatIndex.astro +1 -1
  187. package/src/components/PartOverview.astro +51 -5
  188. package/src/components/assessments/AssessmentSet.astro +41 -15
  189. package/src/components/block-heading.ts +15 -6
  190. package/src/components/card-carry.ts +7 -4
  191. package/src/components/card-manager.ts +14 -2
  192. package/src/components/card-memory.ts +13 -9
  193. package/src/components/deck/DeckEnhancer.astro +13 -0
  194. package/src/components/deck/deck-context.ts +104 -0
  195. package/src/components/deck/deck-controller.ts +1287 -0
  196. package/src/components/deck/deck-map.ts +91 -0
  197. package/src/components/deck-lane.ts +163 -0
  198. package/src/components/diagram/Diagram.astro +131 -86
  199. package/src/components/disclosure/persisted-disclosure.ts +9 -1
  200. package/src/components/figure/Figure.astro +7 -2
  201. package/src/components/figure/FloatCaption.astro +5 -3
  202. package/src/components/fold/Fold.astro +2 -2
  203. package/src/components/notation/NotationFormula.astro +81 -11
  204. package/src/components/notation/NotationGlossary.astro +132 -1
  205. package/src/components/notation/NotationLayer.astro +15 -7
  206. package/src/components/starlight/AboutPanel.astro +186 -3
  207. package/src/components/starlight/CollapsibleSections.astro +24 -3
  208. package/src/components/starlight/DiagramEnhancer.astro +5 -1
  209. package/src/components/starlight/HeadingAnchorEnhancer.astro +1 -1
  210. package/src/components/starlight/LayoutHeader.astro +17 -0
  211. package/src/components/starlight/LayoutPageTitle.astro +110 -0
  212. package/src/components/starlight/LayoutSidebar.astro +807 -21
  213. package/src/components/starlight/LayoutSocialIcons.astro +62 -6
  214. package/src/components/starlight/LessonExportActions.astro +27 -9
  215. package/src/components/starlight/LessonFooter.astro +59 -0
  216. package/src/components/starlight/ReadingMemoryNotice.astro +114 -0
  217. package/src/components/starlight/TextSizeControl.astro +24 -13
  218. package/src/components/starlight/page-marks.ts +139 -0
  219. package/src/components/starlight/section-summary.ts +1 -1
  220. package/src/components/starlight/sidebar-editions.ts +245 -0
  221. package/src/components/table/Table.astro +15 -7
  222. package/src/content-config.ts +87 -1
  223. package/src/curriculum/competency-graph.ts +95 -0
  224. package/src/reference/base-library.mjs +19 -0
  225. package/src/reference/consistency.ts +137 -39
  226. package/src/reference/deck-remark.mjs +600 -0
  227. package/src/reference/deck-source.ts +273 -0
  228. package/src/reference/decks-validate.ts +604 -0
  229. package/src/reference/diagram-fit.ts +18 -4
  230. package/src/reference/diagram-layout.ts +3 -1
  231. package/src/reference/figure-symbols.ts +108 -31
  232. package/src/reference/folds-validate.ts +1 -1
  233. package/src/reference/gate-indices.ts +5 -3
  234. package/src/reference/gate-math.ts +169 -25
  235. package/src/reference/lab-katex-validate.ts +30 -15
  236. package/src/reference/lab-math-scope.ts +34 -3
  237. package/src/reference/math-glyphs.mjs +189 -25
  238. package/src/reference/page-notation.ts +69 -15
  239. package/src/reference/references.ts +28 -0
  240. package/src/reference/registry-scope.mjs +34 -0
  241. package/src/reference/registry.ts +41 -11
  242. package/src/reference/rehype-deck.mjs +286 -0
  243. package/src/reference/rehype-heading-anchors.mjs +48 -6
  244. package/src/reference/remark-notation.mjs +249 -40
  245. package/src/reference/source-lines.mjs +65 -0
  246. package/src/reference/types.ts +28 -0
  247. package/src/rules/catalog.ts +274 -4
  248. package/src/rules/course-rules.ts +21 -1
  249. package/src/rules/in-effect.ts +58 -11
  250. package/src/session/PreferenceStore.ts +23 -0
  251. package/src/session/deck-memory.ts +263 -0
  252. package/src/session/reading-memory.ts +243 -0
  253. package/src/styles/deck.css +1150 -0
  254. package/src/styles/global.css +958 -27
  255. package/src/styles/zones.css +277 -0
  256. /package/src/{components → reference}/float-name.ts +0 -0
@@ -1,7 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
+ import { newestSince, versionPhrase } from "../../page-versions.js";
3
4
  import {
4
5
  escapeText,
6
+ escapeUrl,
7
+ labelOf,
5
8
  serializeAbbreviationAppendix,
6
9
  serializeChapter,
7
10
  serializeNotationAppendix,
@@ -10,6 +13,9 @@ import {
10
13
  function explicoClass() {
11
14
  return readFileSync(new URL("./explico.cls", import.meta.url), "utf8");
12
15
  }
16
+ function explicoCommon() {
17
+ return readFileSync(new URL("./explico-common.sty", import.meta.url), "utf8");
18
+ }
13
19
  function twoDigits(value) {
14
20
  return String(value).padStart(2, "0");
15
21
  }
@@ -32,6 +38,16 @@ function checkItems(manifest, lesson) {
32
38
  }
33
39
  return items;
34
40
  }
41
+ function deckCheckItems(manifest, deck) {
42
+ const lessonIds = deck.lessonId !== void 0 ? [deck.lessonId] : deck.covers.map((cover) => cover.chapter.replace(/\//g, "."));
43
+ const items = /* @__PURE__ */ new Map();
44
+ for (const id of lessonIds) {
45
+ for (const [assessment, list] of checkItems(manifest, { id })) {
46
+ if (!items.has(assessment)) items.set(assessment, list);
47
+ }
48
+ }
49
+ return items;
50
+ }
35
51
  const DOCUMENT_METADATA = [
36
52
  "% A fixed seed for TeX's random numbers, before tagging loads: tagpdf names",
37
53
  "% one of its structure namespaces with random digits, and the same source",
@@ -45,11 +61,19 @@ const DOCUMENT_METADATA = [
45
61
  " \\DocumentMetadata{lang=en,pdfversion=2.0,tagging=on}",
46
62
  "\\fi"
47
63
  ];
48
- function trailerId(manifest, document) {
64
+ function trailerId(manifest, document, since) {
49
65
  return createHash("md5").update(`${manifest.about.material.name}
50
- ${manifest.about.knowledge.version}
66
+ ${versionPhrase(since, manifest.about.knowledge.version)}
51
67
  ${document}`).digest("hex").toUpperCase();
52
68
  }
69
+ function bookSince(parts) {
70
+ return newestSince(
71
+ parts.flatMap(({ part, chapters }) => [
72
+ ...part.overview ? [part.overview.since] : [],
73
+ ...chapters.map(({ lesson }) => lesson.since)
74
+ ])
75
+ );
76
+ }
53
77
  function trailerIdLine(id) {
54
78
  return `\\ifdefined\\directlua\\pdfvariable trailerid {[<${id}><${id}>]}\\else\\pdftrailerid{${id}}\\fi`;
55
79
  }
@@ -64,7 +88,7 @@ function mainTex(manifest, lesson, chapterFile) {
64
88
  ...DOCUMENT_METADATA,
65
89
  `\\documentclass[${paper}]{explico}`,
66
90
  "% The same identifier for every compile of this source.",
67
- trailerIdLine(trailerId(manifest, lesson.slug)),
91
+ trailerIdLine(trailerId(manifest, lesson.slug, lesson.since)),
68
92
  ...numbering.floats === "part" ? ["\\ExplicoFloatsByPart"] : [],
69
93
  `\\ExplicoSectionDepth{${numbering.sections.show ? numbering.sections.depth : 1}}`,
70
94
  `\\hypersetup{pdftitle={${escapeText(lesson.title)}},pdfauthor={${escapeText(
@@ -91,7 +115,7 @@ function readme(manifest, lesson, url) {
91
115
  return [
92
116
  `# ${lesson.title}`,
93
117
  "",
94
- `The LaTeX source of a chapter of *${course}* (content version ${manifest.about.knowledge.version}).` + (url ? ` Interactive version: ${url}` : ""),
118
+ `The LaTeX source of a chapter of *${course}* (${versionPhrase(lesson.since, manifest.about.knowledge.version)}).` + (url ? ` Interactive version: ${url}` : ""),
95
119
  "",
96
120
  "## Build",
97
121
  "",
@@ -110,6 +134,8 @@ function readme(manifest, lesson, url) {
110
134
  "- `main.tex` \u2014 the document: class options, then the chapter.",
111
135
  "- `explico.cls` \u2014 explico's book class: layout, numbering, and the",
112
136
  " environments the chapter uses (memos, notes, worked examples, checks).",
137
+ "- `explico-common.sty` \u2014 what the class shares with explico's slides: fonts,",
138
+ " colours, figures, diagrams and the notation markers.",
113
139
  `- \`${chapterPath(manifest, lesson)}\` \u2014 the chapter.`,
114
140
  "- `figures/` \u2014 the chapter's figures, as vector PDF where the website draws them.",
115
141
  "",
@@ -131,7 +157,7 @@ function chapterSource(input) {
131
157
  checkItems: checkItems(manifest, lesson),
132
158
  source: {
133
159
  course: manifest.about.material.name,
134
- version: manifest.about.knowledge.version,
160
+ since: versionPhrase(lesson.since, manifest.about.knowledge.version),
135
161
  ...url ? { url } : {}
136
162
  }
137
163
  });
@@ -140,21 +166,36 @@ function chapterSource(input) {
140
166
  { path: "latexmkrc", content: LATEXMKRC },
141
167
  { path: "main.tex", content: mainTex(manifest, lesson, file) },
142
168
  { path: "explico.cls", content: explicoClass() },
169
+ { path: "explico-common.sty", content: explicoCommon() },
143
170
  { path: file, content: chapter },
144
171
  ...[...input.pictures.values()].map((picture) => ({ path: picture.path, content: picture.content }))
145
172
  ]);
146
173
  }
147
174
  const OTHER_SCRIPTS = /[\u0370-\u03FF\u0400-\u04FF\u1F00-\u1FFF\u2E80-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\uFF00-\uFFEF\u2600-\u27BF\u{1F000}-\u{1FAFF}]/u;
148
175
  const MATH_GREEK = /[αβγδεθλμπρστφωΓΔΘΛΠΣΦΩ]/gu;
176
+ function needsScripts(tex) {
177
+ return tex.includes("\\ExplicoGreekWord{") || OTHER_SCRIPTS.test(tex.replace(MATH_GREEK, ""));
178
+ }
149
179
  function withScripts(files) {
150
180
  const needed = files.some(
151
- (file) => file.path.endsWith(".tex") && typeof file.content === "string" && (file.content.includes("\\ExplicoGreekWord{") || OTHER_SCRIPTS.test(file.content.replace(MATH_GREEK, "")))
181
+ (file) => file.path.endsWith(".tex") && typeof file.content === "string" && needsScripts(file.content)
152
182
  );
153
183
  if (!needed) return files;
154
184
  return files.map(
155
185
  (file) => file.path === "main.tex" && typeof file.content === "string" ? { ...file, content: file.content.replace(/\\documentclass\[([^\]]*)\]\{explico\}/u, "\\documentclass[$1,scripts]{explico}") } : file
156
186
  );
157
187
  }
188
+ function slidesChapter(entry) {
189
+ const checks = entry.lesson.assessments.length;
190
+ return [
191
+ `\\chapter{${escapeText(entry.lesson.title)}}\\label{${labelOf(entry.lesson.slug, "_top")}}`,
192
+ `This lesson is taught as slides${entry.slidesUrl ? `: \\url{${escapeUrl(entry.slidesUrl)}}` : ""}.` + (checks > 0 ? ` Its knowledge check${checks === 1 ? " is" : "s are"} on its slides.` : ""),
193
+ ...entry.outcomes.length > 0 ? ["", "After it, you can:", "\\begin{itemize}", ...entry.outcomes.map((outcome) => `\\item ${escapeText(outcome)}`), "\\end{itemize}"] : [],
194
+ // Its checks keep their numbers, so the chapters after it keep theirs.
195
+ ...checks > 0 ? [`\\addtocounter{explicocheck}{${checks}}`] : [],
196
+ ""
197
+ ].join("\n");
198
+ }
158
199
  function countNumbered(parts, kind) {
159
200
  let count = 0;
160
201
  const visit = (blocks) => {
@@ -210,7 +251,7 @@ function bookMainTex(input, inputs, appendices) {
210
251
  ...DOCUMENT_METADATA,
211
252
  `\\documentclass[${paper}]{explico}`,
212
253
  "% The same identifier for every compile of this source.",
213
- trailerIdLine(trailerId(manifest, input.document)),
254
+ trailerIdLine(trailerId(manifest, input.document, bookSince(input.parts))),
214
255
  ...numbering.floats === "part" ? ["\\ExplicoFloatsByPart"] : [],
215
256
  `\\ExplicoSectionDepth{${numbering.sections.show ? numbering.sections.depth : 1}}`,
216
257
  "% The contents list parts, chapters and sections.",
@@ -221,7 +262,7 @@ function bookMainTex(input, inputs, appendices) {
221
262
  "\\begin{document}",
222
263
  "\\frontmatter",
223
264
  `\\ExplicoTitlePage{${escapeText(input.title)}}{${escapeText(input.subtitle ?? "")}}{${escapeText(
224
- manifest.about.knowledge.version
265
+ versionPhrase(bookSince(input.parts), manifest.about.knowledge.version)
225
266
  )}}{${escapeText(authors.join(", "))}}{${url ? url.replace(/[\\{}]/gu, "").replace(/[%#]/gu, (char) => `\\${char}`) : ""}}`,
226
267
  "% The contents and lists in black, as a book sets them; links in the text keep their colour.",
227
268
  "\\begingroup\\hypersetup{linkcolor=black}",
@@ -241,7 +282,7 @@ function bookReadme(input) {
241
282
  return [
242
283
  `# ${input.title}`,
243
284
  "",
244
- `The LaTeX source of ${input.subtitle ? `${input.subtitle.replace(/^Part/u, "part")}` : `*${manifest.about.material.name}*`} (content version ${manifest.about.knowledge.version}).` + (url ? ` Interactive version: ${url}` : ""),
285
+ `The LaTeX source of ${input.subtitle ? `${input.subtitle.replace(/^Part/u, "part")}` : `*${manifest.about.material.name}*`} (${versionPhrase(bookSince(input.parts), manifest.about.knowledge.version)}).` + (url ? ` Interactive version: ${url}` : ""),
245
286
  "",
246
287
  "## Build",
247
288
  "",
@@ -261,6 +302,8 @@ function bookReadme(input) {
261
302
  " diagrams, then each part and its chapters, then the appendices.",
262
303
  "- `explico.cls` \u2014 explico's book class: layout, numbering, and the",
263
304
  " environments the chapters use (memos, notes, worked examples, checks).",
305
+ "- `explico-common.sty` \u2014 what the class shares with explico's slides: fonts,",
306
+ " colours, figures, diagrams and the notation markers.",
264
307
  "- `parts/NN-<part>.tex` \u2014 a part's opening page and text;",
265
308
  " `parts/NN-<part>/MM-<chapter>.tex` \u2014 its chapters.",
266
309
  "- `backmatter/` \u2014 the notation and abbreviation appendices.",
@@ -275,7 +318,6 @@ function bookSource(input) {
275
318
  const { manifest, parts } = input;
276
319
  const canonicalBaseUrl = manifest.lessonExports.canonicalBaseUrl;
277
320
  const course = manifest.about.material.name;
278
- const version = manifest.about.knowledge.version;
279
321
  const documentRoutes = new Set(
280
322
  parts.flatMap(({ overview, chapters }) => [
281
323
  ...overview ? [overview.route] : [],
@@ -286,7 +328,7 @@ function bookSource(input) {
286
328
  const common = { documentRoutes, canonicalBaseUrl, inBook: true, notationTargets };
287
329
  const files = [];
288
330
  const inputs = [];
289
- for (const { part, overview, chapters } of parts) {
331
+ for (const { part, overview, chapters, slides = [] } of parts) {
290
332
  const partFile = `parts/${twoDigits(part.ordinal)}-${part.dir}.tex`;
291
333
  files.push({
292
334
  path: partFile,
@@ -294,7 +336,17 @@ function bookSource(input) {
294
336
  `
295
337
  });
296
338
  inputs.push(partFile);
339
+ const pending = [...slides].sort((a, b) => a.lesson.chapter - b.lesson.chapter);
340
+ const writeSlides = (before) => {
341
+ while (pending.length > 0 && pending[0].lesson.chapter < before) {
342
+ const entry = pending.shift();
343
+ const file = chapterPath(manifest, entry.lesson);
344
+ files.push({ path: file, content: slidesChapter(entry) });
345
+ inputs.push(file);
346
+ }
347
+ };
297
348
  for (const { lesson, page, pictures } of chapters) {
349
+ writeSlides(lesson.chapter);
298
350
  const file = chapterPath(manifest, lesson);
299
351
  const url = canonicalBaseUrl ? `${canonicalBaseUrl}${lesson.slug}/` : void 0;
300
352
  files.push({
@@ -304,12 +356,14 @@ function bookSource(input) {
304
356
  page,
305
357
  pictures: new Map([...pictures].map(([anchor, picture]) => [anchor, picture.path])),
306
358
  checkItems: checkItems(manifest, lesson),
307
- source: { course, version, ...url ? { url } : {} }
359
+ // In a book the line is the URL alone; the title page carries the version.
360
+ source: { course, ...url ? { url } : {} }
308
361
  })
309
362
  });
310
363
  inputs.push(file);
311
364
  files.push(...[...pictures.values()].map((picture) => ({ path: picture.path, content: picture.content })));
312
365
  }
366
+ writeSlides(Number.POSITIVE_INFINITY);
313
367
  }
314
368
  const appendices = [];
315
369
  const notation = serializeNotationAppendix(appendixNotation(manifest, parts), {
@@ -335,15 +389,20 @@ function bookSource(input) {
335
389
  { path: "latexmkrc", content: LATEXMKRC },
336
390
  { path: "main.tex", content: bookMainTex(input, inputs, appendices) },
337
391
  { path: "explico.cls", content: explicoClass() },
392
+ { path: "explico-common.sty", content: explicoCommon() },
338
393
  ...files
339
394
  ]);
340
395
  }
341
396
  export {
397
+ bookSince,
342
398
  bookSource,
343
399
  chapterFolderName,
344
400
  chapterPath,
345
401
  chapterSource,
346
402
  checkItems,
403
+ deckCheckItems,
347
404
  explicoClass,
405
+ explicoCommon,
406
+ needsScripts,
348
407
  trailerId
349
408
  };
@@ -0,0 +1,198 @@
1
+ %% explico-common.sty — what every LaTeX document explico writes shares: its
2
+ %% book class (`explico.cls`) and its deck theme (`beamerthemeexplico.sty`).
3
+ %%
4
+ %% The engine, the fonts and the Unicode characters, the colours, the
5
+ %% figures and tables, the TikZ styles of a diagram, the box style, the
6
+ %% notation markers (\explain, \slot) and the cross-references. Edit it
7
+ %% freely in a download.
8
+ \NeedsTeXFormat{LaTeX2e}
9
+ \ProvidesPackage{explico-common}[2026/10/10 explico shared definitions]
10
+
11
+ %% `scripts`: the document holds Greek, Cyrillic, CJK or emoji (the exporter
12
+ %% sets it when it does), so LuaLaTeX loads the fallback fonts below.
13
+ \newif\ifexplico@scripts
14
+ \DeclareOption{scripts}{\explico@scriptstrue}
15
+ \ProcessOptions\relax
16
+
17
+ %% --- Engine and fonts ------------------------------------------------------
18
+ \RequirePackage{iftex}
19
+ \RequirePackage{amsmath,amssymb,mathtools}
20
+ %% Rule N17's engine half, as on the website (src/reference/katex-delimiters.mjs):
21
+ %% a negative \delimitershortfall makes every \left...\right pair strictly taller
22
+ %% than its content, so a nest grows outward at each level. TeX's default (5pt)
23
+ %% lets nested pairs round to the same glyph.
24
+ \delimitershortfall=-1pt
25
+ %% Whether a text font sets Greek; if none does, a Greek letter in prose is
26
+ %% set as its math symbol (below, with the other Unicode characters).
27
+ \newif\ifexplico@greek
28
+ \ifPDFTeX
29
+ \RequirePackage[T1]{fontenc}
30
+ \RequirePackage{lmodern}
31
+ \RequirePackage{textcomp}
32
+ \RequirePackage{bm}
33
+ %% A character no pdfTeX font sets (accented Greek, Chinese, an emoji) stays
34
+ %% blank instead of stopping the compile, and the log says so in LuaLaTeX's
35
+ %% words, which the export turns into its warning about blank characters.
36
+ \def\UTFviii@undefined@err#1{%
37
+ \typeout{Missing character: There is no
38
+ \expandafter\UTFviii@splitcsname\string#1\relax\space in font pdftex!}}
39
+ \else
40
+ %% Latin Modern for text and math. unicode-math is also what lets a tagged
41
+ %% PDF carry MathML for every formula (luamml needs it); bm does not work
42
+ %% with it, so \bm becomes \boldsymbol.
43
+ \RequirePackage{unicode-math}
44
+ \providecommand{\bm}{\boldsymbol}
45
+ %% Latin Modern has no Greek, Cyrillic, Chinese, Japanese, Korean or emoji.
46
+ %% With `scripts`, prose in those scripts comes from fallback fonts: for each
47
+ %% script, the first of the fonts below this machine has (CMU Serif, from
48
+ %% TeX Live's cm-unicode, matches Latin Modern). A script none of them
49
+ %% covers stays blank, and the export warns about it.
50
+ \ifexplico@scripts
51
+ \def\explico@fallbacks{}
52
+ \def\explico@fallback#1#2{%
53
+ \let\explico@found\@empty
54
+ \@for\explico@candidate:=#1\do{%
55
+ \ifx\explico@found\@empty
56
+ \IfFontExistsTF{\explico@candidate}{%
57
+ \edef\explico@fallbacks{\explico@fallbacks"\explico@candidate:mode=harf;",}%
58
+ \let\explico@found\explico@candidate}{}%
59
+ \fi}%
60
+ \ifx\explico@found\@empty\else#2\fi}
61
+ \explico@fallback{CMU Serif,Noto Serif,DejaVu Serif,FreeSerif}{\explico@greektrue}
62
+ \explico@fallback{Noto Serif CJK SC,Noto Sans CJK SC,FandolSong,Source Han Serif SC}{}
63
+ \explico@fallback{Noto Color Emoji,Twemoji Mozilla,Symbola}{}
64
+ \ifx\explico@fallbacks\@empty\else
65
+ \directlua{luaotfload.add_fallback("explico", {\explico@fallbacks})}%
66
+ \setmainfont{Latin Modern Roman}[RawFeature={fallback=explico}]%
67
+ \fi
68
+ \fi
69
+ \fi
70
+ \RequirePackage{microtype}
71
+
72
+ %% --- Page and colour --------------------------------------------------------
73
+ \RequirePackage{xcolor}
74
+ \definecolor{explicoaccent}{HTML}{12775C}
75
+ \definecolor{explicoframe}{HTML}{9DB8AD}
76
+ \definecolor{explicoback}{HTML}{F3F8F6}
77
+ \definecolor{explicoasidecaution}{HTML}{B5651D}
78
+ \definecolor{explicoasidecautionback}{HTML}{FBF3EA}
79
+ \definecolor{explicorule}{HTML}{8A9A93}
80
+
81
+ %% --- Floats, tables and captions -------------------------------------------
82
+ \RequirePackage{graphicx}
83
+ \RequirePackage{array,booktabs,tabularx}
84
+ \RequirePackage[export]{adjustbox}
85
+ %% explico's short caption, run in bold before the long one.
86
+ \newcommand*{\ExplicoCaptionTitle}[1]{\textbf{#1}}
87
+
88
+ %% --- Diagrams ---------------------------------------------------------------
89
+ \RequirePackage{tikz}
90
+ \usetikzlibrary{arrows.meta}
91
+ \tikzset{
92
+ explico diagram node/.style={
93
+ draw=explicorule, fill=explicoback, rounded corners=3pt, line width=0.5pt,
94
+ align=center, inner sep=3pt, font=\small,
95
+ },
96
+ explico diagram edge/.style={
97
+ draw=explicorule, line width=0.6pt, -{Stealth[length=5pt,width=4pt]},
98
+ },
99
+ }
100
+
101
+ %% --- Boxes: memos, notes, asides, worked examples, checks ------------------
102
+ \RequirePackage[most]{tcolorbox}
103
+ \tcbset{
104
+ explico/.style={
105
+ enhanced, breakable, colback=explicoback, colframe=explicoframe,
106
+ boxrule=0.5pt, arc=2pt, left=6pt, right=6pt, top=4pt, bottom=4pt,
107
+ fonttitle=\bfseries\small, coltitle=black, colbacktitle=explicoback!60!white,
108
+ titlerule=0.4pt, before skip=10pt plus 2pt, after skip=10pt plus 2pt,
109
+ },
110
+ }
111
+
112
+ %% --- Unicode in text that the default fonts lack ---------------------------
113
+ \RequirePackage{newunicodechar}
114
+ \newunicodechar{°}{\ifmmode^\circ\else\textdegree\fi}
115
+ \newunicodechar{−}{\ensuremath{-}}
116
+ \newunicodechar{×}{\ensuremath{\times}}
117
+ \newunicodechar{±}{\ensuremath{\pm}}
118
+ \newunicodechar{≤}{\ensuremath{\le}}
119
+ \newunicodechar{≥}{\ensuremath{\ge}}
120
+ \newunicodechar{≈}{\ensuremath{\approx}}
121
+ \newunicodechar{≠}{\ensuremath{\neq}}
122
+ \newunicodechar{→}{\ensuremath{\rightarrow}}
123
+ \newunicodechar{←}{\ensuremath{\leftarrow}}
124
+ \newunicodechar{↑}{\ensuremath{\uparrow}}
125
+ \newunicodechar{↓}{\ensuremath{\downarrow}}
126
+ \newunicodechar{⟨}{\ensuremath{\langle}}
127
+ \newunicodechar{⟩}{\ensuremath{\rangle}}
128
+ \newunicodechar{⋮}{\ensuremath{\vdots}}
129
+ \newunicodechar{√}{\ensuremath{\surd}}
130
+ \newunicodechar{∞}{\ensuremath{\infty}}
131
+ \newunicodechar{′}{\ensuremath{'}}
132
+ \newunicodechar{𝒩}{\ensuremath{\mathcal{N}}}
133
+ \newunicodechar{①}{\textcircled{\scriptsize 1}}
134
+ \newunicodechar{②}{\textcircled{\scriptsize 2}}
135
+ \newunicodechar{③}{\textcircled{\scriptsize 3}}
136
+ \newunicodechar{④}{\textcircled{\scriptsize 4}}
137
+ \newunicodechar{⑤}{\textcircled{\scriptsize 5}}
138
+ \newunicodechar{₀}{\textsubscript{0}}
139
+ \newunicodechar{₁}{\textsubscript{1}}
140
+ \newunicodechar{₂}{\textsubscript{2}}
141
+ \newunicodechar{₃}{\textsubscript{3}}
142
+ \newunicodechar{¹}{\textsuperscript{1}}
143
+ \newunicodechar{²}{\textsuperscript{2}}
144
+ \newunicodechar{³}{\textsuperscript{3}}
145
+ %% A Greek letter on its own is a symbol (the polar coordinates ρ and φ in a
146
+ %% caption, which holds no math): it is set as its math symbol. Inside a Greek
147
+ %% word, which the exporter marks \ExplicoGreekWord{…}, it is a letter, set
148
+ %% from the text font's Greek where one is loaded (`scripts`).
149
+ \newif\ifexplico@word
150
+ \DeclareRobustCommand{\ExplicoGreekWord}[1]{{\explico@wordtrue#1}}
151
+ %% Protected, as \ensuremath is: the text of an equation is expanded for its
152
+ %% tagging (alt text, MathML), and an expandable body breaks it there.
153
+ \protected\def\explico@greekletter#1#2{%
154
+ \ifmmode#2\else
155
+ \ifexplico@word
156
+ \ifexplico@greek\char#1\relax\else\ensuremath{#2}\fi
157
+ \else\ensuremath{#2}\fi
158
+ \fi}
159
+ \newunicodechar{α}{\explico@greekletter{"03B1}{\alpha}}
160
+ \newunicodechar{β}{\explico@greekletter{"03B2}{\beta}}
161
+ \newunicodechar{γ}{\explico@greekletter{"03B3}{\gamma}}
162
+ \newunicodechar{δ}{\explico@greekletter{"03B4}{\delta}}
163
+ \newunicodechar{ε}{\explico@greekletter{"03B5}{\varepsilon}}
164
+ \newunicodechar{θ}{\explico@greekletter{"03B8}{\theta}}
165
+ \newunicodechar{λ}{\explico@greekletter{"03BB}{\lambda}}
166
+ \newunicodechar{μ}{\explico@greekletter{"03BC}{\mu}}
167
+ \newunicodechar{π}{\explico@greekletter{"03C0}{\pi}}
168
+ \newunicodechar{ρ}{\explico@greekletter{"03C1}{\rho}}
169
+ \newunicodechar{σ}{\explico@greekletter{"03C3}{\sigma}}
170
+ \newunicodechar{τ}{\explico@greekletter{"03C4}{\tau}}
171
+ \newunicodechar{φ}{\explico@greekletter{"03C6}{\varphi}}
172
+ \newunicodechar{ω}{\explico@greekletter{"03C9}{\omega}}
173
+ \newunicodechar{Γ}{\explico@greekletter{"0393}{\Gamma}}
174
+ \newunicodechar{Δ}{\explico@greekletter{"0394}{\Delta}}
175
+ \newunicodechar{Θ}{\explico@greekletter{"0398}{\Theta}}
176
+ \newunicodechar{Λ}{\explico@greekletter{"039B}{\Lambda}}
177
+ \newunicodechar{Π}{\explico@greekletter{"03A0}{\Pi}}
178
+ \newunicodechar{Σ}{\explico@greekletter{"03A3}{\Sigma}}
179
+ \newunicodechar{Φ}{\explico@greekletter{"03A6}{\Phi}}
180
+ \newunicodechar{Ω}{\explico@greekletter{"03A9}{\Omega}}
181
+
182
+ %% --- Notation ---------------------------------------------------------------
183
+ %% \explain{key}{tex} and \slot{key}{tex} are explico's markers in math: the
184
+ %% website opens a card for the key; on paper they print their content.
185
+ \providecommand{\explain}[2]{#2}
186
+ \providecommand{\slot}[2]{#2}
187
+ %% \ExplicoNotation{target}{tex}: a formula's symbol that links to its notation entry
188
+ %% (the serializer writes it only where the book has that entry).
189
+ \providecommand{\ExplicoNotation}[2]{\hyperlink{#1}{#2}}
190
+ %% A term in prose with a notation card on the website.
191
+ \newcommand{\ExplicoTerm}[2]{#2}
192
+
193
+ %% --- Cross-references ---------------------------------------------------------
194
+ %% #1 label, #2 text before the number, #3 text after it.
195
+ \newcommand{\ExplicoRef}[3]{\hyperref[#1]{#2\ref*{#1}#3}}
196
+ \newcommand{\ExplicoEqRef}[1]{\hyperref[#1]{(\ref*{#1})}}
197
+
198
+ \endinput