explico 0.3.0 → 0.5.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 (151) hide show
  1. package/CHANGELOG.md +344 -1
  2. package/README.md +117 -3
  3. package/dist/compiler/astro.d.ts +38 -0
  4. package/dist/compiler/astro.js +258 -0
  5. package/dist/compiler/cli.d.ts +13 -0
  6. package/dist/compiler/cli.js +32 -12
  7. package/dist/compiler/collections.d.ts +3 -2
  8. package/dist/compiler/collections.js +28 -1
  9. package/dist/compiler/course-config.d.ts +86 -4
  10. package/dist/compiler/course-config.js +39 -0
  11. package/dist/compiler/exports/standalone-html.d.ts +34 -0
  12. package/dist/compiler/exports/standalone-html.js +803 -0
  13. package/dist/compiler/lesson-derivation.d.ts +1 -1
  14. package/dist/compiler/lesson-derivation.js +2 -2
  15. package/dist/compiler/manifest.d.ts +112 -7
  16. package/dist/compiler/manifest.js +205 -40
  17. package/dist/compiler/resolution-report.d.ts +4 -0
  18. package/dist/reference/base-abbreviations.d.mts +30 -0
  19. package/dist/reference/base-abbreviations.mjs +60 -0
  20. package/dist/reference/base-operators.d.mts +15 -0
  21. package/dist/reference/base-operators.mjs +40 -0
  22. package/dist/reference/base-roman-labels.d.mts +35 -0
  23. package/dist/reference/base-roman-labels.mjs +43 -0
  24. package/dist/reference/consistency.d.ts +8 -2
  25. package/dist/reference/consistency.js +50 -3
  26. package/dist/reference/diagrams-validate.d.ts +21 -0
  27. package/dist/reference/diagrams-validate.js +13 -0
  28. package/dist/reference/equations-validate.d.ts +8 -3
  29. package/dist/reference/equations-validate.js +1 -1
  30. package/dist/reference/equations.d.mts +66 -35
  31. package/dist/reference/equations.mjs +142 -40
  32. package/dist/reference/gate-abbreviations.d.ts +17 -0
  33. package/dist/reference/gate-abbreviations.js +64 -0
  34. package/dist/reference/gate-math.d.ts +28 -5
  35. package/dist/reference/gate-math.js +5 -2
  36. package/dist/reference/gate-prose-notation.d.ts +60 -0
  37. package/dist/reference/gate-prose-notation.js +125 -0
  38. package/dist/reference/keyed-float-validate.d.ts +14 -8
  39. package/dist/reference/keyed-float-validate.js +1 -1
  40. package/dist/reference/keyed-jsx-floats.d.mts +24 -21
  41. package/dist/reference/keyed-jsx-floats.mjs +90 -41
  42. package/dist/reference/knowledge-checks-validate.d.ts +35 -0
  43. package/dist/reference/knowledge-checks-validate.js +78 -0
  44. package/dist/reference/knowledge-checks.d.mts +25 -0
  45. package/dist/reference/knowledge-checks.mjs +45 -0
  46. package/dist/reference/lab-math-scope.d.ts +9 -1
  47. package/dist/reference/lab-math-scope.js +32 -4
  48. package/dist/reference/math-glyphs.d.mts +30 -0
  49. package/dist/reference/math-glyphs.mjs +367 -0
  50. package/dist/reference/mdx-attributes.d.mts +13 -0
  51. package/dist/reference/mdx-attributes.mjs +38 -0
  52. package/dist/reference/mixed-content.d.ts +17 -0
  53. package/dist/reference/mixed-content.js +29 -0
  54. package/dist/reference/page-notation.d.ts +52 -0
  55. package/dist/reference/page-notation.js +171 -0
  56. package/dist/reference/raw-floats-validate.d.ts +18 -0
  57. package/dist/reference/raw-floats-validate.js +53 -0
  58. package/dist/reference/raw-floats.d.mts +18 -0
  59. package/dist/reference/raw-floats.mjs +129 -0
  60. package/dist/reference/rehype-heading-anchors.d.mts +1 -1
  61. package/dist/reference/rehype-heading-anchors.mjs +107 -1
  62. package/dist/reference/rehype-table-header-labels.d.mts +26 -0
  63. package/dist/reference/rehype-table-header-labels.mjs +152 -0
  64. package/dist/reference/remark-abbreviations.d.mts +7 -0
  65. package/dist/reference/remark-abbreviations.mjs +174 -0
  66. package/dist/reference/remark-memo.d.mts +16 -0
  67. package/dist/reference/remark-memo.mjs +339 -0
  68. package/dist/reference/remark-notation.d.mts +102 -5
  69. package/dist/reference/remark-notation.mjs +506 -50
  70. package/dist/reference/remark-short-note.d.mts +4 -0
  71. package/dist/reference/remark-short-note.mjs +237 -0
  72. package/dist/reference/types.d.ts +16 -1
  73. package/dist/reference/units-vocab.js +4 -0
  74. package/docs/VERSIONING.md +33 -4
  75. package/docs/authoring/README.md +12 -0
  76. package/docs/authoring/agents.md +17 -2
  77. package/docs/authoring/components.md +135 -0
  78. package/docs/authoring/migrating.md +154 -7
  79. package/docs/authoring/notation.md +568 -8
  80. package/docs/authoring/structure.md +251 -0
  81. package/docs/authoring/visualizations.md +36 -3
  82. package/package.json +5 -2
  83. package/src/compiler/astro.ts +332 -0
  84. package/src/compiler/cli.ts +33 -10
  85. package/src/compiler/collections.ts +45 -4
  86. package/src/compiler/course-config.ts +131 -3
  87. package/src/compiler/exports/standalone-html.ts +963 -0
  88. package/src/compiler/lesson-derivation.ts +2 -2
  89. package/src/compiler/manifest.ts +367 -49
  90. package/src/components/HoverPanel.astro +45 -4
  91. package/src/components/abbreviation/AbbreviationGlossary.astro +70 -0
  92. package/src/components/abbreviation/AbbreviationLayer.astro +127 -0
  93. package/src/components/assessments/AssessmentRunner.tsx +52 -17
  94. package/src/components/assessments/AssessmentSet.astro +98 -14
  95. package/src/components/assessments/RenderedContent.tsx +55 -0
  96. package/src/components/assessments/assessment-randomization.ts +16 -0
  97. package/src/components/diagram/Diagram.astro +444 -83
  98. package/src/components/disclosure/hover-preview.ts +202 -0
  99. package/src/components/disclosure/persisted-disclosure.ts +66 -0
  100. package/src/components/examples/CompactExamples.astro +2 -0
  101. package/src/components/figure/Figure.astro +279 -10
  102. package/src/components/memo/Memo.astro +453 -0
  103. package/src/components/memo/ShortNote.astro +223 -0
  104. package/src/components/notation/NotationFormula.astro +18 -1
  105. package/src/components/notation/NotationLayer.astro +116 -8
  106. package/src/components/notation/render-notation-math.ts +39 -2
  107. package/src/components/notation/safe-math-to-react.ts +50 -0
  108. package/src/components/reference/FloatRefLayer.astro +233 -15
  109. package/src/components/starlight/CollapsibleSections.astro +461 -0
  110. package/src/components/starlight/DiagramEnhancer.astro +385 -0
  111. package/src/components/starlight/HeadingAnchorEnhancer.astro +5 -17
  112. package/src/components/starlight/KeyedFloatEnhancer.astro +81 -3
  113. package/src/components/starlight/LayoutHeader.astro +50 -0
  114. package/src/components/starlight/LayoutSidebar.astro +62 -1
  115. package/src/components/starlight/LayoutSocialIcons.astro +41 -2
  116. package/src/components/starlight/LessonExportActions.astro +436 -0
  117. package/src/components/starlight/LessonFooter.astro +38 -2
  118. package/src/components/starlight/section-parts.ts +31 -0
  119. package/src/components/table/Table.astro +296 -9
  120. package/src/components/table/TableOverflowExpander.astro +489 -0
  121. package/src/content-config.ts +20 -0
  122. package/src/reference/base-abbreviations.mjs +60 -0
  123. package/src/reference/base-operators.mjs +40 -0
  124. package/src/reference/base-roman-labels.mjs +43 -0
  125. package/src/reference/consistency.ts +73 -2
  126. package/src/reference/diagrams-validate.ts +40 -0
  127. package/src/reference/equations-validate.ts +6 -4
  128. package/src/reference/equations.mjs +142 -40
  129. package/src/reference/gate-abbreviations.ts +128 -0
  130. package/src/reference/gate-math.ts +27 -7
  131. package/src/reference/gate-prose-notation.ts +224 -0
  132. package/src/reference/keyed-float-validate.ts +12 -9
  133. package/src/reference/keyed-jsx-floats.mjs +90 -41
  134. package/src/reference/knowledge-checks-validate.ts +130 -0
  135. package/src/reference/knowledge-checks.mjs +45 -0
  136. package/src/reference/lab-math-scope.ts +45 -3
  137. package/src/reference/math-glyphs.mjs +367 -0
  138. package/src/reference/mdx-attributes.mjs +38 -0
  139. package/src/reference/mixed-content.ts +69 -0
  140. package/src/reference/page-notation.ts +252 -0
  141. package/src/reference/raw-floats-validate.ts +103 -0
  142. package/src/reference/raw-floats.mjs +129 -0
  143. package/src/reference/rehype-heading-anchors.mjs +107 -1
  144. package/src/reference/rehype-table-header-labels.mjs +152 -0
  145. package/src/reference/remark-abbreviations.mjs +174 -0
  146. package/src/reference/remark-memo.mjs +339 -0
  147. package/src/reference/remark-notation.mjs +506 -50
  148. package/src/reference/remark-short-note.mjs +237 -0
  149. package/src/reference/types.ts +16 -0
  150. package/src/reference/units-vocab.ts +4 -0
  151. package/src/styles/global.css +1125 -50
@@ -26,6 +26,10 @@ import { renderNotationFormula } from '../notation/render-notation-math';
26
26
  import type { SafeMathNode as SafeMathNodeType } from '../notation/render-notation-math';
27
27
  import SafeMathNode from '../notation/SafeMathNode.astro';
28
28
  import type { GlyphScopeEntry } from '../../reference/gloss';
29
+ import {
30
+ notationKeysInMathNodes,
31
+ registerPageNotationDependencies,
32
+ } from '../../reference/page-notation';
29
33
 
30
34
  export interface DiagramNode {
31
35
  readonly id: string;
@@ -53,6 +57,19 @@ export interface Props {
53
57
  /** Glyph scope `formula` resolves against — omit if no node uses `formula`. */
54
58
  scope?: readonly GlyphScopeEntry[];
55
59
  direction?: 'TB' | 'LR';
60
+ /** Computed by `remark-notation.mjs` for a top-level lesson diagram. */
61
+ number?: string;
62
+ /** Computed by `remark-notation.mjs` for a top-level lesson diagram. */
63
+ anchorId?: string;
64
+ /** Author-chosen state for a reader with nothing saved yet. Default: `true`. */
65
+ defaultOpen?: boolean;
66
+ /** Persist the reader's open/closed choice across visits. Default: `true`. */
67
+ rememberState?: boolean;
68
+ /**
69
+ * Emit a build warning when the laid-out SVG is wider than this many pixels.
70
+ * Set `false` only when horizontal overflow is deliberate. Default: `800`.
71
+ */
72
+ warnAboveWidth?: number | false;
56
73
  /**
57
74
  * Prose text alternative for the SVG. When omitted, the fallback is
58
75
  * generated from the node labels and the edge chain — readable, but a
@@ -69,6 +86,11 @@ const {
69
86
  scope = [],
70
87
  direction = 'TB',
71
88
  description,
89
+ number,
90
+ anchorId,
91
+ defaultOpen = true,
92
+ rememberState = true,
93
+ warnAboveWidth = 800,
72
94
  } = Astro.props;
73
95
 
74
96
  const idPattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
@@ -78,6 +100,17 @@ if (!idPattern.test(id)) {
78
100
  if (nodes.length === 0) {
79
101
  throw new Error(`Diagram "${id}" has no nodes.`);
80
102
  }
103
+ if ((number && !anchorId) || (!number && anchorId)) {
104
+ throw new Error(
105
+ `Diagram "${id}" must receive both computed \`number\` and \`anchorId\`, or neither.`,
106
+ );
107
+ }
108
+ if (
109
+ warnAboveWidth !== false &&
110
+ (!Number.isFinite(warnAboveWidth) || warnAboveWidth <= 0)
111
+ ) {
112
+ throw new Error(`Diagram "${id}" requires a positive \`warnAboveWidth\`.`);
113
+ }
81
114
 
82
115
  interface RenderedNode extends DiagramNode {
83
116
  readonly mathNodes?: readonly SafeMathNodeType[];
@@ -114,6 +147,15 @@ const rendered: RenderedNode[] = nodes.map((node) => {
114
147
  return { ...node, mathNodes };
115
148
  });
116
149
 
150
+ for (const node of rendered) {
151
+ if (!node.mathNodes) continue;
152
+ registerPageNotationDependencies(
153
+ Astro.locals,
154
+ notationKeysInMathNodes(node.mathNodes),
155
+ `Diagram "${id}" node "${node.id}" formula "${node.formula}"`,
156
+ );
157
+ }
158
+
117
159
  const sized = rendered.map((node) => ({
118
160
  node,
119
161
  box:
@@ -134,6 +176,19 @@ const layout = layoutDiagram(
134
176
  const nodeById = new Map(rendered.map((node) => [node.id, node]));
135
177
  const laidOutById = new Map(layout.nodes.map((node) => [node.id, node]));
136
178
  const markerId = `diagram-arrow-${id}`;
179
+ const legacyAnchorId = `diagram-${id}`;
180
+ const renderedAnchorId = anchorId ?? legacyAnchorId;
181
+ const viewportId = `${legacyAnchorId}-viewport`;
182
+
183
+ if (warnAboveWidth !== false && layout.width > warnAboveWidth) {
184
+ console.warn(
185
+ `[explico/Diagram] "${id}" lays out at ${layout.width}px wide, above the ` +
186
+ `${warnAboveWidth}px authoring threshold. It will scroll and can be expanded ` +
187
+ `by readers, but consider recreating the graph with direction="TB", shorter ` +
188
+ `labels, or explicit node sizes. Set warnAboveWidth={false} only when the ` +
189
+ `horizontal overflow is intentional.`,
190
+ );
191
+ }
137
192
 
138
193
  /** A node's human label for the text fallback — never the internal id. */
139
194
  function fallbackLabel(nodeId: string): string {
@@ -148,100 +203,361 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
148
203
  }
149
204
  ---
150
205
 
151
- <figure class="diagram" id={`diagram-${id}`}>
152
- <figcaption id={`${id}-caption`}>{label}</figcaption>
153
- <div class="diagram-scroll" tabindex="0" role="group" aria-labelledby={`${id}-caption`}>
154
- <svg
155
- viewBox={`0 0 ${layout.width} ${layout.height}`}
156
- width={layout.width}
157
- height={layout.height}
158
- >
159
- <defs>
160
- <marker
161
- id={markerId}
162
- viewBox="0 0 10 10"
163
- refX="9"
164
- refY="5"
165
- markerWidth="7"
166
- markerHeight="7"
167
- orient="auto-start-reverse"
168
- >
169
- <path d="M 0 0 L 10 5 L 0 10 z" class="diagram-arrowhead" />
170
- </marker>
171
- </defs>
172
-
173
- {
174
- layout.edges.map((edge) => (
175
- <path
176
- d={pathFrom(edge.points)}
177
- class="diagram-edge"
178
- marker-end={`url(#${markerId})`}
179
- />
180
- ))
181
- }
182
-
183
- {
184
- layout.nodes.map((position) => {
185
- const node = nodeById.get(position.id)!;
186
- return (
187
- <g transform={`translate(${position.x}, ${position.y})`} class="diagram-node">
188
- <rect width={position.width} height={position.height} rx="8" ry="8" />
189
- <foreignObject width={position.width} height={position.height}>
190
- <div xmlns="http://www.w3.org/1999/xhtml" class="diagram-node-content">
191
- {node.text && <span class="diagram-node-text">{node.text}</span>}
192
- {node.mathNodes && (
193
- <span class="diagram-node-formula notation-math">
194
- {node.mathNodes.map((mathNode) => (
195
- <SafeMathNode node={mathNode} />
206
+ <figure
207
+ class="diagram"
208
+ id={renderedAnchorId}
209
+ aria-labelledby={`${id}-caption`}
210
+ data-dia-number={number}
211
+ data-dia-key={number ? id : undefined}
212
+ data-diagram-width={layout.width}
213
+ data-diagram-expandable
214
+ >
215
+ <div class="diagram__titlebar">
216
+ <details
217
+ class="diagram__details"
218
+ data-diagram-details
219
+ data-remember-state={String(rememberState)}
220
+ data-state={defaultOpen ? 'open' : 'closed'}
221
+ open={defaultOpen}
222
+ >
223
+ <summary class="diagram__summary" id={`${id}-caption`}>
224
+ {number && <span class="diagram__number">Diagram {number}</span>}
225
+ <span class="diagram__caption-text">{label}</span>
226
+ </summary>
227
+ <div class="diagram__body" data-diagram-body>
228
+ <div
229
+ class="diagram-stage"
230
+ id={renderedAnchorId === legacyAnchorId ? undefined : legacyAnchorId}
231
+ >
232
+ <div
233
+ id={viewportId}
234
+ class="diagram-scroll"
235
+ tabindex="0"
236
+ role="group"
237
+ aria-labelledby={`${id}-caption`}
238
+ >
239
+ <div
240
+ class="diagram-canvas"
241
+ style={`--diagram-print-ratio: ${layout.width} / ${layout.height}; width: ${layout.width}px; height: ${layout.height}px;`}
242
+ >
243
+ <svg
244
+ viewBox={`0 0 ${layout.width} ${layout.height}`}
245
+ width="100%"
246
+ height="100%"
247
+ >
248
+ <defs>
249
+ <marker
250
+ id={markerId}
251
+ viewBox="0 0 10 10"
252
+ refX="9"
253
+ refY="5"
254
+ markerWidth="7"
255
+ markerHeight="7"
256
+ orient="auto-start-reverse"
257
+ >
258
+ <path
259
+ d="M 0 0 L 10 5 L 0 10 z"
260
+ class="diagram-arrowhead"
261
+ />
262
+ </marker>
263
+ </defs>
264
+
265
+ {
266
+ layout.edges.map((edge) => (
267
+ <path
268
+ d={pathFrom(edge.points)}
269
+ class="diagram-edge"
270
+ marker-end={`url(#${markerId})`}
271
+ />
272
+ ))
273
+ }
274
+
275
+ {
276
+ layout.nodes.map((position) => {
277
+ const node = nodeById.get(position.id)!;
278
+ return (
279
+ <g
280
+ transform={`translate(${position.x}, ${position.y})`}
281
+ class="diagram-node"
282
+ >
283
+ <rect
284
+ width={position.width}
285
+ height={position.height}
286
+ rx="8"
287
+ ry="8"
288
+ />
289
+ <foreignObject
290
+ width={position.width}
291
+ height={position.height}
292
+ >
293
+ <div
294
+ xmlns="http://www.w3.org/1999/xhtml"
295
+ class="diagram-node-content"
296
+ >
297
+ {
298
+ node.text && (
299
+ <span class="diagram-node-text">{node.text}</span>
300
+ )
301
+ }
302
+ {
303
+ node.mathNodes && (
304
+ <span class="diagram-node-formula notation-math">
305
+ {node.mathNodes.map((mathNode) => (
306
+ <SafeMathNode node={mathNode} />
307
+ ))}
308
+ </span>
309
+ )
310
+ }
311
+ </div>
312
+ </foreignObject>
313
+ </g>
314
+ );
315
+ })
316
+ }
317
+ </svg>
318
+ </div>
319
+ </div>
320
+ </div>
321
+
322
+ <details class="diagram-fallback">
323
+ <summary>Diagram description</summary>
324
+ {
325
+ description ? (
326
+ <p>{description}</p>
327
+ ) : (
328
+ <Fragment>
329
+ <ol>
330
+ {rendered.map((node) => (
331
+ <li>{fallbackLabel(node.id)}</li>
332
+ ))}
333
+ </ol>
334
+ {edges.length > 0 && (
335
+ <p class="diagram-fallback-flow">
336
+ {edges.map((edge, index) => (
337
+ <Fragment>
338
+ {index > 0 && '; '}
339
+ {fallbackLabel(edge.from)} → {fallbackLabel(edge.to)}
340
+ </Fragment>
196
341
  ))}
197
- </span>
342
+ </p>
198
343
  )}
199
- </div>
200
- </foreignObject>
201
- </g>
202
- );
203
- })
204
- }
205
- </svg>
206
- </div>
344
+ </Fragment>
345
+ )
346
+ }
347
+ </details>
348
+ </div>
349
+ </details>
207
350
 
208
- <details class="diagram-fallback">
209
- <summary>Diagram description</summary>
210
- {
211
- description ? (
212
- <p>{description}</p>
213
- ) : (
214
- <Fragment>
215
- <ol>
216
- {rendered.map((node) => (
217
- <li>{fallbackLabel(node.id)}</li>
218
- ))}
219
- </ol>
220
- {edges.length > 0 && (
221
- <p class="diagram-fallback-flow">
222
- {edges.map((edge, index) => (
223
- <Fragment>
224
- {index > 0 && '; '}
225
- {fallbackLabel(edge.from)} → {fallbackLabel(edge.to)}
226
- </Fragment>
227
- ))}
228
- </p>
229
- )}
230
- </Fragment>
231
- )
232
- }
233
- </details>
351
+ <a
352
+ class="diagram__title-anchor"
353
+ href={`#${renderedAnchorId}`}
354
+ aria-label={`${number ? `Diagram ${number}: ` : ''}${label}`}
355
+ >
356
+ <svg
357
+ aria-hidden="true"
358
+ width="16"
359
+ height="16"
360
+ viewBox="0 0 24 24"
361
+ fill="currentColor"
362
+ ><path
363
+ 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"
364
+ ></path></svg
365
+ >
366
+ </a>
367
+
368
+ <div class="diagram-toolbar" data-diagram-toolbar hidden>
369
+ <button
370
+ type="button"
371
+ class="diagram-expand-button"
372
+ data-diagram-expand
373
+ aria-controls={viewportId}
374
+ aria-expanded="false"
375
+ title="Expand diagram horizontally"
376
+ >
377
+ <svg aria-hidden="true" viewBox="0 0 24 24">
378
+ <path class="diagram-expand-icon" d="M3 12h18M8 7l-5 5 5 5M16 7l5 5-5 5" />
379
+ <path
380
+ class="diagram-shrink-icon"
381
+ d="M3 12h7M7 9l3 3-3 3M21 12h-7M17 9l-3 3 3 3"
382
+ />
383
+ </svg>
384
+ <span class="sr-only">Expand {label}</span>
385
+ </button>
386
+ </div>
387
+ </div>
234
388
  </figure>
235
389
 
236
390
  <style>
237
391
  @layer starlight.components {
238
392
  .diagram {
393
+ position: relative;
239
394
  margin: 1.5rem 0;
395
+ max-width: 100%;
396
+ contain: inline-size;
397
+ }
398
+
399
+ .diagram__titlebar {
400
+ position: relative;
401
+ }
402
+
403
+ .diagram__details {
404
+ border-inline-start: 0;
405
+ padding-inline-start: 0;
240
406
  }
241
407
 
242
- .diagram figcaption {
243
- margin-block-end: 0.5rem;
408
+ .diagram__summary {
409
+ position: relative;
410
+ min-height: 1.75rem;
411
+ padding-inline-end: 4rem;
244
412
  font-weight: 600;
413
+ cursor: pointer;
414
+ }
415
+
416
+ .diagram__summary::before {
417
+ position: absolute;
418
+ top: 50%;
419
+ left: -1.35em;
420
+ margin: 0;
421
+ transform: translateY(-50%);
422
+ opacity: 0;
423
+ transition:
424
+ opacity 150ms ease 700ms,
425
+ transform 150ms ease;
426
+ }
427
+
428
+ .diagram__details[data-state='open'] > .diagram__summary::before {
429
+ transform: translateY(-50%) rotateZ(90deg);
430
+ }
431
+
432
+ .diagram__details[data-state='closed'] > .diagram__summary::before {
433
+ opacity: 1;
434
+ transition: none;
435
+ }
436
+
437
+ .diagram__summary:hover::before,
438
+ .diagram__summary:focus-visible::before {
439
+ opacity: 1;
440
+ transition: opacity 100ms ease;
441
+ }
442
+
443
+ .diagram__body {
444
+ overflow: hidden;
445
+ transition: max-height 220ms ease;
446
+ }
447
+
448
+ .diagram__number {
449
+ color: var(--sl-color-text-accent);
450
+ }
451
+
452
+ .diagram__caption-text::before {
453
+ content: ' — ';
454
+ }
455
+
456
+ .diagram__summary > .diagram__caption-text:first-child::before {
457
+ content: none;
458
+ }
459
+
460
+ .diagram__title-anchor {
461
+ position: absolute;
462
+ top: 0.1rem;
463
+ inset-inline-end: 0;
464
+ display: inline-flex;
465
+ width: 1.5rem;
466
+ height: 1.5rem;
467
+ align-items: center;
468
+ justify-content: center;
469
+ color: var(--sl-color-gray-3);
470
+ text-decoration: none;
471
+ opacity: 1;
472
+ }
473
+
474
+ .diagram[data-diagram-overflow='true'] .diagram__title-anchor {
475
+ inset-inline-end: 2rem;
476
+ }
477
+
478
+ .diagram__title-anchor svg {
479
+ width: 0.9rem;
480
+ height: 0.9rem;
481
+ }
482
+
483
+ .diagram__title-anchor:hover,
484
+ .diagram__title-anchor:focus-visible {
485
+ color: var(--sl-color-text-accent);
486
+ }
487
+
488
+ .diagram__title-anchor:focus-visible {
489
+ outline: 2px solid var(--sl-color-accent);
490
+ outline-offset: 0.1rem;
491
+ }
492
+
493
+ @media (hover: hover) {
494
+ .diagram__title-anchor {
495
+ opacity: 0;
496
+ }
497
+
498
+ .diagram__details:has(> .diagram__summary:hover) ~ .diagram__title-anchor,
499
+ .diagram__details:has(> .diagram__summary:focus-visible)
500
+ ~ .diagram__title-anchor,
501
+ .diagram__title-anchor:hover,
502
+ .diagram__title-anchor:focus-visible {
503
+ opacity: 1;
504
+ }
505
+ }
506
+
507
+ .diagram-stage {
508
+ min-width: 0;
509
+ max-width: 100%;
510
+ overflow: hidden;
511
+ }
512
+
513
+ .diagram-toolbar {
514
+ position: absolute;
515
+ z-index: 2;
516
+ top: -0.25rem;
517
+ inset-inline-end: 0;
518
+ }
519
+
520
+ .diagram-toolbar[hidden] {
521
+ display: none;
522
+ }
523
+
524
+ .diagram-expand-button {
525
+ display: inline-flex;
526
+ width: 1.75rem;
527
+ height: 1.75rem;
528
+ margin: 0;
529
+ align-items: center;
530
+ justify-content: center;
531
+ color: var(--sl-color-gray-2);
532
+ border: 0;
533
+ border-radius: 0.25rem;
534
+ background: transparent;
535
+ cursor: pointer;
536
+ }
537
+
538
+ .diagram-expand-button svg {
539
+ width: 1.1rem;
540
+ height: 1.1rem;
541
+ fill: none;
542
+ stroke: currentColor;
543
+ stroke-width: 1.8;
544
+ stroke-linecap: round;
545
+ stroke-linejoin: round;
546
+ }
547
+
548
+ .diagram-expand-button[aria-expanded='false'] .diagram-shrink-icon,
549
+ .diagram-expand-button[aria-expanded='true'] .diagram-expand-icon {
550
+ display: none;
551
+ }
552
+
553
+ .diagram-expand-button:hover {
554
+ color: var(--sl-color-text);
555
+ background: var(--sl-color-accent-low);
556
+ }
557
+
558
+ .diagram-expand-button:focus-visible {
559
+ outline: 2px solid var(--sl-color-accent);
560
+ outline-offset: 1px;
245
561
  }
246
562
 
247
563
  /* A wide graph scrolls inside its own box rather than shrinking to
@@ -250,6 +566,10 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
250
566
  that: a scrollable region a keyboard user cannot reach is unreachable
251
567
  content, not just an awkward one. */
252
568
  .diagram-scroll {
569
+ width: 100%;
570
+ min-width: 0;
571
+ max-width: 100%;
572
+ flex: 1 1 auto;
253
573
  overflow-x: auto;
254
574
  padding-block: 0.15rem;
255
575
  }
@@ -259,11 +579,27 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
259
579
  outline-offset: 2px;
260
580
  }
261
581
 
262
- .diagram svg {
582
+ .diagram-canvas > svg {
263
583
  display: block;
584
+ /* Starlight globally constrains SVGs to the content width. That is safe
585
+ for an all-SVG illustration, but this diagram embeds HTML/KaTeX in
586
+ `foreignObject` nodes. WebKit on iOS can scale the SVG geometry while
587
+ leaving that HTML at its unscaled size, which puts formulas outside
588
+ their boxes. Keep the build-time dimensions and let the keyboard-
589
+ reachable wrapper above provide the intended horizontal scroll. */
590
+ max-width: none;
264
591
  overflow: visible;
265
592
  }
266
593
 
594
+ .diagram-canvas {
595
+ flex: none;
596
+ }
597
+
598
+ .diagram.dia-ref-highlight {
599
+ outline: 3px solid var(--sl-color-accent);
600
+ outline-offset: 0.25rem;
601
+ }
602
+
267
603
  .diagram-node rect {
268
604
  fill: var(--sl-color-gray-7);
269
605
  stroke: var(--sl-color-gray-5);
@@ -313,5 +649,30 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
313
649
  .diagram-fallback ol {
314
650
  padding-inline-start: 1.25rem;
315
651
  }
652
+
653
+ @media print {
654
+ .diagram-toolbar,
655
+ .diagram__title-anchor,
656
+ .diagram__summary::before {
657
+ display: none;
658
+ }
659
+
660
+ .diagram__number {
661
+ color: inherit;
662
+ }
663
+
664
+ .diagram__body {
665
+ display: block !important;
666
+ max-height: none !important;
667
+ overflow: visible !important;
668
+ }
669
+ }
670
+
671
+ @media (prefers-reduced-motion: reduce) {
672
+ .diagram__summary::before,
673
+ .diagram__body {
674
+ transition: none;
675
+ }
676
+ }
316
677
  }
317
678
  </style>