explico 0.11.1 → 0.13.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 (298) hide show
  1. package/CHANGELOG.md +1292 -1
  2. package/README.md +91 -13
  3. package/dist/compiler/astro.d.ts +11 -4
  4. package/dist/compiler/astro.js +48 -15
  5. package/dist/compiler/cli.d.ts +27 -12
  6. package/dist/compiler/cli.js +96 -25
  7. package/dist/compiler/collections.d.ts +33 -2
  8. package/dist/compiler/collections.js +91 -19
  9. package/dist/compiler/content-schema.d.ts +31 -0
  10. package/dist/compiler/content-schema.js +82 -0
  11. package/dist/compiler/content-schemas.d.ts +422 -0
  12. package/dist/compiler/content-schemas.js +229 -0
  13. package/dist/compiler/course-config.d.ts +1 -1
  14. package/dist/compiler/course-config.js +30 -2
  15. package/dist/compiler/exports/combined-html.d.ts +97 -0
  16. package/dist/compiler/exports/combined-html.js +492 -0
  17. package/dist/compiler/exports/download-index.d.ts +9 -0
  18. package/dist/compiler/exports/download-index.js +33 -0
  19. package/dist/compiler/exports/export-chapters.d.ts +98 -0
  20. package/dist/compiler/exports/export-chapters.js +164 -0
  21. package/dist/compiler/exports/export-pages.d.ts +48 -0
  22. package/dist/compiler/exports/export-pages.js +22 -0
  23. package/dist/compiler/exports/html-tree.d.ts +22 -0
  24. package/dist/compiler/exports/html-tree.js +47 -0
  25. package/dist/compiler/exports/latex/capture.d.ts +106 -0
  26. package/dist/compiler/exports/latex/capture.js +435 -0
  27. package/dist/compiler/exports/latex/compile.d.ts +73 -0
  28. package/dist/compiler/exports/latex/compile.js +115 -0
  29. package/dist/compiler/exports/latex/document.d.ts +102 -0
  30. package/dist/compiler/exports/latex/document.js +349 -0
  31. package/dist/compiler/exports/latex/explico.cls +415 -0
  32. package/dist/compiler/exports/latex/numbering.d.ts +18 -0
  33. package/dist/compiler/exports/latex/numbering.js +57 -0
  34. package/dist/compiler/exports/latex/serialize.d.ts +112 -0
  35. package/dist/compiler/exports/latex/serialize.js +606 -0
  36. package/dist/compiler/exports/latex-export.d.ts +44 -0
  37. package/dist/compiler/exports/latex-export.js +169 -0
  38. package/dist/compiler/exports/markdown/serialize.d.ts +51 -0
  39. package/dist/compiler/exports/markdown/serialize.js +527 -0
  40. package/dist/compiler/exports/markdown-export.d.ts +39 -0
  41. package/dist/compiler/exports/markdown-export.js +240 -0
  42. package/dist/compiler/exports/standalone-html.d.ts +15 -5
  43. package/dist/compiler/exports/standalone-html.js +23 -51
  44. package/dist/compiler/exports/static-tree.d.ts +253 -0
  45. package/dist/compiler/exports/static-tree.js +625 -0
  46. package/dist/compiler/exports/zip.d.ts +16 -0
  47. package/dist/compiler/exports/zip.js +72 -0
  48. package/dist/compiler/lesson-derivation.d.ts +8 -2
  49. package/dist/compiler/lesson-derivation.js +6 -7
  50. package/dist/compiler/manifest.d.ts +115 -8
  51. package/dist/compiler/manifest.js +221 -39
  52. package/dist/compiler/render-diagnostics.d.ts +65 -0
  53. package/dist/compiler/render-diagnostics.js +56 -0
  54. package/dist/compiler/resolution-report.d.ts +3 -1
  55. package/dist/{course-config-Dsnri9Tk.d.ts → course-config-rRtME5UV.d.ts} +56 -13
  56. package/dist/curriculum/validation.d.ts +9 -3
  57. package/dist/curriculum/validation.js +35 -17
  58. package/dist/reference/base-abbreviations.d.mts +2 -2
  59. package/dist/reference/base-abbreviations.mjs +2 -2
  60. package/dist/reference/base-operators.d.mts +8 -2
  61. package/dist/reference/base-operators.mjs +8 -10
  62. package/dist/reference/base-roman-labels.d.mts +4 -3
  63. package/dist/reference/base-roman-labels.mjs +4 -3
  64. package/dist/reference/citation-format.d.mts +35 -0
  65. package/dist/reference/citation-format.mjs +74 -0
  66. package/dist/reference/consistency.d.ts +5 -9
  67. package/dist/reference/consistency.js +34 -38
  68. package/dist/reference/curriculum-alignment.js +3 -24
  69. package/dist/reference/derived-entries.d.ts +45 -0
  70. package/dist/reference/derived-entries.js +50 -0
  71. package/dist/reference/derived-notation.d.mts +125 -0
  72. package/dist/reference/derived-notation.mjs +121 -0
  73. package/dist/reference/diagram-fit.d.ts +45 -0
  74. package/dist/reference/diagram-fit.js +54 -0
  75. package/dist/reference/diagram-layout.d.ts +59 -10
  76. package/dist/reference/diagram-layout.js +85 -24
  77. package/dist/reference/diagrams-validate.d.ts +1 -1
  78. package/dist/reference/equations-validate.d.ts +1 -1
  79. package/dist/reference/equations-validate.js +26 -1
  80. package/dist/reference/equations.d.mts +27 -0
  81. package/dist/reference/equations.mjs +120 -22
  82. package/dist/reference/examples-validate.d.ts +1 -1
  83. package/dist/reference/figure-fit.d.ts +70 -0
  84. package/dist/reference/figure-fit.js +60 -0
  85. package/dist/reference/figure-pictures.d.ts +27 -0
  86. package/dist/reference/figure-pictures.js +49 -0
  87. package/dist/reference/figure-symbols.d.ts +32 -0
  88. package/dist/reference/figure-symbols.js +55 -0
  89. package/dist/reference/figures-validate.d.ts +1 -1
  90. package/dist/reference/gate-abbreviations.d.ts +1 -1
  91. package/dist/reference/gate-abbreviations.js +1 -1
  92. package/dist/reference/gate-course-rules.d.ts +4 -3
  93. package/dist/reference/gate-course-rules.js +9 -4
  94. package/dist/reference/gate-heading-length.d.ts +2 -0
  95. package/dist/reference/gate-heading-length.js +7 -5
  96. package/dist/reference/gate-heading-structure.js +26 -0
  97. package/dist/reference/gate-indices.d.ts +35 -0
  98. package/dist/reference/gate-indices.js +299 -0
  99. package/dist/reference/gate-math.d.ts +32 -2
  100. package/dist/reference/gate-math.js +68 -12
  101. package/dist/reference/gate-mdx-expression.d.ts +17 -0
  102. package/dist/reference/gate-mdx-expression.js +29 -0
  103. package/dist/reference/gate-non-chapter-pages.d.ts +39 -0
  104. package/dist/reference/gate-non-chapter-pages.js +156 -0
  105. package/dist/reference/gate-part-pages.d.ts +4 -2
  106. package/dist/reference/gate-part-pages.js +2 -1
  107. package/dist/reference/gate-prop-text.d.ts +28 -0
  108. package/dist/reference/gate-prop-text.js +106 -0
  109. package/dist/reference/gate-prose-notation.d.ts +3 -1
  110. package/dist/reference/gate-prose-notation.js +4 -2
  111. package/dist/reference/gate-retired-keys.d.ts +35 -0
  112. package/dist/reference/gate-retired-keys.js +58 -0
  113. package/dist/reference/gloss.d.ts +2 -0
  114. package/dist/reference/katex-measure.d.ts +17 -0
  115. package/dist/reference/katex-measure.js +223 -0
  116. package/dist/reference/keyed-float-validate.js +12 -0
  117. package/dist/reference/keyed-jsx-floats.d.mts +2 -2
  118. package/dist/reference/keyed-jsx-floats.mjs +11 -14
  119. package/dist/reference/lab-katex-validate.d.ts +43 -0
  120. package/dist/reference/lab-katex-validate.js +96 -0
  121. package/dist/reference/lab-math-scope.d.ts +1 -0
  122. package/dist/reference/lab-math-scope.js +13 -2
  123. package/dist/reference/lint-ignore.js +1 -1
  124. package/dist/reference/math-glyphs.d.mts +94 -2
  125. package/dist/reference/math-glyphs.mjs +851 -46
  126. package/dist/reference/mixed-content.d.ts +8 -3
  127. package/dist/reference/mixed-content.js +4 -2
  128. package/dist/reference/page-notation.d.ts +39 -7
  129. package/dist/reference/page-notation.js +62 -10
  130. package/dist/reference/raw-floats.mjs +37 -13
  131. package/dist/reference/references.d.ts +11 -1
  132. package/dist/reference/references.js +6 -0
  133. package/dist/reference/registry.js +115 -11
  134. package/dist/reference/rehype-fail-katex-errors.d.mts +9 -0
  135. package/dist/reference/rehype-fail-katex-errors.mjs +19 -0
  136. package/dist/reference/rehype-heading-anchors.mjs +11 -1
  137. package/dist/reference/remark-citation.mjs +0 -0
  138. package/dist/reference/remark-notation.mjs +275 -23
  139. package/dist/reference/render-lab-math.d.ts +13 -2
  140. package/dist/reference/render-lab-math.js +18 -6
  141. package/dist/reference/sections-validate.d.ts +28 -3
  142. package/dist/reference/sections-validate.js +31 -4
  143. package/dist/reference/sections.d.mts +24 -6
  144. package/dist/reference/sections.mjs +92 -17
  145. package/dist/reference/tables-validate.d.ts +1 -1
  146. package/dist/reference/tables-validate.js +24 -1
  147. package/dist/reference/text-measure.d.ts +10 -0
  148. package/dist/reference/text-measure.js +41 -0
  149. package/dist/reference/text-widths.d.ts +10 -0
  150. package/dist/reference/text-widths.js +1238 -0
  151. package/dist/reference/types.d.ts +64 -8
  152. package/dist/rules/catalog.js +223 -32
  153. package/dist/rules/in-effect.d.ts +1 -1
  154. package/dist/rules/rules.schema.json +100 -27
  155. package/dist/rules/settings.d.ts +1 -1
  156. package/dist/rules/settings.js +7 -0
  157. package/dist/session/PreferenceStore.d.ts +18 -1
  158. package/dist/session/PreferenceStore.js +29 -1
  159. package/docs/authoring/README.md +33 -21
  160. package/docs/authoring/agents.md +38 -7
  161. package/docs/authoring/components.md +115 -10
  162. package/docs/authoring/migrating.md +878 -0
  163. package/docs/authoring/notation.md +285 -30
  164. package/docs/authoring/retired.md +1 -0
  165. package/docs/authoring/structure-rules.md +84 -45
  166. package/docs/authoring/structure.md +67 -2
  167. package/docs/authoring/visualizations.md +167 -25
  168. package/package.json +12 -7
  169. package/src/compiler/astro.ts +72 -21
  170. package/src/compiler/cli.ts +143 -35
  171. package/src/compiler/collections.ts +158 -27
  172. package/src/compiler/content-schema.ts +108 -0
  173. package/src/compiler/content-schemas.ts +391 -0
  174. package/src/compiler/course-config.ts +86 -15
  175. package/src/compiler/exports/combined-html.ts +669 -0
  176. package/src/compiler/exports/download-index.ts +36 -0
  177. package/src/compiler/exports/export-chapters.ts +233 -0
  178. package/src/compiler/exports/export-pages.ts +44 -0
  179. package/src/compiler/exports/html-tree.ts +62 -0
  180. package/src/compiler/exports/latex/capture.ts +651 -0
  181. package/src/compiler/exports/latex/compile.ts +168 -0
  182. package/src/compiler/exports/latex/document.ts +479 -0
  183. package/src/compiler/exports/latex/explico.cls +415 -0
  184. package/src/compiler/exports/latex/numbering.ts +74 -0
  185. package/src/compiler/exports/latex/serialize.ts +787 -0
  186. package/src/compiler/exports/latex/texlive-packages.txt +76 -0
  187. package/src/compiler/exports/latex-export.ts +255 -0
  188. package/src/compiler/exports/markdown/serialize.ts +632 -0
  189. package/src/compiler/exports/markdown-export.ts +329 -0
  190. package/src/compiler/exports/standalone-html.ts +39 -70
  191. package/src/compiler/exports/static-tree.ts +944 -0
  192. package/src/compiler/exports/zip.ts +88 -0
  193. package/src/compiler/lesson-derivation.ts +13 -8
  194. package/src/compiler/manifest.ts +387 -40
  195. package/src/compiler/render-diagnostics.ts +111 -0
  196. package/src/components/CourseContents.astro +6 -0
  197. package/src/components/HoverPanel.astro +204 -6
  198. package/src/components/PartOverview.astro +18 -9
  199. package/src/components/abbreviation/AbbreviationGlossary.astro +6 -2
  200. package/src/components/abbreviation/AbbreviationLayer.astro +21 -12
  201. package/src/components/assessments/AssessmentRunner.tsx +7 -1
  202. package/src/components/assessments/AssessmentSet.astro +14 -1
  203. package/src/components/block-heading.ts +38 -0
  204. package/src/components/card-arrow.ts +18 -4
  205. package/src/components/card-carry.ts +406 -0
  206. package/src/components/card-drag.ts +1 -30
  207. package/src/components/card-fetch.ts +87 -0
  208. package/src/components/card-manager.ts +1222 -38
  209. package/src/components/card-memory.ts +5 -1
  210. package/src/components/card-placement.ts +211 -13
  211. package/src/components/card-side.ts +281 -31
  212. package/src/components/citation/CitationLayer.astro +45 -40
  213. package/src/components/diagram/Diagram.astro +123 -44
  214. package/src/components/disclosure/print-as-shown.ts +42 -6
  215. package/src/components/figure/Figure.astro +17 -1
  216. package/src/components/figure/FloatCaption.astro +4 -17
  217. package/src/components/figure/figure-state.ts +26 -17
  218. package/src/components/float-toolbar.ts +274 -0
  219. package/src/components/memo/Memo.astro +21 -10
  220. package/src/components/memo/ShortNote.astro +4 -0
  221. package/src/components/notation/NotationGlossary.astro +22 -17
  222. package/src/components/notation/NotationLayer.astro +300 -79
  223. package/src/components/notation/NotationMath.astro +8 -1
  224. package/src/components/notation/render-notation-math.ts +51 -5
  225. package/src/components/readable-text.ts +25 -0
  226. package/src/components/reference/FloatRefLayer.astro +162 -89
  227. package/src/components/starlight/HeadingAnchorEnhancer.astro +13 -8
  228. package/src/components/starlight/KeyedFloatEnhancer.astro +28 -9
  229. package/src/components/starlight/LayoutPageSidebar.astro +17 -3
  230. package/src/components/starlight/LayoutSocialIcons.astro +79 -29
  231. package/src/components/starlight/LessonExportActions.astro +384 -310
  232. package/src/components/starlight/LessonFooter.astro +66 -8
  233. package/src/components/starlight/PrintController.astro +28 -0
  234. package/src/components/starlight/TextSizeControl.astro +137 -0
  235. package/src/components/starlight/print-controller.ts +380 -0
  236. package/src/components/starlight/section-summary.ts +13 -5
  237. package/src/components/table/Table.astro +14 -22
  238. package/src/components/table/table-truncation.ts +15 -0
  239. package/src/content-config.ts +20 -312
  240. package/src/curriculum/validation.ts +72 -24
  241. package/src/reference/base-abbreviations.mjs +2 -2
  242. package/src/reference/base-operators.mjs +8 -10
  243. package/src/reference/base-roman-labels.mjs +4 -3
  244. package/src/reference/citation-format.mjs +74 -0
  245. package/src/reference/consistency.ts +47 -61
  246. package/src/reference/curriculum-alignment.ts +4 -41
  247. package/src/reference/derived-entries.ts +68 -0
  248. package/src/reference/derived-notation.mjs +121 -0
  249. package/src/reference/diagram-fit.ts +104 -0
  250. package/src/reference/diagram-layout.ts +182 -51
  251. package/src/reference/diagrams-validate.ts +2 -1
  252. package/src/reference/equations-validate.ts +41 -2
  253. package/src/reference/equations.mjs +120 -22
  254. package/src/reference/examples-validate.ts +1 -0
  255. package/src/reference/figure-fit.ts +139 -0
  256. package/src/reference/figure-pictures.ts +96 -0
  257. package/src/reference/figure-symbols.ts +117 -0
  258. package/src/reference/figures-validate.ts +2 -1
  259. package/src/reference/gate-abbreviations.ts +1 -1
  260. package/src/reference/gate-course-rules.ts +22 -7
  261. package/src/reference/gate-heading-length.ts +25 -7
  262. package/src/reference/gate-heading-structure.ts +40 -1
  263. package/src/reference/gate-indices.ts +470 -0
  264. package/src/reference/gate-math.ts +102 -13
  265. package/src/reference/gate-mdx-expression.ts +45 -0
  266. package/src/reference/gate-non-chapter-pages.ts +241 -0
  267. package/src/reference/gate-part-pages.ts +1 -1
  268. package/src/reference/gate-prop-text.ts +145 -0
  269. package/src/reference/gate-prose-notation.ts +19 -5
  270. package/src/reference/gate-retired-keys.ts +101 -0
  271. package/src/reference/gloss.ts +2 -0
  272. package/src/reference/katex-measure.ts +384 -0
  273. package/src/reference/keyed-float-validate.ts +22 -0
  274. package/src/reference/keyed-jsx-floats.mjs +11 -14
  275. package/src/reference/lab-katex-validate.ts +156 -0
  276. package/src/reference/lab-math-scope.ts +15 -1
  277. package/src/reference/lint-ignore.ts +4 -1
  278. package/src/reference/math-glyphs.mjs +851 -46
  279. package/src/reference/mixed-content.ts +11 -2
  280. package/src/reference/page-notation.ts +114 -14
  281. package/src/reference/raw-floats.mjs +37 -13
  282. package/src/reference/references.ts +14 -0
  283. package/src/reference/registry.ts +170 -9
  284. package/src/reference/rehype-fail-katex-errors.mjs +19 -0
  285. package/src/reference/rehype-heading-anchors.mjs +11 -1
  286. package/src/reference/remark-citation.mjs +0 -0
  287. package/src/reference/remark-notation.mjs +275 -23
  288. package/src/reference/render-lab-math.ts +40 -5
  289. package/src/reference/sections-validate.ts +67 -3
  290. package/src/reference/sections.mjs +92 -17
  291. package/src/reference/tables-validate.ts +40 -2
  292. package/src/reference/text-measure.ts +63 -0
  293. package/src/reference/text-widths.ts +133 -0
  294. package/src/reference/types.ts +75 -8
  295. package/src/rules/catalog.ts +241 -31
  296. package/src/rules/settings.ts +10 -0
  297. package/src/session/PreferenceStore.ts +53 -0
  298. package/src/styles/global.css +322 -83
@@ -6,8 +6,8 @@
6
6
  * quick look at the source without leaving the reader's place.
7
7
  *
8
8
  * It reads everything it shows from the page's own static markup — the
9
- * marker's `data-*` and the matching `#cite-n` reference-list item — so no
10
- * page serializes a second copy of the source database. Opening, pinning,
9
+ * marker's `data-*` and the reference-list item it links to (`#cite-n`) —
10
+ * so no page serializes a second copy of the source database. Opening, pinning,
11
11
  * Escape and placement come from the card manager (`../card-manager.ts`); a
12
12
  * card per source, several at once.
13
13
  */
@@ -21,12 +21,11 @@ import HoverPanel from '../HoverPanel.astro';
21
21
  </p>
22
22
  <div class="citation-panel-body">
23
23
  <p class="citation-panel-locator" data-panel-locator hidden></p>
24
- <p class="citation-panel-status" data-panel-status hidden></p>
25
24
  <p class="citation-panel-links">
26
- <a class="citation-panel-link" data-panel-link hidden
25
+ <a class="citation-panel-link hover-panel-action" data-panel-link hidden
27
26
  >Go to the source<span aria-hidden="true" data-jump-arrow> ↗</span></a
28
27
  >
29
- <a class="citation-panel-jump" data-panel-jump
28
+ <a class="citation-panel-jump hover-panel-action" data-panel-jump
30
29
  >Go to the reference list<span aria-hidden="true" data-jump-arrow> ↓</span></a
31
30
  >
32
31
  </p>
@@ -35,6 +34,7 @@ import HoverPanel from '../HoverPanel.astro';
35
34
 
36
35
  <script>
37
36
  import { cloneCardShell, registerCardSource } from '../card-manager';
37
+ import { fetchDoc, hrefFrom } from '../card-fetch';
38
38
  import { wireJumpArrow } from '../card-jump';
39
39
  import { referenceLabelFromText } from '../../reference/citation-format.mjs';
40
40
 
@@ -49,10 +49,11 @@ import HoverPanel from '../HoverPanel.astro';
49
49
 
50
50
  function setup() {
51
51
  const template = document.getElementById('citation-panel');
52
+ // Every page registers, with markers or none: a citation's card carried
53
+ // in from another page is built here (rule K5).
52
54
  if (
53
55
  !(template instanceof HTMLTemplateElement) ||
54
- template.dataset.cardSourceReady === 'true' ||
55
- !document.querySelector('a.citation-ref[data-citation-id]')
56
+ template.dataset.cardSourceReady === 'true'
56
57
  ) {
57
58
  return;
58
59
  }
@@ -66,27 +67,34 @@ import HoverPanel from '../HoverPanel.astro';
66
67
  return candidate && !candidate.closest('[data-card-foreign]') ? candidate : null;
67
68
  };
68
69
 
69
- // Fill a card from the marker plus its `#cite-n` reference-list item —
70
- // never from a serialized source table.
71
- const render = (number: string, marker: HTMLElement) => {
72
- const item = document.getElementById(`cite-${number}`);
70
+ // The reference-list item a marker links to (`#cite-n`). Followed, not
71
+ // built from the number: a downloaded part or course holds one list per
72
+ // chapter, each numbered from 1, with the chapter in its items' ids.
73
+ const itemIdOf = (marker: HTMLElement) => {
74
+ const href = marker.getAttribute('href') ?? '';
75
+ return href.startsWith('#') && href.length > 1 ? decodeURIComponent(href.slice(1)) : null;
76
+ };
77
+
78
+ // Fill a card from the marker plus its reference-list item — never from
79
+ // a serialized source table. On another page (K5), both come from the
80
+ // page the card was opened on, and its links go there.
81
+ const render = (itemId: string, marker: HTMLElement, from?: Document, page?: string) => {
82
+ const item = (from ?? document).getElementById(itemId);
83
+ const number = marker.dataset.citationNumber ?? '';
73
84
  const el = cloneCardShell(template);
74
85
  if (!item || !el) return null;
75
86
  const field = (name: string) => el.querySelector<HTMLElement>(`[data-panel-${name}]`);
76
87
  const markerEl = field('marker');
77
88
  const label = field('label');
78
89
  const locator = field('locator');
79
- const status = field('status');
80
90
  const link = el.querySelector<HTMLAnchorElement>('[data-panel-link]');
81
91
  const jump = el.querySelector<HTMLAnchorElement>('[data-panel-jump]');
82
- if (!markerEl || !label || !locator || !status || !link || !jump) return null;
92
+ if (!markerEl || !label || !locator || !link || !jump) return null;
83
93
 
84
94
  // The reference list is a tight list, so the `<li>` has no `<p>`
85
- // wrapper: clone it and drop the trailing link/status chrome.
86
- const line = item.cloneNode(true) as HTMLElement;
87
- for (const extra of line.querySelectorAll(
88
- '.citation-url, .citation-status, .citation-backref',
89
- )) {
95
+ // wrapper: clone it and drop the trailing link chrome.
96
+ const line = document.importNode(item, true);
97
+ for (const extra of line.querySelectorAll('.citation-url, .citation-backref')) {
90
98
  extra.remove();
91
99
  }
92
100
  const referenceText = line.textContent ?? '';
@@ -97,11 +105,6 @@ import HoverPanel from '../HoverPanel.astro';
97
105
  setText(locator, loc);
98
106
  el.setAttribute('aria-label', `Source citation ${number}`);
99
107
 
100
- const sourceStatus = item
101
- .querySelector('.citation-status')
102
- ?.textContent?.trim();
103
- setText(status, sourceStatus ? `Source record: ${sourceStatus}` : '');
104
-
105
108
  const sourceUrl = item.querySelector<HTMLAnchorElement>('a.citation-url');
106
109
  if (sourceUrl) {
107
110
  link.href = sourceUrl.href;
@@ -109,19 +112,34 @@ import HoverPanel from '../HoverPanel.astro';
109
112
  link.hidden = false;
110
113
  }
111
114
 
112
- jump.href = marker.getAttribute('href') ?? '#references';
115
+ const jumpTo = marker.getAttribute('href') ?? '#references';
116
+ jump.href = page ? hrefFrom(jumpTo, page) : jumpTo;
113
117
  wireJumpArrow(jump);
114
118
  return el;
115
119
  };
116
120
 
121
+ /** A reference-list item names its source only on its own page. */
122
+ const idOf = (item: string) => item.split('@')[0] ?? item;
123
+
117
124
  registerCardSource({
118
125
  layer: 'citation',
119
126
  triggerFrom: markerFrom,
120
127
  itemFor: (marker) => {
121
- const number = marker.dataset.citationNumber;
122
- return number && document.getElementById(`cite-${number}`) ? number : null;
128
+ const id = itemIdOf(marker);
129
+ return id && marker.dataset.citationNumber && document.getElementById(id) ? id : null;
123
130
  },
124
- render,
131
+ render: (id, marker) => render(id, marker),
132
+ // K5: on another page, `[3]` is that page's third source.
133
+ awayItem: (id, page) => `${idOf(id)}@${page}`,
134
+ build: (id, page) =>
135
+ fetchDoc(page).then((doc) => {
136
+ const marker = [
137
+ ...doc.querySelectorAll<HTMLElement>('a.citation-ref[data-citation-number]'),
138
+ ].find((candidate) => itemIdOf(candidate) === idOf(id));
139
+ const el = marker ? render(idOf(id), marker, doc, page) : null;
140
+ el?.setAttribute('data-card-foreign', '');
141
+ return el;
142
+ }),
125
143
  });
126
144
  }
127
145
 
@@ -155,21 +173,8 @@ import HoverPanel from '../HoverPanel.astro';
155
173
  color: var(--sl-color-gray-2);
156
174
  }
157
175
 
158
- .citation-panel-status {
159
- color: var(--sl-color-gray-3);
160
- font-size: var(--sl-text-xs);
161
- text-transform: lowercase;
162
- }
163
-
164
176
  .citation-panel-body .citation-panel-links {
165
177
  margin: 0;
166
178
  }
167
-
168
- .citation-panel-link,
169
- .citation-panel-jump {
170
- display: inline-block;
171
- margin-inline-end: 0.9rem;
172
- font-size: var(--sl-text-xs);
173
- }
174
179
  }
175
180
  </style>
@@ -17,9 +17,20 @@
17
17
  * client script**: `NotationLayer`'s trigger scan already queries the whole
18
18
  * document, not just its own subtree.
19
19
  *
20
- * Box sizes are a generous estimate (`reference/diagram-layout.ts`) unless a
21
- * node gives an explicit `width`/`height` — there is no DOM at build time to
22
- * measure a real render against.
20
+ * Each box is sized from what it holds (`nodeBoxSize` in
21
+ * `reference/diagram-layout.ts`): the label measured in DejaVu Sans, the
22
+ * widest common system font, and wrapped to a line count that sets the
23
+ * height, and the formula measured from KaTeX's own layout tree
24
+ * (`reference/katex-measure.ts`). A node's `width` or `height` overrides
25
+ * either; with only a `width`, the label wraps inside it and the height
26
+ * follows. A diagram wider than the lesson column first wraps its labels
27
+ * narrower until it fits (`fitDiagramLayout`).
28
+ *
29
+ * One that still does not fit (rule V8, `rules.V8.columnWidth`, or the
30
+ * diagram's own `warnAboveWidth`) is marked `data-diagram-too-wide`; the
31
+ * build reports it as a `dia-too-wide` warning with the lesson file and this
32
+ * id (`reference/diagram-fit.ts`), and `astro dev` prints the same warning as
33
+ * it renders.
23
34
  *
24
35
  * Named like every float (docs/plans/titles-and-containers.md Part 3): the
25
36
  * long `caption` sits below the graph with its number, in the same caption
@@ -31,7 +42,10 @@
31
42
  * reader fold a diagram away, wrap it in a `<Fold>` or put it in a
32
43
  * collapsible section.
33
44
  */
34
- import { estimateBoxSize, layoutDiagram } from '../../reference/diagram-layout';
45
+ import { fitDiagramLayout, FORMULA_FONT_PX } from '../../reference/diagram-layout';
46
+ import { measureFormula } from '../../reference/katex-measure';
47
+ import { diagramTooWideMessage, diagramWidthLimit } from '../../reference/diagram-fit';
48
+ import { loadManifest } from '../../compiler/manifest';
35
49
  import { renderNotationFormula } from '../notation/render-notation-math';
36
50
  import type { SafeMathNode as SafeMathNodeType } from '../notation/render-notation-math';
37
51
  import SafeMathNode from '../notation/SafeMathNode.astro';
@@ -40,8 +54,11 @@ import { DIAGRAM_NO_LONGER_FOLDS } from '../../reference/diagrams-validate';
40
54
  import type { GlyphScopeEntry } from '../../reference/gloss';
41
55
  import {
42
56
  notationKeysInMathNodes,
57
+ registerPageDerivedNotation,
43
58
  registerPageNotationDependencies,
44
59
  } from '../../reference/page-notation';
60
+ import { indexFindings } from '../../reference/gate-indices';
61
+ import type { DerivedUse } from '../../reference/types';
45
62
 
46
63
  export interface DiagramNode {
47
64
  readonly id: string;
@@ -49,8 +66,9 @@ export interface DiagramNode {
49
66
  readonly text?: string;
50
67
  /** Raw LaTeX, resolved against `scope` before rendering. */
51
68
  readonly formula?: string;
52
- /** Overrides the size estimate — use for a label the heuristic gets wrong. */
69
+ /** The box's width in px; the label wraps inside it and the height follows. */
53
70
  readonly width?: number;
71
+ /** The box's height in px. */
54
72
  readonly height?: number;
55
73
  }
56
74
 
@@ -76,8 +94,9 @@ export interface Props {
76
94
  /** Computed by `remark-notation.mjs` for a top-level lesson diagram. */
77
95
  anchorId?: string;
78
96
  /**
79
- * Emit a build warning when the laid-out SVG is wider than this many pixels.
80
- * Set `false` only when horizontal overflow is deliberate. Default: `800`.
97
+ * The widest this diagram may lay out, in px, before the build warns
98
+ * (`dia-too-wide`, rule V8). Default: the course's `rules.V8.columnWidth`,
99
+ * the lesson column. Set `false` only when horizontal overflow is deliberate.
81
100
  */
82
101
  warnAboveWidth?: number | false;
83
102
  }
@@ -92,7 +111,7 @@ const {
92
111
  direction = 'TB',
93
112
  number,
94
113
  anchorId,
95
- warnAboveWidth = 800,
114
+ warnAboveWidth,
96
115
  } = Astro.props;
97
116
 
98
117
  const idPattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
@@ -128,6 +147,7 @@ if ((number && !anchorId) || (!number && anchorId)) {
128
147
  );
129
148
  }
130
149
  if (
150
+ warnAboveWidth !== undefined &&
131
151
  warnAboveWidth !== false &&
132
152
  (!Number.isFinite(warnAboveWidth) || warnAboveWidth <= 0)
133
153
  ) {
@@ -136,26 +156,47 @@ if (
136
156
 
137
157
  interface RenderedNode extends DiagramNode {
138
158
  readonly mathNodes?: readonly SafeMathNodeType[];
159
+ /** The LaTeX `mathNodes` were rendered from, glyphs bound. */
160
+ readonly boundFormula?: string;
139
161
  }
140
162
 
163
+ // The course's rule settings; a page rendered without a manifest (a
164
+ // component test) checks against the defaults.
165
+ const rules = await loadManifest().then(
166
+ (manifest) => manifest.rules,
167
+ () => undefined,
168
+ );
169
+
170
+ // Rule N23: a diagram's formula is page math, so its indexed symbols take
171
+ // derived cards like the lesson's, and are held to the rule.
172
+ const derivedUses: DerivedUse[] = [];
141
173
  const rendered: RenderedNode[] = nodes.map((node) => {
142
174
  if (!node.text && !node.formula) {
143
175
  throw new Error(`Diagram "${id}" node "${node.id}" has neither text nor formula.`);
144
176
  }
145
177
  if (!node.formula) return { ...node };
146
178
 
147
- const { nodes: mathNodes, unresolved, degraded } = renderNotationFormula(
148
- node.formula,
149
- scope,
150
- );
179
+ const {
180
+ nodes: mathNodes,
181
+ latex,
182
+ unresolved,
183
+ degraded,
184
+ reason,
185
+ derived,
186
+ indices,
187
+ } = renderNotationFormula(node.formula, scope, { derive: true });
151
188
  if (degraded) {
189
+ // The resolver says what went wrong; an empty `scope` is the usual cause
190
+ // when the diagram is written straight into MDX, where nothing passes it.
191
+ const emptyScope =
192
+ scope.length === 0
193
+ ? ' The diagram has no `scope`, so no symbol can resolve: pass the page\'s ' +
194
+ 'notation scope (`labMathScope`), as a wrapper component does.'
195
+ : '';
152
196
  throw new Error(
153
197
  `Diagram "${id}" node "${node.id}" formula "${node.formula}" could not be ` +
154
- `bound to the notation scope: the LaTeX did not parse, or a bare glyph is ` +
155
- `the base of more than one page symbol. Wrap each symbol in ` +
156
- `\\explain{key}{latex}, or brace a subscripted base (write {v_0}^{2}, not ` +
157
- `v_0^{2}), so every glyph resolves — a diagram formula is never allowed to ` +
158
- `render as silent, unexplained math.`,
198
+ `bound to the notation scope: ${(reason ?? 'the LaTeX did not parse').replace(/\.$/, '')}.${emptyScope} ` +
199
+ `A diagram formula is never allowed to render as silent, unexplained math.`,
159
200
  );
160
201
  }
161
202
  if (unresolved.length > 0) {
@@ -166,33 +207,55 @@ const rendered: RenderedNode[] = nodes.map((node) => {
166
207
  `or pass it in the diagram's scope.`,
167
208
  );
168
209
  }
169
- return { ...node, mathNodes };
210
+ const findings = rules?.disabled.includes('N23') ? [] : indexFindings(indices);
211
+ if (findings.length > 0) {
212
+ throw new Error(
213
+ `Diagram "${id}" node "${node.id}" formula "${node.formula}" has an index no card ` +
214
+ `explains (rule N23): ${findings.join('; ')}.`,
215
+ );
216
+ }
217
+ derivedUses.push(...derived);
218
+ return { ...node, mathNodes, boundFormula: latex };
170
219
  });
171
220
 
221
+ const derivedKeys = new Set(derivedUses.map((use) => use.key));
222
+ registerPageDerivedNotation(Astro.locals, derivedUses, `Diagram "${id}"`);
172
223
  for (const node of rendered) {
173
224
  if (!node.mathNodes) continue;
174
225
  registerPageNotationDependencies(
175
226
  Astro.locals,
176
- notationKeysInMathNodes(node.mathNodes),
227
+ // A derived card is no definition: it ships through its own registry.
228
+ notationKeysInMathNodes(node.mathNodes).filter((key) => !derivedKeys.has(key)),
177
229
  `Diagram "${id}" node "${node.id}" formula "${node.formula}"`,
178
230
  );
179
231
  }
180
232
 
181
- const sized = rendered.map((node) => ({
182
- node,
183
- box:
184
- node.width && node.height
185
- ? { width: node.width, height: node.height }
186
- : estimateBoxSize(
187
- node.formula ?? node.text ?? node.id,
188
- node.formula ? 'formula' : 'text',
189
- ),
190
- }));
191
-
192
- const layout = layoutDiagram(
193
- sized.map(({ node, box }) => ({ id: node.id, width: box.width, height: box.height })),
233
+ for (const node of rendered) {
234
+ for (const side of ['width', 'height'] as const) {
235
+ const value = node[side];
236
+ if (value !== undefined && !(Number.isFinite(value) && value > 0)) {
237
+ throw new Error(`Diagram "${id}" node "${node.id}" needs a positive \`${side}\`.`);
238
+ }
239
+ }
240
+ }
241
+
242
+ // Rule V8's settings, from the course's rules above.
243
+ const widthLimit = diagramWidthLimit(warnAboveWidth, rules);
244
+
245
+ const { layout } = fitDiagramLayout(
246
+ rendered.map((node) => ({
247
+ id: node.id,
248
+ content: {
249
+ ...(node.text ? { text: node.text } : {}),
250
+ ...(node.boundFormula
251
+ ? { formula: measureFormula(node.boundFormula, FORMULA_FONT_PX) }
252
+ : {}),
253
+ },
254
+ ...(node.width !== undefined ? { width: node.width } : {}),
255
+ ...(node.height !== undefined ? { height: node.height } : {}),
256
+ })),
194
257
  edges.map((edge) => ({ from: edge.from, to: edge.to })),
195
- { direction },
258
+ { direction, ...(widthLimit !== undefined ? { fitWidth: widthLimit } : {}) },
196
259
  );
197
260
 
198
261
  const nodeById = new Map(rendered.map((node) => [node.id, node]));
@@ -203,13 +266,13 @@ const renderedAnchorId = anchorId ?? legacyAnchorId;
203
266
  const viewportId = `${legacyAnchorId}-viewport`;
204
267
  const captionTextId = `${legacyAnchorId}-caption`;
205
268
 
206
- if (warnAboveWidth !== false && layout.width > warnAboveWidth) {
269
+ const tooWide = widthLimit !== undefined && layout.width > widthLimit;
270
+ if (tooWide && import.meta.env.DEV) {
271
+ const entry = (Astro.locals as { starlightRoute?: { entry?: { filePath?: string; id?: string } } })
272
+ .starlightRoute?.entry;
207
273
  console.warn(
208
- `[explico/Diagram] "${id}" lays out at ${layout.width}px wide, above the ` +
209
- `${warnAboveWidth}px authoring threshold. It will scroll and can be expanded ` +
210
- `by readers, but consider recreating the graph with direction="TB", shorter ` +
211
- `labels, or explicit node sizes. Set warnAboveWidth={false} only when the ` +
212
- `horizontal overflow is intentional.`,
274
+ `[dia-too-wide] ${entry?.filePath ?? entry?.id ?? Astro.url.pathname}: ` +
275
+ diagramTooWideMessage(id, layout.width, widthLimit),
213
276
  );
214
277
  }
215
278
 
@@ -227,6 +290,7 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
227
290
  data-dia-key={number ? id : undefined}
228
291
  data-dia-short={short}
229
292
  data-diagram-width={layout.width}
293
+ data-diagram-too-wide={tooWide ? widthLimit : undefined}
230
294
  data-diagram-expandable
231
295
  >
232
296
  <div class="diagram__body">
@@ -426,23 +490,38 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
426
490
  stroke-width: 1;
427
491
  }
428
492
 
493
+ /* `NODE_FONT_PX` and the insets in `reference/diagram-layout.ts` size each
494
+ box from these values; change them together. The size is in px, not
495
+ rem: the boxes are measured at build time for 13.6px text, so text that
496
+ followed a reader's larger default font size (20px, Chrome's "Large")
497
+ outgrew its box and was cut off. Scaling the whole drawing instead is no
498
+ way out — WebKit on iOS scales the SVG but not the HTML in a
499
+ `foreignObject`. Browser zoom still enlarges it with everything else. */
429
500
  .diagram-node-content {
430
501
  display: flex;
431
502
  height: 100%;
432
503
  flex-direction: column;
433
504
  align-items: center;
434
505
  justify-content: center;
435
- gap: 0.15rem;
436
- padding: 0.2rem 0.5rem;
506
+ gap: 2.4px;
507
+ padding: 3.2px 8px;
437
508
  color: var(--sl-color-text);
438
509
  font-family: var(--sl-font);
439
- font-size: 0.85rem;
510
+ font-size: 13.6px;
440
511
  line-height: 1.25;
441
512
  text-align: center;
442
513
  }
443
514
 
444
- .diagram-node-formula :global(.katex) {
445
- font-size: 1em;
515
+ /* The box was sized for its label wrapped evenly; a narrower font
516
+ balances it the same way rather than leaving one word alone. */
517
+ .diagram-node-text {
518
+ text-wrap: balance;
519
+ }
520
+
521
+ /* The box is as wide as the formula on one line (`nodeBoxSize`); KaTeX's
522
+ inline math would otherwise break after a `+` or `=` and outgrow it. */
523
+ .diagram-node-formula {
524
+ white-space: nowrap;
446
525
  }
447
526
 
448
527
  .diagram-edge {
@@ -16,6 +16,15 @@
16
16
  * never prints, open or folded, and is left exactly as it is: no note, not
17
17
  * opened for an export.
18
18
  *
19
+ * A note never goes inside an Astro island. An island still waiting to
20
+ * hydrate (`client:visible` — every knowledge check) that starts while the
21
+ * page is prepared for print would find a node its framework never rendered
22
+ * and throw away its server render (React's error #418); a hydrated one's
23
+ * markup is its framework's to own either way. A closed disclosure inside
24
+ * an island gets its note after the outermost island instead, once per
25
+ * target URL: the folded questions of one check all point at the check's
26
+ * own anchor, so one note after the check says it for all of them.
27
+ *
19
28
  * Two callers share this module:
20
29
  * - `LessonExportActions.astro`, the print controller a course gets with
21
30
  * `lessonExports.print`: `'as-shown'` for its print button and the
@@ -90,6 +99,15 @@ function anchorIdFor(element: Element): string | undefined {
90
99
  return undefined;
91
100
  }
92
101
 
102
+ /** The outermost `<astro-island>` around an element, if any. */
103
+ function outermostIsland(element: Element): Element | null {
104
+ let island: Element | null = null;
105
+ for (let node = element.parentElement; node; node = node.parentElement) {
106
+ if (node.tagName === 'ASTRO-ISLAND') island = node;
107
+ }
108
+ return island;
109
+ }
110
+
93
111
  function createNote(href: string): HTMLElement {
94
112
  const note = document.createElement('div');
95
113
  note.className = COLLAPSED_NOTE_CLASS;
@@ -152,20 +170,38 @@ export function prepareDisclosures(
152
170
  const insideClosed = (element: Element) =>
153
171
  !!element.parentElement?.closest('details:not([open])');
154
172
 
173
+ // The notes already placed after each island: the last one, which the
174
+ // next follows, and the URLs they name.
175
+ const afterIsland = new Map<Element, { last: Element; hrefs: Set<string> }>();
176
+ const place = (element: Element, where: 'before' | 'after') => {
177
+ const href = options.noteHref(anchorIdFor(element));
178
+ const island = outermostIsland(element);
179
+ if (!island) {
180
+ const note = createNote(href);
181
+ element[where](note);
182
+ notes.push(note);
183
+ return;
184
+ }
185
+ const placed = afterIsland.get(island) ?? { last: island, hrefs: new Set<string>() };
186
+ if (placed.hrefs.has(href)) return;
187
+ const note = createNote(href);
188
+ placed.last.after(note);
189
+ placed.last = note;
190
+ placed.hrefs.add(href);
191
+ afterIsland.set(island, placed);
192
+ notes.push(note);
193
+ };
194
+
155
195
  for (const details of main.querySelectorAll('details:not([open]):not([data-print-omit])')) {
156
196
  if (insideClosed(details)) continue;
157
- const note = createNote(options.noteHref(anchorIdFor(details)));
158
- details.after(note);
159
- notes.push(note);
197
+ place(details, 'after');
160
198
  }
161
199
  // Disclosures built on `hidden` rather than `<details>`.
162
200
  for (const element of main.querySelectorAll(
163
201
  ':is(.short-note, .assessment-panel)[hidden]',
164
202
  )) {
165
203
  if (insideClosed(element)) continue;
166
- const note = createNote(options.noteHref(anchorIdFor(element)));
167
- element.before(note);
168
- notes.push(note);
204
+ place(element, 'before');
169
205
  }
170
206
  }
171
207
 
@@ -84,7 +84,7 @@ if (short !== undefined && (typeof short !== 'string' || !short.trim())) {
84
84
  }
85
85
  if (!number || !anchorId) {
86
86
  throw new Error(
87
- 'Figure is missing its computed `number`/`anchorId` — this page must run remark-notation with a `figures` cross-page table configured (see astro.config.mjs).',
87
+ `Figure is missing its computed \`number\`/\`anchorId\`. remark-notation numbers a <Figure> only on a lesson page, at its top level or in a <Memo>, <ShortNote> or <Fold>: elsewhere (a worked example) show the content without <Figure>, and on a page that is not a chapter (rule S10) link the chapter's figure. At a lesson's top level, this page is not running remark-notation with the manifest's \`figures\` (see astro.config.mjs).`,
88
88
  );
89
89
  }
90
90
 
@@ -176,8 +176,13 @@ const name = `Figure ${number}`;
176
176
  Q8). A figure that should read as set apart goes in a `### … [card]`
177
177
  section. The caption line is styled once for every float, in
178
178
  `global.css` (`.float-caption`). */
179
+ /* A wide picture scrolls sideways. `overflow-x: auto` alone computes
180
+ `overflow-y` to `auto` too, and a body with no fixed height overflows
181
+ vertically only by rounding (a fractional height at a browser zoom),
182
+ which drew a one-pixel vertical scrollbar beside the figure. */
179
183
  .keyed-figure__body {
180
184
  overflow-x: auto;
185
+ overflow-y: hidden;
181
186
  }
182
187
 
183
188
  /* A lab's tools: a tray of their own after the caption line — a thin
@@ -258,6 +263,17 @@ const name = `Figure ${number}`;
258
263
  .keyed-figure__tools-stub {
259
264
  display: none !important;
260
265
  }
266
+
267
+ /* A lab the reader never scrolled to has not drawn yet. The Print button
268
+ and Ctrl+P load it first; the browser's own File → Print cannot wait,
269
+ so the empty box says how to print the figure. */
270
+ .keyed-figure__body:has(astro-island[ssr])::after {
271
+ content: 'Interactive figure not loaded: print with the page’s Print button or Ctrl+P to include it.';
272
+ display: block;
273
+ font-size: 0.85em;
274
+ font-style: italic;
275
+ padding: 0.5rem 0;
276
+ }
261
277
  }
262
278
  </style>
263
279
 
@@ -4,11 +4,11 @@
4
4
  * (docs/plans/figures-and-short-captions.md, decisions 1 and 4). One
5
5
  * anatomy, below the picture, the LaTeX figure convention:
6
6
  *
7
- * Figure 2.2.1 [chain] **‹short›.** ‹caption› ‹live end› [buttons]
7
+ * Figure 2.2.1 **‹short›.** ‹caption› ‹live end› [buttons]
8
8
  *
9
- * The number links to the float, the chain icon copies that link
10
- * (`HeadingAnchorEnhancer.astro` reads `data-copy-link`) and then navigates
11
- * the same way, and the caption is the float's one long caption. A float
9
+ * The number links to the float; hovering or focusing it shows its toolbar,
10
+ * whose Copy link copies that link (`float-toolbar.ts`). The caption is the
11
+ * float's one long caption. A float
12
12
  * with a short caption opens the caption with it as a bold title,
13
13
  * `b.float-caption__title` (decision 9.1), outside `.float-caption__text`, so
14
14
  * a reader of the long caption (a preview card, a fold's bar) still reads
@@ -65,19 +65,6 @@ const tools = Astro.slots.has('default') ? (await Astro.slots.render('default'))
65
65
  </a>
66
66
  )
67
67
  }
68
- <a
69
- class="float-caption__anchor"
70
- href={`#${anchorId}`}
71
- aria-label={`Copy the link to ${name}`}
72
- title="Copy link"
73
- data-copy-link={name}
74
- >
75
- <svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"
76
- ><path
77
- d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"
78
- ></path></svg
79
- >
80
- </a>
81
68
  {short && <b class="float-caption__title">{captionTitle(short)}</b>}{short && ' '}<span
82
69
  class:list={['float-caption__text', `${prefix}__caption-text`]}
83
70
  id={textId}