explico 0.7.0 → 0.11.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 +1409 -1
- package/README.md +60 -15
- package/dist/compiler/astro.d.ts +6 -1
- package/dist/compiler/cli.d.ts +54 -3
- package/dist/compiler/cli.js +80 -6
- package/dist/compiler/collections.d.ts +35 -2
- package/dist/compiler/collections.js +84 -8
- package/dist/compiler/course-config.d.ts +3 -194
- package/dist/compiler/course-config.js +78 -5
- package/dist/compiler/exports/standalone-html.d.ts +6 -1
- package/dist/compiler/knowledge-version.js +1 -1
- package/dist/compiler/lesson-derivation.d.ts +27 -10
- package/dist/compiler/lesson-derivation.js +23 -8
- package/dist/compiler/logging.d.ts +14 -1
- package/dist/compiler/logging.js +6 -2
- package/dist/compiler/manifest.d.ts +204 -31
- package/dist/compiler/manifest.js +270 -55
- package/dist/compiler/resolution-report.d.ts +6 -1
- package/dist/course-config-Dsnri9Tk.d.ts +464 -0
- package/dist/reference/base-accents.d.mts +26 -0
- package/dist/reference/base-accents.mjs +31 -0
- package/dist/reference/base-roman-labels.d.mts +2 -2
- package/dist/reference/base-roman-labels.mjs +2 -2
- package/dist/reference/consistency.d.ts +14 -2
- package/dist/reference/consistency.js +108 -4
- package/dist/reference/delimiter-size.d.ts +21 -11
- package/dist/reference/delimiter-size.js +15 -39
- package/dist/reference/diagrams-validate.d.ts +7 -2
- package/dist/reference/diagrams-validate.js +9 -1
- package/dist/reference/equations-validate.d.ts +8 -5
- package/dist/reference/equations-validate.js +1 -0
- package/dist/reference/equations.d.mts +69 -34
- package/dist/reference/equations.mjs +90 -44
- package/dist/reference/examples-validate.d.ts +31 -0
- package/dist/reference/examples-validate.js +48 -0
- package/dist/reference/figures-validate.d.ts +1 -1
- package/dist/reference/figures-validate.js +4 -1
- package/dist/reference/folds-validate.d.ts +33 -0
- package/dist/reference/folds-validate.js +32 -0
- package/dist/reference/gate-abbreviations.d.ts +3 -1
- package/dist/reference/gate-abbreviations.js +3 -2
- package/dist/reference/gate-course-rules.d.ts +32 -0
- package/dist/reference/gate-course-rules.js +85 -0
- package/dist/reference/gate-heading-length.d.ts +5 -2
- package/dist/reference/gate-heading-length.js +47 -23
- package/dist/reference/gate-heading-structure.d.ts +41 -0
- package/dist/reference/gate-heading-structure.js +113 -0
- package/dist/reference/gate-math-punctuation.d.ts +71 -0
- package/dist/reference/gate-math-punctuation.js +251 -0
- package/dist/reference/gate-math.d.ts +44 -6
- package/dist/reference/gate-math.js +63 -10
- package/dist/reference/gate-part-pages.d.ts +50 -0
- package/dist/reference/gate-part-pages.js +118 -0
- package/dist/reference/gate-prose-notation.d.ts +32 -11
- package/dist/reference/gate-prose-notation.js +30 -8
- package/dist/reference/katex-delimiters.d.mts +15 -0
- package/dist/reference/katex-delimiters.mjs +138 -0
- package/dist/reference/katex-options.mjs +4 -0
- package/dist/reference/keyed-float-validate.d.ts +27 -10
- package/dist/reference/keyed-float-validate.js +15 -1
- package/dist/reference/keyed-jsx-floats.d.mts +11 -5
- package/dist/reference/keyed-jsx-floats.mjs +73 -20
- package/dist/reference/knowledge-checks-validate.d.ts +1 -1
- package/dist/reference/knowledge-checks.d.mts +3 -1
- package/dist/reference/knowledge-checks.mjs +9 -4
- package/dist/reference/lab-math-scope.d.ts +1 -0
- package/dist/reference/lab-math-slot.d.ts +34 -0
- package/dist/reference/lab-math-slot.js +6 -0
- package/dist/reference/math-glyphs.d.mts +80 -4
- package/dist/reference/math-glyphs.mjs +588 -39
- package/dist/reference/page-notation.d.ts +6 -1
- package/dist/reference/raw-floats-validate.js +1 -1
- package/dist/reference/raw-floats.mjs +1 -1
- package/dist/reference/rehype-heading-anchors.d.mts +14 -0
- package/dist/reference/rehype-heading-anchors.mjs +116 -17
- package/dist/reference/rehype-table-header-labels.d.mts +6 -1
- package/dist/reference/rehype-table-header-labels.mjs +21 -3
- package/dist/reference/remark-asides.d.mts +3 -0
- package/dist/reference/remark-asides.mjs +180 -0
- package/dist/reference/remark-memo.d.mts +3 -1
- package/dist/reference/remark-memo.mjs +42 -11
- package/dist/reference/remark-notation.d.mts +60 -10
- package/dist/reference/remark-notation.mjs +387 -36
- package/dist/reference/render-lab-math.d.ts +4 -26
- package/dist/reference/render-lab-math.js +5 -5
- package/dist/reference/sections-validate.d.ts +27 -0
- package/dist/reference/sections-validate.js +41 -0
- package/dist/reference/sections.d.mts +97 -0
- package/dist/reference/sections.mjs +306 -0
- package/dist/reference/tables-validate.d.ts +1 -1
- package/dist/reference/types.d.ts +24 -1
- package/dist/reference/unit-words.d.ts +37 -0
- package/dist/reference/unit-words.js +170 -0
- package/dist/rules/catalog.d.ts +176 -0
- package/dist/rules/catalog.js +983 -0
- package/dist/rules/course-rules.d.ts +115 -0
- package/dist/rules/course-rules.js +292 -0
- package/dist/rules/course-rules.schema.json +138 -0
- package/dist/rules/in-effect.d.ts +53 -0
- package/dist/rules/in-effect.js +142 -0
- package/dist/rules/rules.schema.json +1072 -0
- package/dist/rules/settings.d.ts +3 -0
- package/dist/rules/settings.js +290 -0
- package/dist/rules/waivers.d.ts +53 -0
- package/dist/rules/waivers.js +132 -0
- package/dist/session/PreferenceStore.d.ts +5 -0
- package/dist/session/PreferenceStore.js +9 -0
- package/docs/VERSIONING.md +2 -2
- package/docs/authoring/README.md +232 -40
- package/docs/authoring/agents.md +55 -9
- package/docs/authoring/components.md +178 -22
- package/docs/authoring/migrating.md +1324 -2
- package/docs/authoring/notation.md +616 -344
- package/docs/authoring/prose.md +551 -0
- package/docs/authoring/retired.md +35 -0
- package/docs/authoring/structure-rules.md +422 -40
- package/docs/authoring/structure.md +445 -62
- package/docs/authoring/versioning.md +20 -28
- package/docs/authoring/visualizations.md +142 -20
- package/docs/katex-bracket-scaling.md +191 -0
- package/package.json +3 -2
- package/src/compiler/cli.ts +139 -6
- package/src/compiler/collections.ts +144 -10
- package/src/compiler/course-config.ts +273 -34
- package/src/compiler/knowledge-version.ts +1 -1
- package/src/compiler/lesson-derivation.ts +54 -15
- package/src/compiler/logging.ts +21 -4
- package/src/compiler/manifest.ts +573 -104
- package/src/components/BlockHeading.astro +113 -0
- package/src/components/CourseContents.astro +221 -0
- package/src/components/CurriculumMap.astro +5 -6
- package/src/components/FloatIndex.astro +200 -0
- package/src/components/HoverPanel.astro +431 -79
- package/src/components/PartBridge.astro +27 -0
- package/src/components/PartOverview.astro +401 -0
- package/src/components/PartScope.astro +27 -0
- package/src/components/abbreviation/AbbreviationLayer.astro +49 -51
- package/src/components/assessments/AssessmentRunner.tsx +8 -5
- package/src/components/assessments/AssessmentSet.astro +23 -26
- package/src/components/block-heading.ts +146 -0
- package/src/components/card-arrow.ts +243 -0
- package/src/components/card-drag.ts +631 -0
- package/src/components/card-jump.ts +45 -0
- package/src/components/card-manager.ts +1589 -0
- package/src/components/card-memory.ts +177 -0
- package/src/components/card-placement.ts +504 -0
- package/src/components/card-side.ts +336 -0
- package/src/components/card-travel.ts +197 -0
- package/src/components/citation/CitationLayer.astro +77 -76
- package/src/components/diagram/Diagram.astro +161 -316
- package/src/components/disclosure/course-disclosure.ts +24 -0
- package/src/components/disclosure/hover-preview.ts +122 -42
- package/src/components/disclosure/persisted-disclosure.ts +3 -3
- package/src/components/disclosure/print-as-shown.ts +252 -0
- package/src/components/examples/CompactExample.astro +3 -1
- package/src/components/examples/CompactExamples.astro +101 -94
- package/src/components/figure/Figure.astro +174 -274
- package/src/components/figure/FloatCaption.astro +87 -0
- package/src/components/figure/figure-state.ts +58 -0
- package/src/components/figure/figure-tools.ts +87 -0
- package/src/components/float-name.ts +101 -0
- package/src/components/fold/Fold.astro +125 -0
- package/src/components/fold/fold-id.ts +40 -0
- package/src/components/memo/Memo.astro +162 -231
- package/src/components/memo/ShortNote.astro +6 -6
- package/src/components/notation/NotationFormula.astro +5 -1
- package/src/components/notation/NotationGlossary.astro +24 -14
- package/src/components/notation/NotationLayer.astro +169 -162
- package/src/components/notation/NotationMath.astro +10 -1
- package/src/components/notation/render-notation-math.ts +111 -10
- package/src/components/reference/FloatRefLayer.astro +485 -423
- package/src/components/starlight/AboutPanel.astro +22 -6
- package/src/components/starlight/CollapsibleSections.astro +207 -93
- package/src/components/starlight/DiagramEnhancer.astro +15 -138
- package/src/components/starlight/HeadingAnchorEnhancer.astro +193 -1
- package/src/components/starlight/KeyedFloatEnhancer.astro +100 -9
- package/src/components/starlight/LayoutHeader.astro +298 -19
- package/src/components/starlight/LayoutPageSidebar.astro +99 -12
- package/src/components/starlight/LayoutPageTitle.astro +120 -0
- package/src/components/starlight/LayoutSidebar.astro +229 -11
- package/src/components/starlight/LessonExportActions.astro +53 -72
- package/src/components/starlight/LessonFooter.astro +26 -4
- package/src/components/starlight/section-parts.ts +15 -2
- package/src/components/starlight/section-summary.ts +181 -0
- package/src/components/table/Table.astro +95 -76
- package/src/components/table/TableOverflowExpander.astro +65 -28
- package/src/content-config.ts +40 -4
- package/src/reference/base-accents.mjs +31 -0
- package/src/reference/base-roman-labels.mjs +2 -2
- package/src/reference/consistency.ts +176 -5
- package/src/reference/delimiter-size.ts +40 -61
- package/src/reference/diagrams-validate.ts +17 -1
- package/src/reference/equations-validate.ts +10 -5
- package/src/reference/equations.mjs +90 -44
- package/src/reference/examples-validate.ts +97 -0
- package/src/reference/figures-validate.ts +6 -1
- package/src/reference/folds-validate.ts +65 -0
- package/src/reference/gate-abbreviations.ts +3 -2
- package/src/reference/gate-course-rules.ts +134 -0
- package/src/reference/gate-heading-length.ts +68 -37
- package/src/reference/gate-heading-structure.ts +193 -0
- package/src/reference/gate-math-punctuation.ts +381 -0
- package/src/reference/gate-math.ts +119 -12
- package/src/reference/gate-part-pages.ts +217 -0
- package/src/reference/gate-prose-notation.ts +74 -13
- package/src/reference/katex-delimiters.mjs +138 -0
- package/src/reference/katex-options.mjs +4 -0
- package/src/reference/keyed-float-validate.ts +45 -11
- package/src/reference/keyed-jsx-floats.mjs +73 -20
- package/src/reference/knowledge-checks-validate.ts +1 -1
- package/src/reference/knowledge-checks.mjs +9 -4
- package/src/reference/lab-math-slot.ts +36 -0
- package/src/reference/math-glyphs.mjs +588 -39
- package/src/reference/raw-floats-validate.ts +1 -1
- package/src/reference/raw-floats.mjs +1 -1
- package/src/reference/rehype-heading-anchors.mjs +116 -17
- package/src/reference/rehype-table-header-labels.mjs +21 -3
- package/src/reference/remark-asides.mjs +180 -0
- package/src/reference/remark-memo.mjs +42 -11
- package/src/reference/remark-notation.mjs +387 -36
- package/src/reference/render-lab-math.ts +17 -32
- package/src/reference/sections-validate.ts +81 -0
- package/src/reference/sections.mjs +306 -0
- package/src/reference/tables-validate.ts +2 -1
- package/src/reference/types.ts +24 -0
- package/src/reference/unit-words.ts +233 -0
- package/src/rules/catalog.ts +1298 -0
- package/src/rules/course-rules.ts +456 -0
- package/src/rules/in-effect.ts +235 -0
- package/src/rules/settings.ts +448 -0
- package/src/rules/waivers.ts +249 -0
- package/src/session/PreferenceStore.ts +15 -0
- package/src/styles/global.css +1471 -163
- package/src/styles/surfaces.css +182 -0
- package/src/components/hover-panel.ts +0 -339
- package/src/components/popup-stack.ts +0 -345
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* A part's own page: the chapters it holds, in reading order, beside the
|
|
4
|
+
* dependency rail that says where the part sits in the course.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is read from the compiler manifest and the competency
|
|
7
|
+
* collection — chapter order, titles, descriptions, float counts, and which
|
|
8
|
+
* parts this one builds on or feeds. A course authors the page's prose; it
|
|
9
|
+
* never re-types the list, so a renamed chapter or a new prerequisite cannot
|
|
10
|
+
* leave a stale copy behind on the part page.
|
|
11
|
+
*
|
|
12
|
+
* Usage, in `content/docs/<part>/index.mdx`:
|
|
13
|
+
*
|
|
14
|
+
* ```mdx
|
|
15
|
+
* ---
|
|
16
|
+
* title: 'L1 · Foundations & ANOVA'
|
|
17
|
+
* description: Where the course fixes its prediction target.
|
|
18
|
+
* ---
|
|
19
|
+
*
|
|
20
|
+
* import PartOverview from 'explico/components/PartOverview.astro';
|
|
21
|
+
* import PartScope from 'explico/components/PartScope.astro';
|
|
22
|
+
*
|
|
23
|
+
* <PartScope>
|
|
24
|
+
*
|
|
25
|
+
* Authored introduction — what this part is for. Every part after the first
|
|
26
|
+
* also opens with a `<PartBridge>` paragraph (rule S2).
|
|
27
|
+
*
|
|
28
|
+
* </PartScope>
|
|
29
|
+
*
|
|
30
|
+
* <PartOverview
|
|
31
|
+
* chain={[
|
|
32
|
+
* 'The conditional mean minimises squared error',
|
|
33
|
+
* 'Its residual is orthogonal to every function of X',
|
|
34
|
+
* 'So total variance splits between and within',
|
|
35
|
+
* ]}
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
import { getCollection } from 'astro:content';
|
|
40
|
+
import { loadManifest, type ManifestPart } from '../compiler/manifest';
|
|
41
|
+
|
|
42
|
+
interface Props {
|
|
43
|
+
/**
|
|
44
|
+
* The part directory. Defaults to the directory of the page being rendered,
|
|
45
|
+
* which is what a part's own `index.mdx` wants.
|
|
46
|
+
*/
|
|
47
|
+
readonly part?: string;
|
|
48
|
+
/**
|
|
49
|
+
* The part's argument in order, one step per line — the thread that runs
|
|
50
|
+
* through its chapters. Authored, because no manifest field knows why one
|
|
51
|
+
* chapter follows another.
|
|
52
|
+
*/
|
|
53
|
+
readonly chain?: readonly string[];
|
|
54
|
+
/** Set `false` to drop the rail and keep only the reading list. */
|
|
55
|
+
readonly rail?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { part: partProp, chain = [], rail = true } = Astro.props;
|
|
59
|
+
|
|
60
|
+
const routeSlug = Astro.locals.starlightRoute.entry.id;
|
|
61
|
+
const partDir = partProp ?? routeSlug.replace(/\/index$/, '').split('/')[0];
|
|
62
|
+
|
|
63
|
+
const [manifest, competencyEntries] = await Promise.all([
|
|
64
|
+
loadManifest(),
|
|
65
|
+
getCollection('competencies'),
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
const part = manifest.parts.find((entry) => entry.dir === partDir);
|
|
69
|
+
if (!part) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`<PartOverview> found no part "${partDir}". Parts come from ` +
|
|
72
|
+
'`courseConfig.parts`; pass `part="<dir>"` when rendering this ' +
|
|
73
|
+
'component outside a part directory.',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
78
|
+
const lessonById = new Map(
|
|
79
|
+
manifest.lessons.map((lesson) => [lesson.id, lesson]),
|
|
80
|
+
);
|
|
81
|
+
const chapters = part.lessons
|
|
82
|
+
.map((id) => lessonById.get(id))
|
|
83
|
+
.filter((lesson): lesson is NonNullable<typeof lesson> => lesson !== undefined);
|
|
84
|
+
|
|
85
|
+
const competencyById = new Map(
|
|
86
|
+
competencyEntries.map(({ data }) => [data.id, data]),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const taught = new Set(chapters.flatMap((lesson) => lesson.teaches));
|
|
90
|
+
const required = new Set(
|
|
91
|
+
chapters.flatMap((lesson) => lesson.requires).filter((id) => !taught.has(id)),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
/** The part each competency is taught in — how a rail edge finds its page. */
|
|
95
|
+
const partByCompetency = new Map<string, ManifestPart>();
|
|
96
|
+
for (const other of manifest.parts) {
|
|
97
|
+
for (const lessonId of other.lessons) {
|
|
98
|
+
for (const competencyId of lessonById.get(lessonId)?.teaches ?? []) {
|
|
99
|
+
if (!partByCompetency.has(competencyId)) {
|
|
100
|
+
partByCompetency.set(competencyId, other);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const partsFor = (competencyIds: Iterable<string>): ManifestPart[] => {
|
|
107
|
+
const seen = new Map<string, ManifestPart>();
|
|
108
|
+
for (const id of competencyIds) {
|
|
109
|
+
const owner = partByCompetency.get(id);
|
|
110
|
+
if (owner && owner.dir !== part.dir) seen.set(owner.dir, owner);
|
|
111
|
+
}
|
|
112
|
+
return [...seen.values()].sort((a, b) => a.ordinal - b.ordinal);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const buildsOn = partsFor(required);
|
|
116
|
+
const leadsTo = manifest.parts
|
|
117
|
+
.filter((other) => other.dir !== part.dir)
|
|
118
|
+
.filter((other) =>
|
|
119
|
+
other.lessons.some((lessonId) =>
|
|
120
|
+
(lessonById.get(lessonId)?.requires ?? []).some((id) => taught.has(id)),
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
.sort((a, b) => a.ordinal - b.ordinal);
|
|
124
|
+
|
|
125
|
+
const outcomes = [...taught]
|
|
126
|
+
.map((id) => competencyById.get(id)?.outcome)
|
|
127
|
+
.filter((outcome): outcome is string => typeof outcome === 'string');
|
|
128
|
+
|
|
129
|
+
const equationCount = chapters.reduce(
|
|
130
|
+
(total, lesson) => total + lesson.equations.length,
|
|
131
|
+
0,
|
|
132
|
+
);
|
|
133
|
+
const checkCount = chapters.reduce(
|
|
134
|
+
(total, lesson) => total + lesson.knowledgeChecks.length,
|
|
135
|
+
0,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const partLink = (target: ManifestPart) =>
|
|
139
|
+
target.overview ? `${base}${target.link}` : undefined;
|
|
140
|
+
|
|
141
|
+
/** `30–40 min`, or `40 min` when the estimate is a single number. */
|
|
142
|
+
const minutes = (range: { min: number; max: number }) =>
|
|
143
|
+
range.min === range.max
|
|
144
|
+
? `${range.min} min`
|
|
145
|
+
: `${range.min}–${range.max} min`;
|
|
146
|
+
const hasRail =
|
|
147
|
+
rail &&
|
|
148
|
+
(chain.length > 0 ||
|
|
149
|
+
outcomes.length > 0 ||
|
|
150
|
+
buildsOn.length > 0 ||
|
|
151
|
+
leadsTo.length > 0);
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
<div class="part-overview not-content" data-rail={hasRail ? '' : undefined}>
|
|
155
|
+
<p class="part-meta">
|
|
156
|
+
<span>Part {part.ordinal} of {manifest.parts.length}</span>
|
|
157
|
+
<span>{chapters.length} {chapters.length === 1 ? 'chapter' : 'chapters'}</span>
|
|
158
|
+
{part.studyMinutes && <span>{minutes(part.studyMinutes)}</span>}
|
|
159
|
+
{equationCount > 0 && <span>{equationCount} numbered equations</span>}
|
|
160
|
+
{
|
|
161
|
+
checkCount > 0 && (
|
|
162
|
+
<span>
|
|
163
|
+
{checkCount} knowledge {checkCount === 1 ? 'check' : 'checks'}
|
|
164
|
+
</span>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
</p>
|
|
168
|
+
|
|
169
|
+
<div class="part-body">
|
|
170
|
+
<ol class="part-chapters">
|
|
171
|
+
{
|
|
172
|
+
chapters.map((lesson) => (
|
|
173
|
+
<li>
|
|
174
|
+
<a href={`${base}/${lesson.slug}/`}>{lesson.title}</a>
|
|
175
|
+
<p>{lesson.description}</p>
|
|
176
|
+
<p class="chapter-meta">
|
|
177
|
+
{lesson.studyMinutes && (
|
|
178
|
+
<span class="chapter-time">{minutes(lesson.studyMinutes)}</span>
|
|
179
|
+
)}
|
|
180
|
+
{lesson.equations.length > 0 && (
|
|
181
|
+
<span>
|
|
182
|
+
{lesson.equations.length} equation
|
|
183
|
+
{lesson.equations.length === 1 ? '' : 's'}
|
|
184
|
+
</span>
|
|
185
|
+
)}
|
|
186
|
+
{lesson.knowledgeChecks.length > 0 && (
|
|
187
|
+
<span>
|
|
188
|
+
{lesson.knowledgeChecks.length} check
|
|
189
|
+
{lesson.knowledgeChecks.length === 1 ? '' : 's'}
|
|
190
|
+
</span>
|
|
191
|
+
)}
|
|
192
|
+
{lesson.status !== 'reviewed' && <span>{lesson.status}</span>}
|
|
193
|
+
</p>
|
|
194
|
+
</li>
|
|
195
|
+
))
|
|
196
|
+
}
|
|
197
|
+
</ol>
|
|
198
|
+
|
|
199
|
+
{
|
|
200
|
+
hasRail && (
|
|
201
|
+
<aside class="part-rail ex-tier-meta" aria-label={`About ${part.label}`}>
|
|
202
|
+
{chain.length > 0 && (
|
|
203
|
+
<section>
|
|
204
|
+
<h2>The argument</h2>
|
|
205
|
+
<ol class="part-chain">
|
|
206
|
+
{chain.map((step) => (
|
|
207
|
+
<li>{step}</li>
|
|
208
|
+
))}
|
|
209
|
+
</ol>
|
|
210
|
+
</section>
|
|
211
|
+
)}
|
|
212
|
+
{outcomes.length > 0 && (
|
|
213
|
+
<section>
|
|
214
|
+
<h2>After this part you can</h2>
|
|
215
|
+
<ul>
|
|
216
|
+
{outcomes.map((outcome) => (
|
|
217
|
+
<li>{outcome}</li>
|
|
218
|
+
))}
|
|
219
|
+
</ul>
|
|
220
|
+
</section>
|
|
221
|
+
)}
|
|
222
|
+
{buildsOn.length > 0 && (
|
|
223
|
+
<section>
|
|
224
|
+
<h2>Builds on</h2>
|
|
225
|
+
<ul>
|
|
226
|
+
{buildsOn.map((other) => (
|
|
227
|
+
<li>
|
|
228
|
+
{partLink(other) ? (
|
|
229
|
+
<a href={partLink(other)}>{other.label}</a>
|
|
230
|
+
) : (
|
|
231
|
+
other.label
|
|
232
|
+
)}
|
|
233
|
+
</li>
|
|
234
|
+
))}
|
|
235
|
+
</ul>
|
|
236
|
+
</section>
|
|
237
|
+
)}
|
|
238
|
+
{leadsTo.length > 0 && (
|
|
239
|
+
<section>
|
|
240
|
+
<h2>Leads to</h2>
|
|
241
|
+
<ul>
|
|
242
|
+
{leadsTo.map((other) => (
|
|
243
|
+
<li>
|
|
244
|
+
{partLink(other) ? (
|
|
245
|
+
<a href={partLink(other)}>{other.label}</a>
|
|
246
|
+
) : (
|
|
247
|
+
other.label
|
|
248
|
+
)}
|
|
249
|
+
</li>
|
|
250
|
+
))}
|
|
251
|
+
</ul>
|
|
252
|
+
</section>
|
|
253
|
+
)}
|
|
254
|
+
</aside>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<style>
|
|
261
|
+
.part-overview {
|
|
262
|
+
margin-block-start: 1.5rem;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.part-meta {
|
|
266
|
+
display: flex;
|
|
267
|
+
flex-wrap: wrap;
|
|
268
|
+
gap: 0.3rem 1rem;
|
|
269
|
+
margin: 0 0 1.25rem;
|
|
270
|
+
color: var(--sl-color-gray-3);
|
|
271
|
+
font-size: var(--sl-text-xs);
|
|
272
|
+
letter-spacing: 0.04em;
|
|
273
|
+
text-transform: uppercase;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.part-body {
|
|
277
|
+
display: grid;
|
|
278
|
+
gap: 1.75rem;
|
|
279
|
+
align-items: start;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.part-overview[data-rail] .part-body {
|
|
283
|
+
grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@media (max-width: 62rem) {
|
|
287
|
+
.part-overview[data-rail] .part-body {
|
|
288
|
+
grid-template-columns: minmax(0, 1fr);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.part-chapters {
|
|
293
|
+
counter-reset: chapter;
|
|
294
|
+
margin: 0;
|
|
295
|
+
padding: 0;
|
|
296
|
+
list-style: none;
|
|
297
|
+
border-block-start: 1px solid var(--sl-color-gray-5);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.part-chapters > li {
|
|
301
|
+
counter-increment: chapter;
|
|
302
|
+
display: grid;
|
|
303
|
+
grid-template-columns: 2.25rem minmax(0, 1fr);
|
|
304
|
+
gap: 0.15rem 0.5rem;
|
|
305
|
+
margin: 0;
|
|
306
|
+
padding-block: 0.85rem;
|
|
307
|
+
border-block-end: 1px solid var(--sl-color-gray-5);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.part-chapters > li::before {
|
|
311
|
+
content: counter(chapter, decimal-leading-zero);
|
|
312
|
+
grid-row: 1 / span 3;
|
|
313
|
+
color: var(--sl-color-text-accent);
|
|
314
|
+
font-variant-numeric: tabular-nums;
|
|
315
|
+
font-size: var(--sl-text-xs);
|
|
316
|
+
line-height: 1.7;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.part-chapters > li > a {
|
|
320
|
+
font-weight: 600;
|
|
321
|
+
text-decoration: none;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.part-chapters > li > a:hover,
|
|
325
|
+
.part-chapters > li > a:focus-visible {
|
|
326
|
+
text-decoration: underline;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.part-chapters > li > p {
|
|
330
|
+
margin: 0;
|
|
331
|
+
color: var(--sl-color-gray-2);
|
|
332
|
+
font-size: var(--sl-text-sm);
|
|
333
|
+
line-height: 1.5;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.chapter-meta {
|
|
337
|
+
display: flex;
|
|
338
|
+
flex-wrap: wrap;
|
|
339
|
+
gap: 0.2rem 0.75rem;
|
|
340
|
+
color: var(--sl-color-gray-3);
|
|
341
|
+
font-size: var(--sl-text-xs);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.chapter-meta:empty {
|
|
345
|
+
display: none;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* The estimate is the number a reader plans around, so it leads the row. */
|
|
349
|
+
.chapter-time {
|
|
350
|
+
color: var(--sl-color-text-accent);
|
|
351
|
+
font-variant-numeric: tabular-nums;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* Part logistics: the meta tier (`ex-tier-meta`, `surfaces.css`) draws
|
|
355
|
+
the rail's surface, border and radius. */
|
|
356
|
+
.part-rail {
|
|
357
|
+
display: grid;
|
|
358
|
+
gap: 1rem;
|
|
359
|
+
padding: 1rem 1.1rem;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.part-rail section {
|
|
363
|
+
display: grid;
|
|
364
|
+
gap: 0.4rem;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.part-rail h2 {
|
|
368
|
+
margin: 0;
|
|
369
|
+
color: var(--sl-color-gray-3);
|
|
370
|
+
font-size: var(--sl-text-xs);
|
|
371
|
+
font-weight: 500;
|
|
372
|
+
letter-spacing: 0.07em;
|
|
373
|
+
text-transform: uppercase;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.part-rail ul,
|
|
377
|
+
.part-rail ol {
|
|
378
|
+
margin: 0;
|
|
379
|
+
padding-inline-start: 1.1rem;
|
|
380
|
+
display: grid;
|
|
381
|
+
gap: 0.3rem;
|
|
382
|
+
font-size: var(--sl-text-sm);
|
|
383
|
+
line-height: 1.45;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.part-rail li {
|
|
387
|
+
margin: 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.part-chain {
|
|
391
|
+
list-style: none;
|
|
392
|
+
padding-inline-start: 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.part-chain > li + li::before {
|
|
396
|
+
content: '\2193';
|
|
397
|
+
display: block;
|
|
398
|
+
color: var(--sl-color-gray-4);
|
|
399
|
+
line-height: 1.1;
|
|
400
|
+
}
|
|
401
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* The paragraph on a part's overview page that says what this part itself
|
|
4
|
+
* covers — the question it answers, and how its chapters answer it. Rule S2
|
|
5
|
+
* requires one on every part page, after `<PartBridge>` when there is one;
|
|
6
|
+
* `gate-part-pages.ts` checks that it holds one paragraph within the course's
|
|
7
|
+
* `partPages.scope` word range.
|
|
8
|
+
*
|
|
9
|
+
* Usage, in `content/docs/<part>/index.mdx`:
|
|
10
|
+
*
|
|
11
|
+
* ```mdx
|
|
12
|
+
* import PartScope from 'explico/components/PartScope.astro';
|
|
13
|
+
*
|
|
14
|
+
* <PartScope>
|
|
15
|
+
*
|
|
16
|
+
* This part applies those results to one problem: a projectile launched over
|
|
17
|
+
* level ground. …
|
|
18
|
+
*
|
|
19
|
+
* </PartScope>
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Keep a blank line inside each tag, so the paragraph is parsed as Markdown
|
|
23
|
+
* and its math and `[[key]]` references render.
|
|
24
|
+
*/
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<div class="part-intro part-scope" data-part-intro="scope"><slot /></div>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* Cards for the inline abbreviation links `remarkAbbreviations` emits (N14,
|
|
4
|
+
* close sibling of `CitationLayer.astro`). The links are real anchors to the
|
|
5
|
+
* glossary's "Abbreviations" section and work with no JavaScript; this
|
|
6
|
+
* island only adds a quick look at the resolution without leaving the
|
|
7
|
+
* reader's place.
|
|
8
8
|
*
|
|
9
9
|
* The abbreviations table is small by nature (a name + one-line resolution
|
|
10
10
|
* each), so unlike the math-notation layer this renders every entry
|
|
11
11
|
* unconditionally on every lesson page — one hidden block per entry — rather
|
|
12
|
-
* than computing a per-page dependency closure.
|
|
12
|
+
* than computing a per-page dependency closure. A card reads its content
|
|
13
13
|
* from that static markup, never from a second serialized copy of the table.
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
14
|
+
* Opening, pinning, Escape and placement come from the card manager
|
|
15
|
+
* (`../card-manager.ts`).
|
|
16
16
|
*/
|
|
17
17
|
import { getCollection } from 'astro:content';
|
|
18
18
|
import HoverPanel from '../HoverPanel.astro';
|
|
@@ -21,11 +21,11 @@ const entries = await getCollection('abbreviations');
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
23
|
<HoverPanel id="abbreviation-panel" label="Abbreviation" data-abbreviation-panel>
|
|
24
|
+
<p class="abbreviation-panel-term" data-panel-term slot="heading"></p>
|
|
24
25
|
<div class="abbreviation-panel-body">
|
|
25
|
-
<p class="abbreviation-panel-term" data-panel-term></p>
|
|
26
26
|
<p class="abbreviation-panel-expansion" data-panel-expansion></p>
|
|
27
27
|
<a class="abbreviation-panel-jump" data-panel-jump
|
|
28
|
-
>
|
|
28
|
+
>Go to the glossary<span aria-hidden="true" data-jump-arrow> →</span></a
|
|
29
29
|
>
|
|
30
30
|
</div>
|
|
31
31
|
</HoverPanel>
|
|
@@ -39,55 +39,52 @@ const entries = await getCollection('abbreviations');
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
<script>
|
|
42
|
-
import {
|
|
42
|
+
import { cloneCardShell, registerCardSource } from '../card-manager';
|
|
43
|
+
import { wireJumpArrow } from '../card-jump';
|
|
43
44
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
48
|
-
if (!panel || panel.dataset.hoverPanelReady === 'true') return;
|
|
49
|
-
|
|
50
|
-
const markers = Array.from(
|
|
51
|
-
document.querySelectorAll<HTMLAnchorElement>(
|
|
52
|
-
'.sl-markdown-content a.abbreviation-term[data-abbr-key]',
|
|
53
|
-
),
|
|
54
|
-
);
|
|
55
|
-
if (markers.length === 0) return;
|
|
56
|
-
const markerSet = new Set<HTMLElement>(markers);
|
|
57
|
-
|
|
58
|
-
const term = panel.querySelector<HTMLElement>('[data-panel-term]')!;
|
|
59
|
-
const expansion = panel.querySelector<HTMLElement>(
|
|
60
|
-
'[data-panel-expansion]',
|
|
61
|
-
)!;
|
|
62
|
-
const jump = panel.querySelector<HTMLAnchorElement>('[data-panel-jump]')!;
|
|
63
|
-
|
|
64
|
-
const markerFrom = (node: EventTarget | null) => {
|
|
65
|
-
const candidate = closestElement(
|
|
66
|
-
node,
|
|
67
|
-
'a.abbreviation-term[data-abbr-key]',
|
|
68
|
-
);
|
|
69
|
-
return candidate && markerSet.has(candidate) ? candidate : null;
|
|
70
|
-
};
|
|
45
|
+
function closestElement(node: EventTarget | null, selector: string) {
|
|
46
|
+
return node instanceof Element ? node.closest<HTMLElement>(selector) : null;
|
|
47
|
+
}
|
|
71
48
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
49
|
+
const entryFor = (key: string) =>
|
|
50
|
+
document.querySelector<HTMLElement>(`[data-abbr-entry="${CSS.escape(key)}"]`);
|
|
51
|
+
|
|
52
|
+
function setup() {
|
|
53
|
+
const template = document.getElementById('abbreviation-panel');
|
|
54
|
+
if (
|
|
55
|
+
!(template instanceof HTMLTemplateElement) ||
|
|
56
|
+
template.dataset.cardSourceReady === 'true' ||
|
|
57
|
+
!document.querySelector('a.abbreviation-term[data-abbr-key]')
|
|
58
|
+
) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
template.dataset.cardSourceReady = 'true';
|
|
62
|
+
|
|
63
|
+
// Fill a card from the matching hidden `[data-abbr-entry]` block — never
|
|
64
|
+
// from a second serialized copy of the table.
|
|
65
|
+
const render = (key: string, marker: HTMLElement) => {
|
|
66
|
+
const entry = entryFor(key);
|
|
67
|
+
const el = cloneCardShell(template);
|
|
68
|
+
const term = el?.querySelector<HTMLElement>('[data-panel-term]');
|
|
69
|
+
const expansion = el?.querySelector<HTMLElement>('[data-panel-expansion]');
|
|
70
|
+
const jump = el?.querySelector<HTMLAnchorElement>('[data-panel-jump]');
|
|
71
|
+
if (!entry || !el || !term || !expansion || !jump) return null;
|
|
79
72
|
|
|
80
73
|
term.textContent = marker.textContent ?? '';
|
|
81
74
|
expansion.textContent = entry.textContent ?? '';
|
|
82
75
|
jump.href = marker.getAttribute('href') ?? '#abbreviations';
|
|
83
|
-
|
|
76
|
+
wireJumpArrow(jump);
|
|
77
|
+
el.setAttribute('aria-label', `Abbreviation: ${term.textContent}`);
|
|
78
|
+
return el;
|
|
84
79
|
};
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
registerCardSource({
|
|
82
|
+
layer: 'abbreviation',
|
|
83
|
+
triggerFrom: (node) => closestElement(node, 'a.abbreviation-term[data-abbr-key]'),
|
|
84
|
+
itemFor: (marker) => {
|
|
85
|
+
const key = marker.dataset.abbrKey;
|
|
86
|
+
return key && entryFor(key) ? key : null;
|
|
87
|
+
},
|
|
91
88
|
render,
|
|
92
89
|
});
|
|
93
90
|
}
|
|
@@ -116,6 +113,7 @@ const entries = await getCollection('abbreviations');
|
|
|
116
113
|
}
|
|
117
114
|
|
|
118
115
|
.abbreviation-panel-term {
|
|
116
|
+
margin: 0;
|
|
119
117
|
font-weight: 600;
|
|
120
118
|
}
|
|
121
119
|
|
|
@@ -332,11 +332,6 @@ export default function AssessmentRunner({
|
|
|
332
332
|
return (
|
|
333
333
|
<section className="assessment-shell" aria-labelledby={headingId}>
|
|
334
334
|
{!labelledBy && <h3 id={headingId}>{title}</h3>}
|
|
335
|
-
<p className="assessment-progress" aria-live="polite">
|
|
336
|
-
{answered === 0
|
|
337
|
-
? `${items.length} question${items.length === 1 ? '' : 's'}`
|
|
338
|
-
: `${correct} of ${items.length} correct`}
|
|
339
|
-
</p>
|
|
340
335
|
<ol className="assessment-list">
|
|
341
336
|
{items.map((item, position) => (
|
|
342
337
|
<Question
|
|
@@ -350,6 +345,14 @@ export default function AssessmentRunner({
|
|
|
350
345
|
/>
|
|
351
346
|
))}
|
|
352
347
|
</ol>
|
|
348
|
+
{/* The score, once there is one. Empty until the first answer — the
|
|
349
|
+
list already shows how many questions there are — and under the
|
|
350
|
+
list, so appearing never pushes down the question just answered.
|
|
351
|
+
The live region stays in the tree while empty, so the first score
|
|
352
|
+
is announced. */}
|
|
353
|
+
<p className="assessment-progress" aria-live="polite">
|
|
354
|
+
{answered === 0 ? '' : `${correct} of ${items.length} correct`}
|
|
355
|
+
</p>
|
|
353
356
|
</section>
|
|
354
357
|
);
|
|
355
358
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
3
|
import { loadManifest } from '../../compiler/manifest';
|
|
4
|
+
import { DISCLOSURE_DEFAULTS } from '../../compiler/course-config';
|
|
4
5
|
import type { ContentSegment } from '../../reference/mixed-content';
|
|
5
6
|
import { renderNotationMath } from '../notation/render-notation-math';
|
|
6
7
|
import type { MixedContentNode } from '../notation/render-notation-math';
|
|
7
8
|
import { knowledgeCheckAnchorId } from '../../reference/knowledge-checks.mjs';
|
|
9
|
+
import BlockHeading from '../BlockHeading.astro';
|
|
8
10
|
import AssessmentRunner from './AssessmentRunner';
|
|
9
11
|
|
|
10
12
|
interface Props {
|
|
@@ -26,6 +28,9 @@ const lessonId = Astro.url.pathname
|
|
|
26
28
|
.replace(/\//g, '.');
|
|
27
29
|
|
|
28
30
|
const manifest = await loadManifest();
|
|
31
|
+
// A check folds like a section, by the course's `disclosure.knowledgeChecks`
|
|
32
|
+
// (collapsible and open unless the course says otherwise).
|
|
33
|
+
const { knowledgeChecks } = manifest.disclosure ?? DISCLOSURE_DEFAULTS;
|
|
29
34
|
const lesson = manifest.lessons.find((entry) => entry.id === lessonId);
|
|
30
35
|
if (!lesson) {
|
|
31
36
|
throw new Error(
|
|
@@ -55,7 +60,9 @@ const entries = ids.map((id, index) => {
|
|
|
55
60
|
lesson.numbering.checks + lesson.assessments.length + index + 1;
|
|
56
61
|
const number =
|
|
57
62
|
publishedNumber ??
|
|
58
|
-
(lesson.
|
|
63
|
+
(lesson.numbering.prefix
|
|
64
|
+
? `${lesson.numbering.prefix}.${localIndex}`
|
|
65
|
+
: String(index + 1));
|
|
59
66
|
return { entry, number, anchorId: knowledgeCheckAnchorId(id) };
|
|
60
67
|
});
|
|
61
68
|
|
|
@@ -85,34 +92,24 @@ function renderedContent(
|
|
|
85
92
|
}
|
|
86
93
|
---
|
|
87
94
|
|
|
95
|
+
{/* The heading is the engine's (`BlockHeading.astro`): the "Knowledge
|
|
96
|
+
check" badge and the number before the assessment file's `title`.
|
|
97
|
+
`card="accent"`: the check is author-marked enrichment, so its heading and
|
|
98
|
+
questions sit in one accent card (`surfaces.css`), drawn by
|
|
99
|
+
`CollapsibleSections.astro` the way a `[card]` heading's section is. */}
|
|
88
100
|
{
|
|
89
101
|
entries.map(({ entry, number, anchorId }) => (
|
|
90
102
|
<>
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
data
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<a
|
|
102
|
-
class="sl-anchor-link"
|
|
103
|
-
href={`#${anchorId}`}
|
|
104
|
-
aria-label={`Knowledge check ${number} titled “${entry.data.title}”`}
|
|
105
|
-
>
|
|
106
|
-
<span aria-hidden="true" class="sl-anchor-icon">
|
|
107
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
108
|
-
<path 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" />
|
|
109
|
-
</svg>
|
|
110
|
-
</span>
|
|
111
|
-
<span class="sr-only" data-pagefind-ignore="true">
|
|
112
|
-
Link to knowledge check {number}: {entry.data.title}
|
|
113
|
-
</span>
|
|
114
|
-
</a>
|
|
115
|
-
</div>
|
|
103
|
+
<BlockHeading
|
|
104
|
+
kind="check"
|
|
105
|
+
anchorId={anchorId}
|
|
106
|
+
title={entry.data.title}
|
|
107
|
+
number={number}
|
|
108
|
+
collapsible={knowledgeChecks.collapsible}
|
|
109
|
+
defaultOpen={knowledgeChecks.defaultOpen}
|
|
110
|
+
card="accent"
|
|
111
|
+
class="assessment-heading-wrapper"
|
|
112
|
+
/>
|
|
116
113
|
<AssessmentRunner
|
|
117
114
|
client:visible
|
|
118
115
|
assessmentId={entry.data.id}
|