explico 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +295 -7
- package/README.md +16 -2
- package/dist/compiler/astro.d.ts +13 -2
- package/dist/compiler/astro.js +39 -0
- package/dist/compiler/cli.d.ts +22 -3
- package/dist/compiler/cli.js +71 -10
- package/dist/compiler/course-config.d.ts +31 -1
- package/dist/compiler/course-config.js +7 -0
- package/dist/compiler/exports/standalone-html.js +13 -0
- package/dist/compiler/logging.d.ts +109 -0
- package/dist/compiler/logging.js +107 -0
- package/dist/compiler/manifest.d.ts +16 -4
- package/dist/compiler/manifest.js +7 -3
- package/dist/reference/base-abbreviations.mjs +8 -2
- package/dist/reference/consistency.d.ts +8 -10
- package/dist/reference/consistency.js +3 -47
- package/dist/reference/delimiter-size.d.ts +39 -0
- package/dist/reference/delimiter-size.js +177 -0
- package/dist/reference/diagrams-validate.d.ts +1 -1
- package/dist/reference/equations-validate.d.ts +2 -2
- package/dist/reference/equations-validate.js +1 -1
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/gate-heading-length.d.ts +12 -0
- package/dist/reference/gate-heading-length.js +79 -0
- package/dist/reference/gate-math.d.ts +8 -2
- package/dist/reference/gate-math.js +30 -7
- package/dist/reference/keyed-float-validate.d.ts +2 -2
- package/dist/reference/keyed-float-validate.js +1 -1
- package/dist/reference/math-glyphs.d.mts +12 -0
- package/dist/reference/math-glyphs.mjs +183 -42
- package/dist/reference/references.js +2 -1
- package/dist/reference/registry.js +1 -1
- package/dist/reference/rehype-base-links.d.mts +23 -0
- package/dist/reference/rehype-base-links.mjs +40 -0
- package/dist/reference/remark-abbreviations.mjs +10 -12
- package/dist/reference/remark-citation.d.mts +5 -1
- package/dist/reference/remark-citation.mjs +0 -0
- package/dist/reference/render-lab-math.d.ts +18 -1
- package/dist/reference/render-lab-math.js +10 -3
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +1 -1
- package/docs/VERSIONING.md +24 -4
- package/docs/authoring/README.md +14 -2
- package/docs/authoring/agents.md +7 -3
- package/docs/authoring/components.md +4 -1
- package/docs/authoring/migrating.md +477 -8
- package/docs/authoring/notation.md +194 -7
- package/docs/authoring/prose.md +64 -0
- package/docs/authoring/structure-rules.md +69 -0
- package/docs/authoring/visualizations.md +2 -2
- package/package.json +1 -1
- package/src/compiler/astro.ts +89 -0
- package/src/compiler/cli.ts +144 -12
- package/src/compiler/course-config.ts +40 -0
- package/src/compiler/exports/standalone-html.ts +15 -0
- package/src/compiler/logging.ts +248 -0
- package/src/compiler/manifest.ts +20 -3
- package/src/components/diagram/Diagram.astro +0 -55
- package/src/components/memo/Memo.astro +2 -5
- package/src/components/memo/ShortNote.astro +2 -5
- package/src/components/notation/NotationFormula.astro +199 -10
- package/src/components/notation/NotationGlossary.astro +89 -58
- package/src/components/notation/NotationLayer.astro +238 -156
- package/src/components/scroll-animation.ts +160 -0
- package/src/components/starlight/AnchorScrollEnhancer.astro +102 -0
- package/src/components/starlight/HeadingAnchorEnhancer.astro +2 -4
- package/src/components/starlight/KeyedFloatEnhancer.astro +17 -16
- package/src/components/starlight/LessonFooter.astro +5 -1
- package/src/content-config.ts +16 -1
- package/src/reference/base-abbreviations.mjs +8 -2
- package/src/reference/consistency.ts +10 -66
- package/src/reference/delimiter-size.ts +301 -0
- package/src/reference/diagrams-validate.ts +1 -1
- package/src/reference/equations-validate.ts +3 -3
- package/src/reference/figures-validate.ts +1 -1
- package/src/reference/gate-heading-length.ts +131 -0
- package/src/reference/gate-math.ts +48 -8
- package/src/reference/keyed-float-validate.ts +3 -3
- package/src/reference/math-glyphs.mjs +183 -42
- package/src/reference/references.ts +2 -1
- package/src/reference/registry.ts +1 -1
- package/src/reference/rehype-base-links.mjs +40 -0
- package/src/reference/remark-abbreviations.mjs +10 -12
- package/src/reference/remark-citation.mjs +0 -0
- package/src/reference/render-lab-math.ts +24 -1
- package/src/reference/tables-validate.ts +1 -1
- package/src/reference/types.ts +1 -1
- package/src/styles/global.css +120 -15
|
@@ -70,12 +70,6 @@ export interface Props {
|
|
|
70
70
|
* Set `false` only when horizontal overflow is deliberate. Default: `800`.
|
|
71
71
|
*/
|
|
72
72
|
warnAboveWidth?: number | false;
|
|
73
|
-
/**
|
|
74
|
-
* Prose text alternative for the SVG. When omitted, the fallback is
|
|
75
|
-
* generated from the node labels and the edge chain — readable, but a
|
|
76
|
-
* hand-written sentence usually reads better for a screen-reader user.
|
|
77
|
-
*/
|
|
78
|
-
description?: string;
|
|
79
73
|
}
|
|
80
74
|
|
|
81
75
|
const {
|
|
@@ -85,7 +79,6 @@ const {
|
|
|
85
79
|
edges,
|
|
86
80
|
scope = [],
|
|
87
81
|
direction = 'TB',
|
|
88
|
-
description,
|
|
89
82
|
number,
|
|
90
83
|
anchorId,
|
|
91
84
|
defaultOpen = true,
|
|
@@ -190,12 +183,6 @@ if (warnAboveWidth !== false && layout.width > warnAboveWidth) {
|
|
|
190
183
|
);
|
|
191
184
|
}
|
|
192
185
|
|
|
193
|
-
/** A node's human label for the text fallback — never the internal id. */
|
|
194
|
-
function fallbackLabel(nodeId: string): string {
|
|
195
|
-
const node = nodeById.get(nodeId);
|
|
196
|
-
return node?.text ?? node?.formula ?? nodeId;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
186
|
function pathFrom(points: readonly { x: number; y: number }[]): string {
|
|
200
187
|
if (points.length === 0) return '';
|
|
201
188
|
const [first, ...rest] = points;
|
|
@@ -318,33 +305,6 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
|
|
|
318
305
|
</div>
|
|
319
306
|
</div>
|
|
320
307
|
</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>
|
|
341
|
-
))}
|
|
342
|
-
</p>
|
|
343
|
-
)}
|
|
344
|
-
</Fragment>
|
|
345
|
-
)
|
|
346
|
-
}
|
|
347
|
-
</details>
|
|
348
308
|
</div>
|
|
349
309
|
</details>
|
|
350
310
|
|
|
@@ -635,21 +595,6 @@ function pathFrom(points: readonly { x: number; y: number }[]): string {
|
|
|
635
595
|
fill: var(--sl-color-gray-4);
|
|
636
596
|
}
|
|
637
597
|
|
|
638
|
-
.diagram-fallback {
|
|
639
|
-
margin-block-start: 0.5rem;
|
|
640
|
-
color: var(--sl-color-gray-3);
|
|
641
|
-
font-size: var(--sl-text-sm);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
.diagram-fallback ol,
|
|
645
|
-
.diagram-fallback p {
|
|
646
|
-
margin: 0.35rem 0 0;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
.diagram-fallback ol {
|
|
650
|
-
padding-inline-start: 1.25rem;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
598
|
@media print {
|
|
654
599
|
.diagram-toolbar,
|
|
655
600
|
.diagram__title-anchor,
|
|
@@ -239,8 +239,8 @@ if (!number || !anchorId) {
|
|
|
239
239
|
<script>
|
|
240
240
|
import { attachHoverPeek, closeHoverPeek } from '../disclosure/hover-preview';
|
|
241
241
|
import { wirePersistedDetails } from '../disclosure/persisted-disclosure';
|
|
242
|
+
import { animatedScrollIntoView } from '../scroll-animation';
|
|
242
243
|
|
|
243
|
-
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
244
244
|
let memoPreviewSerial = 0;
|
|
245
245
|
|
|
246
246
|
function cloneMemoBody(body: HTMLElement): HTMLElement {
|
|
@@ -442,10 +442,7 @@ if (!number || !anchorId) {
|
|
|
442
442
|
history.pushState(null, '', `#${details.id}`);
|
|
443
443
|
}
|
|
444
444
|
requestAnimationFrame(() => {
|
|
445
|
-
details
|
|
446
|
-
behavior: reduceMotion.matches ? 'auto' : 'smooth',
|
|
447
|
-
block: 'start',
|
|
448
|
-
});
|
|
445
|
+
animatedScrollIntoView(details, { block: 'start' });
|
|
449
446
|
});
|
|
450
447
|
});
|
|
451
448
|
}
|
|
@@ -116,7 +116,7 @@ if (!anchorId) {
|
|
|
116
116
|
</style>
|
|
117
117
|
|
|
118
118
|
<script>
|
|
119
|
-
|
|
119
|
+
import { animatedScrollIntoView } from '../scroll-animation';
|
|
120
120
|
|
|
121
121
|
document
|
|
122
122
|
.querySelectorAll<HTMLElement>('.short-note:not([data-short-note-wired])')
|
|
@@ -185,10 +185,7 @@ if (!anchorId) {
|
|
|
185
185
|
history.pushState(null, '', `#${note.id}`);
|
|
186
186
|
}
|
|
187
187
|
requestAnimationFrame(() => {
|
|
188
|
-
note
|
|
189
|
-
behavior: reduceMotion.matches ? 'auto' : 'smooth',
|
|
190
|
-
block: 'nearest',
|
|
191
|
-
});
|
|
188
|
+
animatedScrollIntoView(note, { block: 'nearest' });
|
|
192
189
|
});
|
|
193
190
|
};
|
|
194
191
|
|
|
@@ -33,6 +33,10 @@ export interface Props {
|
|
|
33
33
|
glossLabel?: string;
|
|
34
34
|
/** The owning entry's display label, used to name the scrollable region. */
|
|
35
35
|
entryLabel?: string;
|
|
36
|
+
/** `G.<n>` — the glossary's own equation numbering. Omitted renders no number. */
|
|
37
|
+
number?: string;
|
|
38
|
+
/** Same-page anchor the number links to; ignored unless `number` is set. */
|
|
39
|
+
numberHref?: string;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const {
|
|
@@ -41,6 +45,8 @@ const {
|
|
|
41
45
|
glosses = [],
|
|
42
46
|
glossLabel = 'Symbols',
|
|
43
47
|
entryLabel,
|
|
48
|
+
number,
|
|
49
|
+
numberHref,
|
|
44
50
|
} = Astro.props;
|
|
45
51
|
const { nodes } = renderNotationFormula(formula, scope);
|
|
46
52
|
registerPageNotationDependencies(
|
|
@@ -48,20 +54,49 @@ registerPageNotationDependencies(
|
|
|
48
54
|
notationKeysInMathNodes(nodes),
|
|
49
55
|
`NotationFormula formula "${formula}"`,
|
|
50
56
|
);
|
|
57
|
+
const latex = stripNotationMarkers(formula);
|
|
58
|
+
const numbered = Boolean(number && numberHref);
|
|
51
59
|
---
|
|
52
60
|
|
|
53
61
|
<div class="notation-formula-block">
|
|
54
62
|
<div
|
|
55
|
-
class=
|
|
63
|
+
class:list={['notation-formula-math', numbered && 'notation-formula-math--numbered']}
|
|
56
64
|
data-notation-formula
|
|
57
|
-
data-latex={
|
|
65
|
+
data-latex={latex}
|
|
58
66
|
role="group"
|
|
59
67
|
aria-label={entryLabel ? `${entryLabel} formula` : 'Formula'}
|
|
60
|
-
tabindex="0"
|
|
61
68
|
>
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
<div class="notation-formula-scroll" tabindex="0">
|
|
70
|
+
<span class="notation-math">
|
|
71
|
+
{nodes.map((node) => <SafeMathNode node={node} />)}
|
|
72
|
+
</span>
|
|
73
|
+
</div>
|
|
74
|
+
{numbered && (
|
|
75
|
+
<div class="notation-formula-controls">
|
|
76
|
+
<a
|
|
77
|
+
class="notation-formula-number"
|
|
78
|
+
href={numberHref}
|
|
79
|
+
aria-label={`Equation ${number}`}
|
|
80
|
+
title="Anchor to this equation"
|
|
81
|
+
>
|
|
82
|
+
({number})
|
|
83
|
+
</a>
|
|
84
|
+
<button
|
|
85
|
+
class="notation-formula-copy"
|
|
86
|
+
type="button"
|
|
87
|
+
data-notation-formula-copy
|
|
88
|
+
data-latex={latex}
|
|
89
|
+
aria-label={`Copy LaTeX for Equation ${number}`}
|
|
90
|
+
title="Copy LaTeX"
|
|
91
|
+
>
|
|
92
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
93
|
+
<rect x="9" y="9" width="10" height="10" rx="1"></rect>
|
|
94
|
+
<path d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"
|
|
95
|
+
></path>
|
|
96
|
+
</svg>
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
65
100
|
</div>
|
|
66
101
|
|
|
67
102
|
{
|
|
@@ -95,25 +130,179 @@ registerPageNotationDependencies(
|
|
|
95
130
|
gap: 0.5rem;
|
|
96
131
|
}
|
|
97
132
|
|
|
133
|
+
/* The outer box is deliberately never a scroll clip itself — it hosts the
|
|
134
|
+
absolutely-positioned number/copy controls and their "Copied!" flash,
|
|
135
|
+
which a clipping ancestor would cut off. Mirrors `.keyed-equation` in
|
|
136
|
+
`global.css`, which splits the same way (its own overflow lives on the
|
|
137
|
+
nested `.katex-display`, not on `.keyed-equation` itself). Its cursor,
|
|
138
|
+
hover highlight, and "Copied!" flash are the generic
|
|
139
|
+
`[data-notation-formula]` rules in `global.css` — shared with every
|
|
140
|
+
other producer of that attribute, not reimplemented here. */
|
|
141
|
+
.notation-formula-math {
|
|
142
|
+
color: var(--sl-color-text);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.notation-formula-math--numbered {
|
|
146
|
+
padding-inline-end: 5.25rem;
|
|
147
|
+
}
|
|
148
|
+
|
|
98
149
|
/* A wide equation scrolls inside its own box, which makes the box a
|
|
99
150
|
scrollable region — and a scrollable region a keyboard user cannot reach
|
|
100
151
|
is unreachable content, not just an awkward one. `tabindex` on the
|
|
101
|
-
|
|
102
|
-
|
|
152
|
+
scrolling element itself (not an ancestor) is the fix, same convention
|
|
153
|
+
as `.table-overflow-scroll` in `global.css`; this is its focus ring. */
|
|
154
|
+
.notation-formula-scroll {
|
|
103
155
|
overflow-x: auto;
|
|
104
156
|
/* `overflow-x: auto` alone makes the block axis compute to `auto` too
|
|
105
157
|
(CSS Overflow §3) — equations must never scroll vertically on their
|
|
106
158
|
own, only sideways. */
|
|
107
159
|
overflow-y: hidden;
|
|
108
160
|
padding-block: 0.15rem;
|
|
109
|
-
color: var(--sl-color-text);
|
|
110
161
|
}
|
|
111
162
|
|
|
112
|
-
.notation-formula-
|
|
163
|
+
.notation-formula-scroll:focus-visible {
|
|
113
164
|
outline: 3px solid var(--sl-color-accent);
|
|
114
165
|
outline-offset: 2px;
|
|
115
166
|
}
|
|
116
167
|
|
|
168
|
+
/* Numbered controls — a glossary entry's own equation number and copy
|
|
169
|
+
button, styled to match `.keyed-equation__controls`/`__number`/`__copy`
|
|
170
|
+
in `global.css` exactly (same values), so a reader sees one consistent
|
|
171
|
+
"numbered equation" affordance across lessons and the glossary. */
|
|
172
|
+
.notation-formula-controls {
|
|
173
|
+
position: absolute;
|
|
174
|
+
inset-inline-end: 0.25rem;
|
|
175
|
+
inset-block-start: 50%;
|
|
176
|
+
transform: translateY(-50%);
|
|
177
|
+
display: inline-flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: 0.4rem;
|
|
180
|
+
/* Starlight's generic prose "sibling spacing" rule adds a margin-top
|
|
181
|
+
to any block-level element that follows another (here, following
|
|
182
|
+
`.notation-formula-scroll`) — harmless for flow content, but it
|
|
183
|
+
shifts an absolutely-positioned box's `top: 50%` off-center, since
|
|
184
|
+
margin still applies to an out-of-flow element's own offset. */
|
|
185
|
+
margin: 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.notation-formula-number {
|
|
189
|
+
font-variant-numeric: tabular-nums;
|
|
190
|
+
font-size: var(--sl-text-sm);
|
|
191
|
+
color: var(--sl-color-text-accent);
|
|
192
|
+
text-decoration: none;
|
|
193
|
+
/* Without this it would inherit the container's `cursor: copy` above
|
|
194
|
+
instead of the link's usual pointer, since an inherited value beats
|
|
195
|
+
the browser's own "auto → hand cursor for a link" special case. */
|
|
196
|
+
cursor: pointer;
|
|
197
|
+
opacity: 0.45;
|
|
198
|
+
transition: opacity 120ms ease-out;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.notation-formula-copy {
|
|
202
|
+
display: inline-flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
justify-content: center;
|
|
205
|
+
width: 1.5rem;
|
|
206
|
+
height: 1.5rem;
|
|
207
|
+
padding: 0;
|
|
208
|
+
border: 1px solid var(--sl-color-gray-5);
|
|
209
|
+
border-radius: 999px;
|
|
210
|
+
color: var(--sl-color-gray-1);
|
|
211
|
+
background: transparent;
|
|
212
|
+
font: inherit;
|
|
213
|
+
cursor: pointer;
|
|
214
|
+
opacity: 0.45;
|
|
215
|
+
transition:
|
|
216
|
+
opacity 120ms ease-out,
|
|
217
|
+
border-color 120ms ease-out,
|
|
218
|
+
color 120ms ease-out;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.notation-formula-copy svg {
|
|
222
|
+
width: 0.85rem;
|
|
223
|
+
height: 0.85rem;
|
|
224
|
+
fill: none;
|
|
225
|
+
stroke: currentColor;
|
|
226
|
+
stroke-width: 1.75;
|
|
227
|
+
stroke-linecap: round;
|
|
228
|
+
stroke-linejoin: round;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@media (hover: none) {
|
|
232
|
+
.notation-formula-number,
|
|
233
|
+
.notation-formula-copy {
|
|
234
|
+
opacity: 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Hovering (or focusing) anywhere in the equation reveals the number and
|
|
239
|
+
copy button together — the "highlight" that signals the whole box is
|
|
240
|
+
interactive, not just its own small controls corner. */
|
|
241
|
+
.notation-formula-math:hover .notation-formula-number,
|
|
242
|
+
.notation-formula-math:focus-within .notation-formula-number,
|
|
243
|
+
.notation-formula-number:focus-visible {
|
|
244
|
+
opacity: 1;
|
|
245
|
+
text-decoration: underline;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.notation-formula-math:hover .notation-formula-copy,
|
|
249
|
+
.notation-formula-math:focus-within .notation-formula-copy,
|
|
250
|
+
.notation-formula-copy:focus-visible {
|
|
251
|
+
opacity: 1;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.notation-formula-copy:hover,
|
|
255
|
+
.notation-formula-copy:focus-visible {
|
|
256
|
+
border-color: var(--sl-color-accent);
|
|
257
|
+
color: var(--sl-color-text-accent);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.notation-formula-copy:focus-visible {
|
|
261
|
+
outline: 2px solid var(--sl-color-accent);
|
|
262
|
+
outline-offset: 2px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* `data-copied` is set briefly by the click handler in
|
|
266
|
+
`NotationLayer.astro` — same "flash a tooltip, then remove the
|
|
267
|
+
attribute" pattern as `.keyed-equation__copy[data-copied]`. */
|
|
268
|
+
.notation-formula-copy[data-copied] {
|
|
269
|
+
opacity: 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.notation-formula-copy[data-copied]::after {
|
|
273
|
+
content: 'Copied!';
|
|
274
|
+
position: absolute;
|
|
275
|
+
inset-block-end: calc(100% + 0.4rem);
|
|
276
|
+
inset-inline-end: 0;
|
|
277
|
+
padding: 0.15rem 0.45rem;
|
|
278
|
+
border-radius: 0.35rem;
|
|
279
|
+
background: var(--sl-color-accent-low);
|
|
280
|
+
color: var(--sl-color-text-accent);
|
|
281
|
+
font-size: var(--sl-text-xs);
|
|
282
|
+
white-space: nowrap;
|
|
283
|
+
pointer-events: none;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* The non-numbered case (a formula inside a hover popup, the "Notation
|
|
287
|
+
used on this page" list, a `Diagram`, or a lab's interactive equation)
|
|
288
|
+
has no controls to flash against, so the box itself flashes instead —
|
|
289
|
+
the generic `[data-notation-formula][data-copied]::after` rule in
|
|
290
|
+
`global.css`, not reimplemented here. */
|
|
291
|
+
|
|
292
|
+
@media (prefers-reduced-motion: reduce) {
|
|
293
|
+
.notation-formula-number,
|
|
294
|
+
.notation-formula-copy {
|
|
295
|
+
transition: none;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@media (forced-colors: active) {
|
|
300
|
+
.notation-formula-number,
|
|
301
|
+
.notation-formula-copy {
|
|
302
|
+
opacity: 1;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
117
306
|
.notation-gloss-list {
|
|
118
307
|
display: flex;
|
|
119
308
|
flex-wrap: wrap;
|
|
@@ -19,6 +19,15 @@ const [notationEntries, lessonEntries, manifest] = await Promise.all([
|
|
|
19
19
|
getCollection('docs', ({ id }) => isLessonSlug(id)),
|
|
20
20
|
loadManifest(),
|
|
21
21
|
]);
|
|
22
|
+
// Starlight's `link-alt` icon — the same glyph its own heading anchors use
|
|
23
|
+
// (`rehype-heading-anchors.mjs`). Reimplemented here rather than reused via
|
|
24
|
+
// `.sl-anchor-link`/`.sl-heading-wrapper` because that CSS assumes a single
|
|
25
|
+
// inline prose heading, not this entry's three-column grid header (symbol,
|
|
26
|
+
// name, editorial status) — the glyph carries over, the layout doesn't, so
|
|
27
|
+
// this file supplies its own reveal styling (below).
|
|
28
|
+
const HEADING_LINK_ICON_PATH =
|
|
29
|
+
'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';
|
|
30
|
+
|
|
22
31
|
const labelOf = (data: { key: string; label?: string }) =>
|
|
23
32
|
resolveLabel(data.key, data.label);
|
|
24
33
|
const notationLabelByKey = new Map(
|
|
@@ -83,6 +92,18 @@ const renderedEntries = await Promise.all(
|
|
|
83
92
|
})),
|
|
84
93
|
);
|
|
85
94
|
|
|
95
|
+
// Every entry with a rigorous `formula` gets its own equation number, `G.<n>`
|
|
96
|
+
// in page order — the glossary's own numbering, distinct from a lesson's
|
|
97
|
+
// chapter-qualified `(2.1)` (`equations-validate.ts`), since a glossary
|
|
98
|
+
// formula has no lesson/part to qualify it against.
|
|
99
|
+
let equationCounter = 0;
|
|
100
|
+
const equationNumberByKey = new Map<string, string>();
|
|
101
|
+
for (const { entry } of renderedEntries) {
|
|
102
|
+
if (!entry.data.formula) continue;
|
|
103
|
+
equationCounter += 1;
|
|
104
|
+
equationNumberByKey.set(entry.data.key, `G.${equationCounter}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
// Drives the hover explanation for `[[key]]` links inside the definition
|
|
87
108
|
// bodies. Every entry is already shown in full on this page, so the layer
|
|
88
109
|
// renders in panel-only mode: no visible list, just the shared panel.
|
|
@@ -138,17 +159,26 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
138
159
|
<NotationMath class="glossary-symbol" latex={entry.data.latex} />
|
|
139
160
|
</span>
|
|
140
161
|
<div>
|
|
141
|
-
<h2>
|
|
162
|
+
<h2>
|
|
163
|
+
<a
|
|
164
|
+
class="glossary-term-link"
|
|
165
|
+
href={`#notation-${entry.data.key}`}
|
|
166
|
+
>
|
|
167
|
+
{labelOf(entry.data)}
|
|
168
|
+
</a>
|
|
169
|
+
<a
|
|
170
|
+
class="glossary-anchor"
|
|
171
|
+
href={`#notation-${entry.data.key}`}
|
|
172
|
+
aria-label={`Link to “${labelOf(entry.data)}”`}
|
|
173
|
+
>
|
|
174
|
+
<span class="glossary-anchor-icon" aria-hidden="true">
|
|
175
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
176
|
+
<path d={HEADING_LINK_ICON_PATH}></path>
|
|
177
|
+
</svg>
|
|
178
|
+
</span>
|
|
179
|
+
</a>
|
|
180
|
+
</h2>
|
|
142
181
|
<code>{entry.data.key}</code>
|
|
143
|
-
<a
|
|
144
|
-
class="glossary-anchor"
|
|
145
|
-
href={`#notation-${entry.data.key}`}
|
|
146
|
-
data-glossary-anchor
|
|
147
|
-
aria-label={`Copy link to “${labelOf(entry.data)}”`}
|
|
148
|
-
title="Copy link to this entry"
|
|
149
|
-
>
|
|
150
|
-
<span aria-hidden="true">#</span>
|
|
151
|
-
</a>
|
|
152
182
|
</div>
|
|
153
183
|
<span class="glossary-status">{entry.data.editorialStatus}</span>
|
|
154
184
|
</header>
|
|
@@ -193,6 +223,8 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
193
223
|
scope={formulaScopeByKey.get(entry.data.key) ?? []}
|
|
194
224
|
glosses={glossesByKey.get(entry.data.key)}
|
|
195
225
|
entryLabel={labelOf(entry.data)}
|
|
226
|
+
number={equationNumberByKey.get(entry.data.key)}
|
|
227
|
+
numberHref={`#notation-${entry.data.key}`}
|
|
196
228
|
/>
|
|
197
229
|
</div>
|
|
198
230
|
)}
|
|
@@ -264,34 +296,6 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
264
296
|
definitions={panelDefinitions}
|
|
265
297
|
/>
|
|
266
298
|
|
|
267
|
-
<script>
|
|
268
|
-
const RESET_MS = 1200;
|
|
269
|
-
|
|
270
|
-
for (const anchor of document.querySelectorAll<HTMLAnchorElement>(
|
|
271
|
-
'[data-glossary-anchor]',
|
|
272
|
-
)) {
|
|
273
|
-
anchor.addEventListener('click', () => {
|
|
274
|
-
const href = anchor.getAttribute('href');
|
|
275
|
-
if (!href || !navigator.clipboard) return;
|
|
276
|
-
|
|
277
|
-
// Keep the native hash navigation (scroll + history) and copy the
|
|
278
|
-
// absolute URL alongside it.
|
|
279
|
-
const url = new URL(href, window.location.href).href;
|
|
280
|
-
navigator.clipboard.writeText(url).then(
|
|
281
|
-
() => {
|
|
282
|
-
anchor.dataset.copied = 'true';
|
|
283
|
-
window.setTimeout(() => {
|
|
284
|
-
delete anchor.dataset.copied;
|
|
285
|
-
}, RESET_MS);
|
|
286
|
-
},
|
|
287
|
-
() => {
|
|
288
|
-
/* Clipboard blocked: the plain anchor link still works. */
|
|
289
|
-
},
|
|
290
|
-
);
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
</script>
|
|
294
|
-
|
|
295
299
|
<style>
|
|
296
300
|
.glossary-index ul {
|
|
297
301
|
display: grid;
|
|
@@ -326,6 +330,17 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
326
330
|
font-size: var(--sl-text-xl);
|
|
327
331
|
}
|
|
328
332
|
|
|
333
|
+
.glossary-term-link {
|
|
334
|
+
color: inherit;
|
|
335
|
+
text-decoration: none;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.glossary-term-link:hover,
|
|
339
|
+
.glossary-term-link:focus-visible {
|
|
340
|
+
color: var(--sl-color-text-accent);
|
|
341
|
+
text-decoration: underline;
|
|
342
|
+
}
|
|
343
|
+
|
|
329
344
|
.glossary-symbol {
|
|
330
345
|
color: var(--sl-color-accent-high);
|
|
331
346
|
font-size: 1.2rem;
|
|
@@ -353,14 +368,22 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
353
368
|
}
|
|
354
369
|
}
|
|
355
370
|
|
|
371
|
+
/* Mirrors the hover/focus-reveal treatment of a content heading's own
|
|
372
|
+
`.sl-anchor-link` (`rehype-heading-anchors.mjs`), reimplemented here
|
|
373
|
+
rather than reused because that class's CSS assumes a single inline
|
|
374
|
+
prose heading, not this entry's grid-based header. */
|
|
356
375
|
.glossary-anchor {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
376
|
+
display: inline-flex;
|
|
377
|
+
align-items: center;
|
|
378
|
+
margin-inline-start: 0.4rem;
|
|
360
379
|
color: var(--sl-color-gray-3);
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
380
|
+
vertical-align: middle;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.glossary-anchor-icon svg {
|
|
384
|
+
width: 0.85em;
|
|
385
|
+
height: 0.85em;
|
|
386
|
+
display: block;
|
|
364
387
|
}
|
|
365
388
|
|
|
366
389
|
.glossary-anchor:hover,
|
|
@@ -368,20 +391,28 @@ const panelDefinitions: NotationLayerDefinition[] = notationEntries.map(
|
|
|
368
391
|
color: var(--sl-color-text-accent);
|
|
369
392
|
}
|
|
370
393
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
394
|
+
@media (hover: hover) {
|
|
395
|
+
.glossary-anchor {
|
|
396
|
+
opacity: 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
h2:hover .glossary-anchor,
|
|
400
|
+
h2:focus-within .glossary-anchor,
|
|
401
|
+
.glossary-anchor:focus-visible {
|
|
402
|
+
opacity: 1;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
407
|
+
.glossary-anchor {
|
|
408
|
+
transition: opacity 120ms ease-out, color 120ms ease-out;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
@media (forced-colors: active) {
|
|
413
|
+
.glossary-anchor {
|
|
414
|
+
opacity: 1;
|
|
415
|
+
}
|
|
385
416
|
}
|
|
386
417
|
|
|
387
418
|
.glossary-status {
|