explico 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +108 -0
  2. package/dist/analytics/AnalyticsEmitter.d.ts +22 -0
  3. package/dist/analytics/AnalyticsEmitter.js +5 -0
  4. package/dist/compiler/cli.d.ts +189 -0
  5. package/dist/compiler/cli.js +552 -0
  6. package/dist/compiler/collections.d.ts +70 -0
  7. package/dist/compiler/collections.js +383 -0
  8. package/dist/compiler/course-config.d.ts +31 -0
  9. package/dist/compiler/course-config.js +0 -0
  10. package/dist/compiler/lesson-derivation.d.ts +89 -0
  11. package/dist/compiler/lesson-derivation.js +91 -0
  12. package/dist/compiler/manifest.d.ts +178 -0
  13. package/dist/compiler/manifest.js +345 -0
  14. package/dist/compiler/resolution-report.d.ts +45 -0
  15. package/dist/compiler/resolution-report.js +97 -0
  16. package/dist/curriculum/validation.d.ts +76 -0
  17. package/dist/curriculum/validation.js +320 -0
  18. package/dist/progress/ProgressRepository.d.ts +53 -0
  19. package/dist/progress/ProgressRepository.js +12 -0
  20. package/dist/reference/base-library.mjs +27 -0
  21. package/dist/reference/citation-format.mjs +133 -0
  22. package/dist/reference/consistency.d.ts +64 -0
  23. package/dist/reference/consistency.js +234 -0
  24. package/dist/reference/curriculum-alignment.d.ts +7 -0
  25. package/dist/reference/curriculum-alignment.js +166 -0
  26. package/dist/reference/equations-validate.d.ts +50 -0
  27. package/dist/reference/equations-validate.js +90 -0
  28. package/dist/reference/equations.mjs +144 -0
  29. package/dist/reference/gate-math.d.ts +41 -0
  30. package/dist/reference/gate-math.js +151 -0
  31. package/dist/reference/katex-options.mjs +91 -0
  32. package/dist/reference/lab-math-scope.d.ts +21 -0
  33. package/dist/reference/lab-math-scope.js +28 -0
  34. package/dist/reference/label.d.ts +11 -0
  35. package/dist/reference/label.js +38 -0
  36. package/dist/reference/lint-ignore.d.ts +32 -0
  37. package/dist/reference/lint-ignore.js +39 -0
  38. package/dist/reference/math-glyphs.mjs +830 -0
  39. package/dist/reference/prose.d.ts +15 -0
  40. package/dist/reference/prose.js +12 -0
  41. package/dist/reference/references.d.ts +17 -0
  42. package/dist/reference/references.js +42 -0
  43. package/dist/reference/registry.d.ts +6 -0
  44. package/dist/reference/registry.js +548 -0
  45. package/dist/reference/rehype-fail-katex-errors.mjs +55 -0
  46. package/dist/reference/remark-citation.mjs +0 -0
  47. package/dist/reference/remark-notation.mjs +753 -0
  48. package/dist/reference/render-lab-math.d.ts +21 -0
  49. package/dist/reference/render-lab-math.js +58 -0
  50. package/dist/reference/types.d.ts +151 -0
  51. package/dist/reference/types.js +0 -0
  52. package/dist/reference/units-vocab.d.ts +26 -0
  53. package/dist/reference/units-vocab.js +119 -0
  54. package/dist/session/PreferenceStore.d.ts +28 -0
  55. package/dist/session/PreferenceStore.js +30 -0
  56. package/dist/session/user-context.d.ts +7 -0
  57. package/dist/session/user-context.js +10 -0
  58. package/dist/session/user.d.ts +21 -0
  59. package/dist/session/user.js +8 -0
  60. package/package.json +53 -0
  61. package/src/analytics/AnalyticsEmitter.ts +23 -0
  62. package/src/compiler/cli.ts +866 -0
  63. package/src/compiler/collections.ts +554 -0
  64. package/src/compiler/course-config.ts +32 -0
  65. package/src/compiler/lesson-derivation.ts +186 -0
  66. package/src/compiler/manifest.ts +601 -0
  67. package/src/compiler/resolution-report.ts +150 -0
  68. package/src/components/CurriculumMap.astro +208 -0
  69. package/src/components/HoverPanel.astro +67 -0
  70. package/src/components/assessments/AssessmentRunner.tsx +320 -0
  71. package/src/components/assessments/AssessmentSet.astro +54 -0
  72. package/src/components/citation/CitationLayer.astro +173 -0
  73. package/src/components/examples/CompactExample.astro +34 -0
  74. package/src/components/examples/CompactExamples.astro +253 -0
  75. package/src/components/hover-panel.ts +273 -0
  76. package/src/components/notation/NotationGlossary.astro +412 -0
  77. package/src/components/notation/NotationLayer.astro +594 -0
  78. package/src/components/notation/NotationMath.astro +24 -0
  79. package/src/components/notation/SafeMathNode.astro +23 -0
  80. package/src/components/notation/render-notation-math.ts +223 -0
  81. package/src/components/starlight/EquationEnhancer.astro +72 -0
  82. package/src/components/starlight/LayoutHeader.astro +102 -0
  83. package/src/components/starlight/LayoutPageSidebar.astro +33 -0
  84. package/src/components/starlight/LayoutSidebar.astro +29 -0
  85. package/src/components/starlight/LessonFooter.astro +58 -0
  86. package/src/content-config.ts +259 -0
  87. package/src/curriculum/validation.ts +481 -0
  88. package/src/progress/ProgressRepository.ts +64 -0
  89. package/src/reference/base-library.mjs +27 -0
  90. package/src/reference/citation-format.mjs +133 -0
  91. package/src/reference/consistency.ts +375 -0
  92. package/src/reference/curriculum-alignment.ts +228 -0
  93. package/src/reference/equations-validate.ts +167 -0
  94. package/src/reference/equations.mjs +144 -0
  95. package/src/reference/gate-math.ts +250 -0
  96. package/src/reference/katex-options.mjs +91 -0
  97. package/src/reference/lab-math-scope.ts +54 -0
  98. package/src/reference/label.ts +47 -0
  99. package/src/reference/lint-ignore.ts +75 -0
  100. package/src/reference/math-glyphs.mjs +830 -0
  101. package/src/reference/prose.ts +21 -0
  102. package/src/reference/references.ts +82 -0
  103. package/src/reference/registry.ts +723 -0
  104. package/src/reference/rehype-fail-katex-errors.mjs +55 -0
  105. package/src/reference/remark-citation.mjs +0 -0
  106. package/src/reference/remark-notation.mjs +753 -0
  107. package/src/reference/render-lab-math.ts +99 -0
  108. package/src/reference/types.ts +182 -0
  109. package/src/reference/units-vocab.ts +143 -0
  110. package/src/session/PreferenceStore.ts +54 -0
  111. package/src/session/user-context.ts +18 -0
  112. package/src/session/user.ts +24 -0
  113. package/src/styles/global.css +763 -0
@@ -0,0 +1,594 @@
1
+ ---
2
+ export type NotationAlignment =
3
+ | {
4
+ kind: 'competency';
5
+ introducedByCompetency: string;
6
+ introducedInLesson: string;
7
+ }
8
+ | { kind: 'general'; rationale: string };
9
+
10
+ export interface NotationLayerDefinition {
11
+ key: string;
12
+ notation: string;
13
+ label: string;
14
+ summary: string;
15
+ formula?: string;
16
+ units?: string;
17
+ seeAlso?: readonly string[];
18
+ alignment?: NotationAlignment;
19
+ editorialStatus?: 'draft' | 'in-review' | 'reviewed';
20
+ scope?: 'local' | 'shared';
21
+ href?: string;
22
+ }
23
+
24
+ export interface Props {
25
+ definitions: NotationLayerDefinition[];
26
+ id?: string;
27
+ label?: string;
28
+ glossaryHref?: string;
29
+ /**
30
+ * `'full'` (default) renders the visible "Notation used on this page"
31
+ * disclosure plus the hover panel. `'panel'` renders only the hover panel
32
+ * and keeps the entry data as a hidden source, for pages that already show
33
+ * every definition in full (the glossary) but still want marked `[[key]]`
34
+ * links to reveal an explanation on hover.
35
+ */
36
+ presentation?: 'full' | 'panel';
37
+ }
38
+
39
+ import NotationMath from './NotationMath.astro';
40
+ import HoverPanel from '../HoverPanel.astro';
41
+
42
+ const {
43
+ definitions,
44
+ id = 'page-notation',
45
+ label = 'Notation used on this page',
46
+ glossaryHref = `${import.meta.env.BASE_URL.replace(/\/$/, '')}/glossary/`,
47
+ presentation = 'full',
48
+ } = Astro.props;
49
+
50
+ const panelOnly = presentation === 'panel';
51
+
52
+ const keyPattern = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
53
+ const idPattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
54
+
55
+ if (!idPattern.test(id)) {
56
+ throw new Error(`Unsafe NotationLayer id: ${id}`);
57
+ }
58
+
59
+ const seen = new Set<string>();
60
+ const items = definitions.map((definition) => {
61
+ if (!keyPattern.test(definition.key)) {
62
+ throw new Error(`Unsafe notation key: ${definition.key}`);
63
+ }
64
+ if (seen.has(definition.key)) {
65
+ throw new Error(
66
+ `Duplicate notation key in NotationLayer: ${definition.key}`,
67
+ );
68
+ }
69
+ seen.add(definition.key);
70
+
71
+ const scope = definition.scope ?? 'shared';
72
+ const href =
73
+ definition.href ??
74
+ (scope === 'local'
75
+ ? `#notation-${definition.key}`
76
+ : `${glossaryHref}#notation-${definition.key}`);
77
+
78
+ return { ...definition, scope, href };
79
+ });
80
+
81
+ const panelId = `${id}-panel`;
82
+ ---
83
+
84
+ {
85
+ items.length > 0 && (
86
+ <section
87
+ class:list={['notation-layer', panelOnly && 'notation-layer--panel-only']}
88
+ data-notation-layer
89
+ data-notation-layer-id={id}
90
+ aria-labelledby={panelOnly ? undefined : `${id}-heading`}
91
+ aria-label={panelOnly ? 'Notation explanations' : undefined}
92
+ >
93
+ {!panelOnly && (
94
+ <h2 id={`${id}-heading`} class="notation-layer-heading">
95
+ Page notation
96
+ </h2>
97
+ )}
98
+
99
+ <details class="notation-list" hidden={panelOnly}>
100
+ <summary>
101
+ {label} <span class="notation-count">({items.length})</span>
102
+ </summary>
103
+ <dl>
104
+ {items.map((definition) => (
105
+ <div
106
+ class="notation-entry"
107
+ id={panelOnly ? undefined : `notation-${definition.key}`}
108
+ data-notation-entry={definition.key}
109
+ data-notation-href={definition.href}
110
+ >
111
+ <dt>
112
+ <span
113
+ class="notation-symbol-trigger"
114
+ data-notation-symbol-trigger
115
+ data-notation-key={definition.key}
116
+ >
117
+ <NotationMath
118
+ class="notation-symbol"
119
+ latex={definition.notation}
120
+ />
121
+ </span>
122
+ <a
123
+ href={definition.href}
124
+ data-notation-link={definition.key}
125
+ aria-label={`${definition.label}: open the full definition`}
126
+ >
127
+ {definition.label}
128
+ </a>
129
+ {definition.editorialStatus &&
130
+ definition.editorialStatus !== 'reviewed' && (
131
+ <span class="notation-status">
132
+ {definition.editorialStatus}
133
+ </span>
134
+ )}
135
+ </dt>
136
+ <dd>
137
+ <p data-notation-summary>{definition.summary}</p>
138
+ {definition.formula && (
139
+ <span class="notation-formula" data-notation-formula>
140
+ <NotationMath latex={definition.formula} />
141
+ </span>
142
+ )}
143
+ {definition.units && (
144
+ <p class="notation-units" data-notation-units>
145
+ <strong>Units:</strong> {definition.units}
146
+ </p>
147
+ )}
148
+ <button
149
+ class="notation-pin"
150
+ type="button"
151
+ data-notation-control={definition.key}
152
+ aria-controls={panelId}
153
+ aria-expanded="false"
154
+ aria-pressed="false"
155
+ hidden
156
+ >
157
+ Pin explanation
158
+ </button>
159
+ </dd>
160
+ </div>
161
+ ))}
162
+ </dl>
163
+ </details>
164
+
165
+ {!panelOnly && (
166
+ <p class="notation-enhancement-help" data-notation-help hidden>
167
+ Point to a marked symbol for a quick explanation, or use the list
168
+ above. Select “Pin explanation” to keep one open; press Escape to
169
+ close it.
170
+ </p>
171
+ )}
172
+
173
+ <HoverPanel id={panelId} label="Notation explanation" data-notation-panel>
174
+ <div class="notation-panel-body">
175
+ <header class="notation-panel-heading">
176
+ <span class="notation-panel-symbol" data-panel-symbol />
177
+ <h3 data-panel-title />
178
+ </header>
179
+ <p data-panel-summary />
180
+ <div data-panel-formula hidden />
181
+ <p data-panel-units hidden />
182
+ <a data-panel-link>Open the full definition</a>
183
+ </div>
184
+ </HoverPanel>
185
+ </section>
186
+ )
187
+ }
188
+
189
+ <script>
190
+ import { createHoverPanel, closestElement } from '../hover-panel';
191
+
192
+ const triggerSelector = '[data-notation-key]';
193
+
194
+ function setOptionalText(element: HTMLElement, value: string) {
195
+ element.textContent = value;
196
+ element.hidden = value.length === 0;
197
+ }
198
+
199
+ function setupNotationLayer(root: HTMLElement) {
200
+ if (root.dataset.notationEnhanced === 'true') return;
201
+ root.dataset.notationEnhanced = 'true';
202
+
203
+ const entryElements = Array.from(
204
+ root.querySelectorAll<HTMLElement>('[data-notation-entry]'),
205
+ );
206
+ const entries = new Map(
207
+ entryElements.map((entry) => [entry.dataset.notationEntry ?? '', entry]),
208
+ );
209
+ entries.delete('');
210
+ if (entries.size === 0) return;
211
+
212
+ const panel = root.querySelector<HTMLElement>('[data-notation-panel]');
213
+ const panelSymbol = root.querySelector<HTMLElement>('[data-panel-symbol]');
214
+ const panelTitle = root.querySelector<HTMLElement>('[data-panel-title]');
215
+ const panelSummary = root.querySelector<HTMLElement>(
216
+ '[data-panel-summary]',
217
+ );
218
+ const panelFormula = root.querySelector<HTMLElement>(
219
+ '[data-panel-formula]',
220
+ );
221
+ const panelUnits = root.querySelector<HTMLElement>('[data-panel-units]');
222
+ const panelLink =
223
+ root.querySelector<HTMLAnchorElement>('[data-panel-link]');
224
+ if (
225
+ !panel ||
226
+ !panelSymbol ||
227
+ !panelTitle ||
228
+ !panelSummary ||
229
+ !panelFormula ||
230
+ !panelUnits ||
231
+ !panelLink
232
+ ) {
233
+ return;
234
+ }
235
+
236
+ const controls = Array.from(
237
+ root.querySelectorAll<HTMLButtonElement>('[data-notation-control]'),
238
+ );
239
+ const triggers = Array.from(
240
+ document.querySelectorAll<HTMLElement>(triggerSelector),
241
+ ).filter(
242
+ (element) =>
243
+ !root.contains(element) &&
244
+ !element.closest('.katex-mathml') &&
245
+ entries.has(element.dataset.notationKey ?? ''),
246
+ );
247
+ const triggerSet = new Set(triggers);
248
+
249
+ // Symbol glyphs rendered inside the page-notation list. They live within
250
+ // `root`, so the external-marker filter above deliberately excludes them;
251
+ // wire them as their own trigger set so pointing at a listed symbol opens
252
+ // the same explanation as pointing at one embedded in prose or an equation.
253
+ const symbolTriggers = Array.from(
254
+ root.querySelectorAll<HTMLElement>('[data-notation-symbol-trigger]'),
255
+ ).filter((element) => entries.has(element.dataset.notationKey ?? ''));
256
+ const symbolTriggerSet = new Set(symbolTriggers);
257
+ const highlightTargets = [...triggers, ...symbolTriggers];
258
+
259
+ for (const trigger of highlightTargets) {
260
+ trigger.classList.add('notation-trigger');
261
+ }
262
+ for (const control of controls) control.hidden = false;
263
+ const help = root.querySelector<HTMLElement>('[data-notation-help]');
264
+ if (help) help.hidden = false;
265
+
266
+ const keyFor = (element: HTMLElement | null) =>
267
+ element?.dataset.notationKey ??
268
+ element?.dataset.notationControl ??
269
+ element?.dataset.notationLink ??
270
+ null;
271
+
272
+ const externalTriggerFrom = (node: EventTarget | null) => {
273
+ const candidate = closestElement(node, triggerSelector);
274
+ return candidate && triggerSet.has(candidate) ? candidate : null;
275
+ };
276
+
277
+ const symbolTriggerFrom = (node: EventTarget | null) => {
278
+ const candidate = closestElement(node, '[data-notation-symbol-trigger]');
279
+ return candidate && symbolTriggerSet.has(candidate) ? candidate : null;
280
+ };
281
+
282
+ const controlFrom = (node: EventTarget | null) => {
283
+ const candidate = closestElement(node, '[data-notation-control]');
284
+ return candidate && root.contains(candidate) ? candidate : null;
285
+ };
286
+
287
+ const staticLinkFrom = (node: EventTarget | null) => {
288
+ const candidate = closestElement(node, '[data-notation-link]');
289
+ return candidate && root.contains(candidate) ? candidate : null;
290
+ };
291
+
292
+ const triggerFrom = (node: EventTarget | null) =>
293
+ controlFrom(node) ??
294
+ staticLinkFrom(node) ??
295
+ externalTriggerFrom(node) ??
296
+ symbolTriggerFrom(node);
297
+
298
+ const render = (_anchor: HTMLElement, key: string) => {
299
+ const entry = entries.get(key);
300
+ if (!entry) return false;
301
+
302
+ const sourceSymbol = entry.querySelector<HTMLElement>('.notation-symbol');
303
+ panelSymbol.replaceChildren(
304
+ ...(sourceSymbol
305
+ ? [sourceSymbol.cloneNode(true)]
306
+ : [document.createTextNode(entry.dataset.notationEntry ?? key)]),
307
+ );
308
+ panelTitle.textContent =
309
+ entry
310
+ .querySelector<HTMLAnchorElement>('[data-notation-link]')
311
+ ?.textContent?.trim() ?? key;
312
+ panelSummary.textContent =
313
+ entry
314
+ .querySelector<HTMLElement>('[data-notation-summary]')
315
+ ?.textContent?.trim() ?? '';
316
+ const sourceFormula = entry.querySelector<HTMLElement>(
317
+ '[data-notation-formula]',
318
+ );
319
+ panelFormula.replaceChildren(
320
+ ...(sourceFormula ? [sourceFormula.cloneNode(true)] : []),
321
+ );
322
+ panelFormula.hidden = sourceFormula === null;
323
+ setOptionalText(
324
+ panelUnits,
325
+ entry
326
+ .querySelector<HTMLElement>('[data-notation-units]')
327
+ ?.textContent?.trim() ?? '',
328
+ );
329
+ panelLink.href = entry.dataset.notationHref ?? `#notation-${key}`;
330
+ return true;
331
+ };
332
+
333
+ const updateControls = (pinnedKey: string | null, visible: boolean) => {
334
+ for (const control of controls) {
335
+ const selected = control.dataset.notationControl === pinnedKey;
336
+ control.setAttribute('aria-pressed', String(selected));
337
+ control.setAttribute('aria-expanded', String(selected && visible));
338
+ control.textContent = selected
339
+ ? 'Unpin explanation'
340
+ : 'Pin explanation';
341
+ }
342
+ };
343
+
344
+ createHoverPanel({
345
+ panel,
346
+ root,
347
+ triggerFrom,
348
+ keyFor,
349
+ render,
350
+ pinControlFrom: controlFrom,
351
+ onShow: (key) => {
352
+ for (const trigger of highlightTargets) {
353
+ trigger.classList.toggle(
354
+ 'is-notation-active',
355
+ trigger.dataset.notationKey === key,
356
+ );
357
+ }
358
+ panel.dataset.activeNotationKey = key;
359
+ },
360
+ onClose: () => {
361
+ delete panel.dataset.activeNotationKey;
362
+ for (const trigger of highlightTargets) {
363
+ trigger.classList.remove('is-notation-active');
364
+ }
365
+ },
366
+ onSync: ({ pinnedKey, visible }) => updateControls(pinnedKey, visible),
367
+ });
368
+ }
369
+
370
+ for (const root of document.querySelectorAll<HTMLElement>(
371
+ '[data-notation-layer]',
372
+ )) {
373
+ setupNotationLayer(root);
374
+ }
375
+ </script>
376
+
377
+ <style>
378
+ @layer starlight.components {
379
+ .notation-layer {
380
+ margin-block: 2.5rem 1rem;
381
+ color: var(--sl-color-text);
382
+ }
383
+
384
+ /* Panel-only mode contributes just the fixed-position explanation panel;
385
+ the section itself must take no layout space. */
386
+ .notation-layer--panel-only {
387
+ margin: 0;
388
+ }
389
+
390
+ .notation-layer-heading {
391
+ margin: 0 0 0.65rem;
392
+ font-size: var(--sl-text-lg);
393
+ }
394
+
395
+ .notation-list {
396
+ border: 1px solid var(--sl-color-gray-5);
397
+ border-radius: 0.6rem;
398
+ background: var(--sl-color-gray-7);
399
+ }
400
+
401
+ .notation-list > summary {
402
+ padding: 0.8rem 1rem;
403
+ cursor: pointer;
404
+ font-weight: 600;
405
+ }
406
+
407
+ .notation-count,
408
+ .notation-enhancement-help {
409
+ color: var(--sl-color-gray-3);
410
+ font-weight: 400;
411
+ }
412
+
413
+ .notation-list dl {
414
+ display: grid;
415
+ gap: 0;
416
+ margin: 0;
417
+ padding: 0 1rem 1rem;
418
+ }
419
+
420
+ .notation-entry {
421
+ display: grid;
422
+ grid-template-columns: minmax(9rem, 0.65fr) minmax(12rem, 1.35fr);
423
+ gap: 0.5rem 1rem;
424
+ padding-block: 0.85rem;
425
+ border-top: 1px solid var(--sl-color-gray-5);
426
+ }
427
+
428
+ .notation-entry dt,
429
+ .notation-entry dd,
430
+ .notation-entry p {
431
+ margin: 0;
432
+ }
433
+
434
+ .notation-entry dt {
435
+ display: flex;
436
+ flex-wrap: wrap;
437
+ align-items: baseline;
438
+ gap: 0.35rem 0.65rem;
439
+ font-weight: 600;
440
+ }
441
+
442
+ .notation-entry dd {
443
+ display: grid;
444
+ gap: 0.45rem;
445
+ color: var(--sl-color-gray-2);
446
+ }
447
+
448
+ .notation-symbol {
449
+ min-width: 2.25rem;
450
+ color: var(--sl-color-accent-high);
451
+ }
452
+
453
+ .notation-symbol-trigger {
454
+ border-radius: 0.16em;
455
+ cursor: help;
456
+ }
457
+
458
+ .notation-symbol-trigger:hover,
459
+ .notation-symbol-trigger.is-notation-active {
460
+ background: var(--sl-color-accent-low);
461
+ box-shadow: 0 0 0 0.12em var(--sl-color-accent-low);
462
+ }
463
+
464
+ @media (forced-colors: active) {
465
+ .notation-symbol-trigger:hover,
466
+ .notation-symbol-trigger.is-notation-active {
467
+ outline: 2px solid Highlight;
468
+ color: HighlightText;
469
+ background: Highlight;
470
+ box-shadow: none;
471
+ }
472
+ }
473
+
474
+ .notation-status {
475
+ padding: 0.08rem 0.35rem;
476
+ border: 1px solid var(--sl-color-gray-5);
477
+ border-radius: 999px;
478
+ color: var(--sl-color-gray-3);
479
+ font-size: var(--sl-text-xs);
480
+ font-weight: 500;
481
+ }
482
+
483
+ .notation-formula,
484
+ .notation-units {
485
+ font-size: var(--sl-text-sm);
486
+ }
487
+
488
+ .notation-formula {
489
+ overflow-x: auto;
490
+ padding-block: 0.15rem;
491
+ color: var(--sl-color-text);
492
+ }
493
+
494
+ .notation-pin {
495
+ justify-self: start;
496
+ padding: 0.25rem 0.55rem;
497
+ border: 1px solid var(--sl-color-gray-5);
498
+ border-radius: 0.35rem;
499
+ color: var(--sl-color-text-accent);
500
+ background: var(--sl-color-bg);
501
+ font: inherit;
502
+ font-size: var(--sl-text-sm);
503
+ cursor: pointer;
504
+ }
505
+
506
+ .notation-pin:hover,
507
+ .notation-pin[aria-pressed='true'] {
508
+ border-color: var(--sl-color-accent);
509
+ background: var(--sl-color-accent-low);
510
+ }
511
+
512
+ .notation-enhancement-help {
513
+ margin: 0.55rem 0 0;
514
+ font-size: var(--sl-text-xs);
515
+ }
516
+
517
+ /* The fixed-position shell is `HoverPanel`; these rules style only the
518
+ notation-owned fields slotted into it. */
519
+ .notation-panel-heading {
520
+ display: flex;
521
+ align-items: baseline;
522
+ gap: 0.7rem;
523
+ }
524
+
525
+ .notation-panel-heading h3,
526
+ .notation-panel-body p {
527
+ margin: 0;
528
+ }
529
+
530
+ .notation-panel-heading h3 {
531
+ font-size: var(--sl-text-base);
532
+ }
533
+
534
+ .notation-panel-body p {
535
+ margin-block: 0.65rem;
536
+ color: var(--sl-color-gray-2);
537
+ font-size: var(--sl-text-sm);
538
+ line-height: 1.5;
539
+ }
540
+
541
+ .notation-panel-body [data-panel-formula] {
542
+ overflow-x: auto;
543
+ margin-block: 0.65rem;
544
+ color: var(--sl-color-text);
545
+ }
546
+
547
+ .notation-panel-symbol {
548
+ color: var(--sl-color-accent-high);
549
+ }
550
+
551
+ .notation-layer :focus-visible {
552
+ outline: 3px solid var(--sl-color-accent);
553
+ outline-offset: 2px;
554
+ }
555
+ }
556
+
557
+ :global(.katex-html [data-notation-key]),
558
+ :global(.notation-term[data-notation-key]) {
559
+ border-radius: 0.16em;
560
+ }
561
+
562
+ /* KaTeX represents vertical-list depth with a guaranteed-empty second
563
+ table row. Because that row is painted last, it can otherwise intercept
564
+ pointer hits meant for visible notation in fractions and subscripts. */
565
+ :global(.katex-html .vlist-t2 > .vlist-r:nth-child(2)) {
566
+ pointer-events: none;
567
+ }
568
+
569
+ :global(.katex-html [data-notation-key]) {
570
+ cursor: help;
571
+ }
572
+
573
+ :global(.katex-html [data-notation-key]:hover),
574
+ :global([data-notation-key].is-notation-active) {
575
+ background: var(--sl-color-accent-low);
576
+ box-shadow: 0 0 0 0.12em var(--sl-color-accent-low);
577
+ }
578
+
579
+ @media (max-width: 40rem) {
580
+ .notation-entry {
581
+ grid-template-columns: 1fr;
582
+ }
583
+ }
584
+
585
+ @media (forced-colors: active) {
586
+ :global(.katex-html [data-notation-key]:hover),
587
+ :global([data-notation-key].is-notation-active) {
588
+ outline: 2px solid Highlight;
589
+ color: HighlightText;
590
+ background: Highlight;
591
+ box-shadow: none;
592
+ }
593
+ }
594
+ </style>
@@ -0,0 +1,24 @@
1
+ ---
2
+ import { renderNotationMath } from './render-notation-math';
3
+ import SafeMathNode from './SafeMathNode.astro';
4
+
5
+ interface Props {
6
+ latex: string;
7
+ class?: string;
8
+ }
9
+
10
+ const { latex, class: className } = Astro.props;
11
+ const nodes = renderNotationMath(latex);
12
+ ---
13
+
14
+ <span class:list={['notation-math', className]} data-notation-math>
15
+ {nodes.map((node) => <SafeMathNode node={node} />)}
16
+ </span>
17
+
18
+ <style>
19
+ .notation-math :global(.katex) {
20
+ /* Compact labels inherit their surrounding text size. KaTeX's own sizing
21
+ classes retain the smaller scale for subscripts and superscripts. */
22
+ font-size: 1em;
23
+ }
24
+ </style>
@@ -0,0 +1,23 @@
1
+ ---
2
+ import type { SafeMathNode as Node } from './render-notation-math';
3
+ import SafeMathNode from './SafeMathNode.astro';
4
+
5
+ interface Props {
6
+ node: Node;
7
+ }
8
+
9
+ const { node } = Astro.props;
10
+ const Element = node.type === 'element' ? node.tagName : undefined;
11
+ ---
12
+
13
+ {
14
+ node.type === 'text'
15
+ ? node.value
16
+ : Element && (
17
+ <Element {...node.properties}>
18
+ {node.children.map((child) => (
19
+ <SafeMathNode node={child} />
20
+ ))}
21
+ </Element>
22
+ )
23
+ }