@erclx/canon 5.52.0 → 5.54.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 +17 -0
- package/README.md +11 -13
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canon-cli/SKILL.md +5 -8
- package/claude/skills/context-fold/REQUIREMENT.md +0 -1
- package/claude/skills/design-identity/REQUIREMENT.md +2 -2
- package/claude/skills/design-identity/SKILL.md +6 -6
- package/claude/skills/design-record/REQUIREMENT.md +2 -2
- package/claude/skills/design-record/SKILL.md +4 -4
- package/claude/skills/design-record/references/folder.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +1 -1
- package/claude/skills/design-taste/references/kinds.md +0 -1
- package/claude/skills/draw-figure/REQUIREMENT.md +3 -4
- package/claude/skills/draw-figure/SKILL.md +5 -6
- package/claude/skills/draw-figure/references/figures.md +0 -1
- package/claude/skills/draw-figure/references/hand-drawn.md +1 -1
- package/claude/skills/pick-by-eye/REQUIREMENT.md +4 -5
- package/claude/skills/pick-by-eye/SKILL.md +34 -41
- package/claude/skills/pick-by-eye/references/board.md +42 -0
- package/claude/skills/pick-by-eye/references/live-arms.md +3 -3
- package/claude/skills/plan-groundwork/SKILL.md +1 -1
- package/claude/skills/teach-craft/REQUIREMENT.md +30 -0
- package/claude/skills/teach-craft/SKILL.md +70 -0
- package/claude/skills/teach-craft/references/adopted.md +113 -0
- package/claude/skills/teach-craft/references/lesson-craft.md +60 -0
- package/claude/skills/{teach-workspace → teach-craft}/references/pedagogy.md +41 -6
- package/docs/agents/audits.md +0 -1
- package/docs/agents/install-and-sync.md +0 -8
- package/docs/target/migrations.md +16 -2
- package/docs/target/projects.md +1 -1
- package/docs/workflow/parallel-features.md +1 -1
- package/docs/workflow/skill-map-on-request.md +10 -22
- package/docs/workflow/skill-map.md +11 -14
- package/docs/workflow/visual-design-workflow.md +1 -4
- package/governance/detect.toml +1 -6
- package/governance/renames.toml +0 -1
- package/package.json +2 -8
- package/scripts/core/regen-web-previews.ts +2 -67
- package/src/audits/catalog.ts +0 -1
- package/src/browser/engine.ts +3 -6
- package/src/capture/render.ts +1 -1
- package/src/claude/cases/authoring.ts +0 -12
- package/src/claude/cases/misc.ts +0 -22
- package/src/claude/cases/workflow.ts +2 -38
- package/src/claude/skills-audit.ts +1 -1
- package/src/cli/help.ts +0 -10
- package/src/cli.ts +0 -8
- package/src/commands/design.ts +16 -315
- package/src/commands/records.ts +0 -1
- package/src/commands/serve.ts +2 -2
- package/src/commands/sync.ts +4 -5
- package/src/design/components.ts +8 -1514
- package/src/design/css.ts +5 -6
- package/src/design/fonts.ts +3 -5
- package/src/design/regen.ts +5 -32
- package/src/design/render.ts +1 -1
- package/src/design/tokens.ts +3 -2
- package/src/driver/drive.ts +3 -4
- package/src/driver/steps.ts +2 -5
- package/src/gate/stages.ts +4 -10
- package/src/gov/audit.ts +1 -6
- package/src/inventory/walk.ts +1 -1
- package/src/markdown/scope.ts +6 -35
- package/src/records/validate.ts +4 -176
- package/src/sandbox/equivalence.ts +0 -5
- package/src/shipped/references.ts +1 -2
- package/src/sync/check.ts +4 -29
- package/src/sync/stamp.ts +6 -6
- package/src/sync/target.ts +1 -4
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/canon/DESIGN.md +1 -1
- package/claude/skills/canvas-board/REQUIREMENT.md +0 -35
- package/claude/skills/canvas-board/SKILL.md +0 -143
- package/claude/skills/canvas-board/references/canvas.md +0 -170
- package/claude/skills/draft-and-pick/REQUIREMENT.md +0 -26
- package/claude/skills/draft-and-pick/SKILL.md +0 -11
- package/claude/skills/draft-doc/REQUIREMENT.md +0 -26
- package/claude/skills/draft-doc/SKILL.md +0 -11
- package/claude/skills/draft-figure/REQUIREMENT.md +0 -26
- package/claude/skills/draft-figure/SKILL.md +0 -11
- package/claude/skills/draft-identity/REQUIREMENT.md +0 -26
- package/claude/skills/draft-identity/SKILL.md +0 -11
- package/claude/skills/draft-ready/REQUIREMENT.md +0 -26
- package/claude/skills/draft-ready/SKILL.md +0 -11
- package/claude/skills/draft-screencast/REQUIREMENT.md +0 -26
- package/claude/skills/draft-screencast/SKILL.md +0 -11
- package/claude/skills/draft-slides/REQUIREMENT.md +0 -26
- package/claude/skills/draft-slides/SKILL.md +0 -11
- package/claude/skills/make-deck/REQUIREMENT.md +0 -36
- package/claude/skills/make-deck/SKILL.md +0 -80
- package/claude/skills/make-deck/references/deck.md +0 -183
- package/claude/skills/read-frames/REQUIREMENT.md +0 -30
- package/claude/skills/read-frames/SKILL.md +0 -55
- package/claude/skills/record-screencast/REQUIREMENT.md +0 -35
- package/claude/skills/record-screencast/SKILL.md +0 -99
- package/claude/skills/script-screencast/REQUIREMENT.md +0 -34
- package/claude/skills/script-screencast/SKILL.md +0 -70
- package/claude/skills/script-screencast/references/beats-template.md +0 -119
- package/claude/skills/teach-workspace/REQUIREMENT.md +0 -34
- package/claude/skills/teach-workspace/SKILL.md +0 -196
- package/claude/skills/teach-workspace/references/glossary.md +0 -90
- package/claude/skills/teach-workspace/references/lesson-build.md +0 -106
- package/claude/skills/teach-workspace/references/lesson-craft.md +0 -135
- package/claude/skills/teach-workspace/references/promotion-fold.md +0 -35
- package/claude/skills/teach-workspace/references/promotion.md +0 -116
- package/claude/skills/teach-workspace/references/revisit.md +0 -28
- package/claude/skills/teach-workspace/references/teach.md +0 -211
- package/claude/skills/video-craft/REQUIREMENT.md +0 -35
- package/claude/skills/video-craft/SKILL.md +0 -96
- package/claude/skills/video-craft/references/adopted.md +0 -110
- package/governance/rules/ui/canvas.md +0 -13
- package/src/canvas/address.ts +0 -227
- package/src/canvas/arrange.ts +0 -66
- package/src/canvas/capture.ts +0 -391
- package/src/canvas/client/app.tsx +0 -188
- package/src/canvas/client/inspector/align-grid.tsx +0 -162
- package/src/canvas/client/inspector/color-field.tsx +0 -398
- package/src/canvas/client/inspector/color-inputs.tsx +0 -139
- package/src/canvas/client/inspector/color-picker.tsx +0 -668
- package/src/canvas/client/inspector/color-space.ts +0 -240
- package/src/canvas/client/inspector/field.tsx +0 -263
- package/src/canvas/client/inspector/fields.css +0 -763
- package/src/canvas/client/inspector/section.tsx +0 -27
- package/src/canvas/client/inspector/size-field.tsx +0 -69
- package/src/canvas/client/inspector/token-select.tsx +0 -109
- package/src/canvas/client/inspector/values.ts +0 -297
- package/src/canvas/client/inspector.tsx +0 -692
- package/src/canvas/client/keys.ts +0 -71
- package/src/canvas/client/layers.tsx +0 -118
- package/src/canvas/client/main.tsx +0 -71
- package/src/canvas/client/pages-panel.tsx +0 -212
- package/src/canvas/client/panel-handle.tsx +0 -90
- package/src/canvas/client/selection.tsx +0 -199
- package/src/canvas/client/shell.css +0 -762
- package/src/canvas/client/state.ts +0 -792
- package/src/canvas/client/surface.tsx +0 -803
- package/src/canvas/client/theme-panel.tsx +0 -55
- package/src/canvas/client/tools.tsx +0 -153
- package/src/canvas/content.ts +0 -781
- package/src/canvas/edit.ts +0 -575
- package/src/canvas/editing.ts +0 -203
- package/src/canvas/fit.ts +0 -238
- package/src/canvas/global.d.ts +0 -15
- package/src/canvas/history.ts +0 -116
- package/src/canvas/index.html +0 -20
- package/src/canvas/server.ts +0 -766
- package/src/canvas/tokens.ts +0 -221
- package/src/commands/canvas.ts +0 -1097
- package/src/commands/demo.ts +0 -542
- package/src/commands/slides.ts +0 -184
- package/src/commands/teach.ts +0 -1401
- package/src/demo/beats.ts +0 -135
- package/src/demo/compile.ts +0 -350
- package/src/demo/container.ts +0 -219
- package/src/demo/cursors.ts +0 -55
- package/src/demo/drive.ts +0 -442
- package/src/demo/pointer.ts +0 -178
- package/src/demo/theme.ts +0 -112
- package/src/demo/timeline.ts +0 -64
- package/src/design/adapter.ts +0 -59
- package/src/design/base.css +0 -203
- package/src/design/brand-favicon.ts +0 -18
- package/src/design/neutral.ts +0 -127
- package/src/design/project-fonts.ts +0 -392
- package/src/slides/convert/chart.ts +0 -165
- package/src/slides/convert/deck.ts +0 -244
- package/src/slides/convert/export.ts +0 -567
- package/src/slides/convert/master.ts +0 -198
- package/src/slides/convert/shapes.ts +0 -804
- package/src/slides/convert/svg.ts +0 -38
- package/src/slides/convert/walk.ts +0 -671
- package/src/slides/decks.ts +0 -110
- package/src/slides/open.ts +0 -18
- package/src/slides/package/fonts.ts +0 -301
- package/src/slides/package/motion.ts +0 -316
- package/src/slides/package/test-face.ts +0 -113
- package/src/teach/browser/compile.ts +0 -44
- package/src/teach/browser/dismiss.ts +0 -76
- package/src/teach/browser/focus-line.ts +0 -19
- package/src/teach/browser/glossary-filter.ts +0 -44
- package/src/teach/browser/head.ts +0 -30
- package/src/teach/browser/quiz.ts +0 -19
- package/src/teach/browser/reload.ts +0 -9
- package/src/teach/browser/sidebar.ts +0 -233
- package/src/teach/browser/theme.ts +0 -18
- package/src/teach/components/code.tsx +0 -24
- package/src/teach/components/heading.tsx +0 -11
- package/src/teach/components/list.tsx +0 -17
- package/src/teach/components/paragraph.tsx +0 -29
- package/src/teach/components/refs.tsx +0 -28
- package/src/teach/cross-references.ts +0 -78
- package/src/teach/fonts.ts +0 -16
- package/src/teach/highlight.ts +0 -71
- package/src/teach/html/jsx-dev-runtime.ts +0 -13
- package/src/teach/html/jsx-runtime.ts +0 -113
- package/src/teach/html/raw.ts +0 -28
- package/src/teach/lesson.ts +0 -178
- package/src/teach/nav.ts +0 -1337
- package/src/teach/render.ts +0 -240
- package/src/teach/workspace.ts +0 -1164
- package/src/teach/write-if-changed.ts +0 -17
package/src/design/components.ts
CHANGED
|
@@ -6,14 +6,10 @@
|
|
|
6
6
|
* where the tokens were already right and the surface still read as unrelated
|
|
7
7
|
* to itself, so a token file alone would not have caught either.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* asked for teach. It reads instead as `TEACH_COMPONENTS`, a sibling array a
|
|
14
|
-
* caller opts into by passing an explicit component list to
|
|
15
|
-
* `buildDesignCss`'s `components` option, which is what widening that option
|
|
16
|
-
* from a boolean to `boolean | readonly Component[]` bought.
|
|
9
|
+
* `COMPONENTS` is the generic default every project installing the `design`
|
|
10
|
+
* domain receives. A caller wanting a different set passes an explicit list to
|
|
11
|
+
* `buildDesignCss`'s `components` option, which takes
|
|
12
|
+
* `boolean | readonly Component[]` for that reason.
|
|
17
13
|
*
|
|
18
14
|
* It does not live in `canon/DESIGN.md`. `claude/skills/design-record/references/design.md` keeps CSS class
|
|
19
15
|
* names out of that record and says they live in code, which is here.
|
|
@@ -122,14 +118,10 @@ const SCROLLBAR: Component = {
|
|
|
122
118
|
/**
|
|
123
119
|
* The hand-drawn SVG diagram figure and its caption, sized wider than the
|
|
124
120
|
* reading measure and cleared past an outline rail on a wide viewport.
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* that component's `reads` list named `--teach-hand` and `--teach-wide`
|
|
130
|
-
* while only `TEACH_CHROME` declared them, so this component declares its
|
|
131
|
-
* own `--figure-hand` and `--figure-wide` defaults rather than borrowing
|
|
132
|
-
* undeclared custom properties from a teach-only sibling.
|
|
121
|
+
* Per `.canon/groundwork/86-portable-hand-drawn-figures/06-decision.md` item 4,
|
|
122
|
+
* any project can draw one hand-drawn-styled figure. It declares its own
|
|
123
|
+
* `--figure-hand` and `--figure-wide` defaults rather than reading custom
|
|
124
|
+
* properties another component would have to declare.
|
|
133
125
|
*/
|
|
134
126
|
const HAND_DRAWN_FIGURE: Component = {
|
|
135
127
|
name: 'hand-drawn-figure',
|
|
@@ -199,1501 +191,3 @@ export const COMPONENTS: readonly Component[] = [
|
|
|
199
191
|
SCROLLBAR,
|
|
200
192
|
HAND_DRAWN_FIGURE,
|
|
201
193
|
]
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Where the course sidebar stops being a column and becomes an overlay.
|
|
205
|
-
*
|
|
206
|
-
* The media query below and both `matchMedia` calls in `@/teach/nav` read this
|
|
207
|
-
* one value. Three literals is what it was, and each had a test asserting its
|
|
208
|
-
* own copy, so a half-finished move passed green while the scripts switched at
|
|
209
|
-
* one width and the layout at another.
|
|
210
|
-
*/
|
|
211
|
-
export const TEACH_SIDEBAR_BREAKPOINT = 1100
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* The colors highlighted code renders in, one per token class the teach
|
|
215
|
-
* article maps highlight.js spans onto. Hex rather than `color-mix` so the
|
|
216
|
-
* contrast test can measure each against the code surface, and dark by default
|
|
217
|
-
* with a light override, following the same `data-theme` switch as the ground
|
|
218
|
-
* they sit on. Teach-local until a second surface renders highlighted code.
|
|
219
|
-
* `keyword`, `comment` and `meta` repeat the accent, muted and secondary
|
|
220
|
-
* values, so code reads in the page's own palette.
|
|
221
|
-
*/
|
|
222
|
-
export const TEACH_SYNTAX: readonly {
|
|
223
|
-
readonly name: string
|
|
224
|
-
readonly dark: string
|
|
225
|
-
readonly light: string
|
|
226
|
-
}[] = [
|
|
227
|
-
{ name: 'keyword', dark: '#c76b5f', light: '#ad4a4b' },
|
|
228
|
-
{ name: 'string', dark: '#8dbb6c', light: '#2d6b22' },
|
|
229
|
-
{ name: 'number', dark: '#d4a15a', light: '#8a5300' },
|
|
230
|
-
{ name: 'title', dark: '#b99ad8', light: '#72489a' },
|
|
231
|
-
{ name: 'attr', dark: '#6fb3c4', light: '#1e6478' },
|
|
232
|
-
{ name: 'comment', dark: '#7f7f7c', light: '#6e6d6c' },
|
|
233
|
-
{ name: 'meta', dark: '#8c8b86', light: '#666561' },
|
|
234
|
-
]
|
|
235
|
-
|
|
236
|
-
const syntaxProperty = (name: string): string => `--teach-syntax-${name}`
|
|
237
|
-
|
|
238
|
-
const TEACH_SYNTAX_DARK = TEACH_SYNTAX.map(
|
|
239
|
-
({ name, dark }) => ` ${syntaxProperty(name)}: ${dark};`,
|
|
240
|
-
).join('\n')
|
|
241
|
-
|
|
242
|
-
const TEACH_SYNTAX_LIGHT = TEACH_SYNTAX.map(
|
|
243
|
-
({ name, light }) => ` ${syntaxProperty(name)}: ${light};`,
|
|
244
|
-
).join('\n')
|
|
245
|
-
|
|
246
|
-
const TEACH_CHROME: Component = {
|
|
247
|
-
name: 'teach-chrome',
|
|
248
|
-
note: [
|
|
249
|
-
'The masthead, breadcrumb, jump menu, theme toggle, course sidebar, and footer',
|
|
250
|
-
'navigation shared by every teach page. Recovered from two gitignored',
|
|
251
|
-
'course.css files that predate a regression that dropped this layer from the',
|
|
252
|
-
"generator, and rewritten onto this module's tokens rather than the",
|
|
253
|
-
'incompatible palette they carried. See canon/context/features/teach.md',
|
|
254
|
-
'for the shape.',
|
|
255
|
-
].join('\n '),
|
|
256
|
-
reads: [
|
|
257
|
-
'--color-background',
|
|
258
|
-
'--color-surface',
|
|
259
|
-
'--color-chrome',
|
|
260
|
-
'--color-border',
|
|
261
|
-
'--color-text',
|
|
262
|
-
'--color-text-body',
|
|
263
|
-
'--color-text-secondary',
|
|
264
|
-
'--color-muted',
|
|
265
|
-
'--color-accent',
|
|
266
|
-
'--teach-sans',
|
|
267
|
-
'--teach-mono',
|
|
268
|
-
'--teach-measure',
|
|
269
|
-
'--teach-shadow',
|
|
270
|
-
'--color-teach-accent-bg',
|
|
271
|
-
'--type-body-family',
|
|
272
|
-
'--type-code-family',
|
|
273
|
-
'--t3',
|
|
274
|
-
'--t4',
|
|
275
|
-
'--t5',
|
|
276
|
-
'--t6',
|
|
277
|
-
],
|
|
278
|
-
rules: `:root {
|
|
279
|
-
--teach-sans: var(--type-body-family);
|
|
280
|
-
--teach-hand: 'Virgil', 'Excalifont', cursive;
|
|
281
|
-
--teach-mono: var(--type-code-family);
|
|
282
|
-
--teach-measure: 52rem;
|
|
283
|
-
--teach-mast-h: 3.5rem;
|
|
284
|
-
--teach-shadow: 0 1px 2px rgba(20, 20, 20, 0.04);
|
|
285
|
-
--color-teach-accent-bg: color-mix(in srgb, var(--color-accent) 14%, var(--color-background));
|
|
286
|
-
|
|
287
|
-
/* Aliases for the retired teach palette. Every already-written lesson draws
|
|
288
|
-
its hand-authored diagrams with fill and stroke values pinned to these
|
|
289
|
-
names directly in the SVG markup, which this rewrite cannot reach without
|
|
290
|
-
editing lesson content the same pass that recovered the chrome was asked
|
|
291
|
-
not to touch. */
|
|
292
|
-
--panel: var(--color-surface);
|
|
293
|
-
--rule: var(--color-border);
|
|
294
|
-
--ink: var(--color-text-body);
|
|
295
|
-
--ink-soft: var(--color-text-secondary);
|
|
296
|
-
--ink-faint: var(--color-muted);
|
|
297
|
-
--accent: var(--color-accent);
|
|
298
|
-
--accent-bg: var(--color-teach-accent-bg);
|
|
299
|
-
|
|
300
|
-
${TEACH_SYNTAX_DARK}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
[data-theme='light'] {
|
|
304
|
-
${TEACH_SYNTAX_LIGHT}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
@media (prefers-color-scheme: dark) {
|
|
308
|
-
:root:not([data-theme='light']) {
|
|
309
|
-
--teach-shadow: none;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
:root[data-theme='dark'] {
|
|
314
|
-
--teach-shadow: none;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
html { scroll-padding-top: calc(var(--teach-mast-h) + 1rem); }
|
|
318
|
-
|
|
319
|
-
/* No padding at all on the body, so the sticky bar reaches both window edges
|
|
320
|
-
and the sidebar can travel the full height. The gutter moves onto \`main\` and
|
|
321
|
-
the bottom room onto \`.pane\`: a flex item's sticky containing block is the
|
|
322
|
-
flex container's content box, so padding here is space the sidebar could
|
|
323
|
-
never reach and it would ride up by exactly that much near the foot. */
|
|
324
|
-
body {
|
|
325
|
-
margin: 0;
|
|
326
|
-
padding: 0;
|
|
327
|
-
display: flex;
|
|
328
|
-
align-items: flex-start;
|
|
329
|
-
background: var(--color-background);
|
|
330
|
-
color: var(--color-text-body);
|
|
331
|
-
font-family: var(--teach-sans);
|
|
332
|
-
font-size: var(--t3);
|
|
333
|
-
line-height: 1.55;
|
|
334
|
-
-webkit-font-smoothing: antialiased;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/* The bar row and the text column resolve to the same 52rem content box, so a
|
|
338
|
-
listing page and a lesson start at the same left edge as the chrome above
|
|
339
|
-
them. A narrower \`.wide-body\` was what put the navigation and the content on
|
|
340
|
-
two different measures. */
|
|
341
|
-
main { box-sizing: border-box; max-width: calc(var(--teach-measure) + 3rem); margin: 0 auto; padding: 2.25rem 1.5rem 0; }
|
|
342
|
-
.wide-body { max-width: calc(var(--teach-measure) + 3rem); }
|
|
343
|
-
|
|
344
|
-
/* Everything but the sidebar. \`flow-root\` rather than a bare block, because a
|
|
345
|
-
bottom margin collapsing out of here makes the document taller than the flex
|
|
346
|
-
container and a sticky sidebar can only travel inside its containing block,
|
|
347
|
-
so it would ride up by exactly the escaped margin on a tall window. The
|
|
348
|
-
border box is what keeps \`min-height: 100vh\` plus the bottom room from
|
|
349
|
-
scrolling every short page by that padding. */
|
|
350
|
-
.pane {
|
|
351
|
-
flex: 1 1 auto;
|
|
352
|
-
min-width: 0;
|
|
353
|
-
display: flow-root;
|
|
354
|
-
box-sizing: border-box;
|
|
355
|
-
min-height: 100vh;
|
|
356
|
-
padding-bottom: 7rem;
|
|
357
|
-
border-left: 1px solid var(--color-border);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
html.sb-shut .pane { border-left: 0; }
|
|
361
|
-
|
|
362
|
-
.bar {
|
|
363
|
-
position: sticky;
|
|
364
|
-
top: 0;
|
|
365
|
-
z-index: 30;
|
|
366
|
-
background: var(--color-background);
|
|
367
|
-
border-bottom: 1px solid var(--color-border);
|
|
368
|
-
padding: 0 1.5rem;
|
|
369
|
-
cursor: default;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/* The reading bar rides the masthead's own bottom edge rather than arriving as
|
|
373
|
-
a second element. It reports position inside the lesson, which nothing
|
|
374
|
-
reported, where the retired track reported position in the course, which the
|
|
375
|
-
sidebar now reports. */
|
|
376
|
-
.bar::after {
|
|
377
|
-
content: "";
|
|
378
|
-
position: absolute;
|
|
379
|
-
left: 0;
|
|
380
|
-
bottom: -1px;
|
|
381
|
-
height: 2px;
|
|
382
|
-
width: var(--read, 0%);
|
|
383
|
-
background: var(--color-accent);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/* The height is the token rather than the sum of two paddings, so the sidebar's
|
|
387
|
-
own top row lands on the same seam the bar's bottom border draws. The row
|
|
388
|
-
spans the window rather than the reading measure: capped, the toggle and the
|
|
389
|
-
theme control floated in from the edges by an amount that changed per page. */
|
|
390
|
-
.mast {
|
|
391
|
-
max-width: none;
|
|
392
|
-
margin: 0;
|
|
393
|
-
min-height: var(--teach-mast-h);
|
|
394
|
-
padding: 0 0.9rem;
|
|
395
|
-
display: flex;
|
|
396
|
-
justify-content: space-between;
|
|
397
|
-
align-items: center;
|
|
398
|
-
gap: 0.55rem;
|
|
399
|
-
font-size: var(--t5);
|
|
400
|
-
color: var(--color-muted);
|
|
401
|
-
cursor: default;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
.mast a { color: var(--color-accent); text-decoration: none; font-weight: 600; }
|
|
405
|
-
.mast a:hover { text-decoration: underline; }
|
|
406
|
-
|
|
407
|
-
.nav {
|
|
408
|
-
width: 100%;
|
|
409
|
-
margin: 0;
|
|
410
|
-
display: flex;
|
|
411
|
-
gap: 0.6rem;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
.nav > a, .nav > span {
|
|
415
|
-
flex: 1;
|
|
416
|
-
padding: 0.65rem 0.95rem;
|
|
417
|
-
border: 1px solid var(--color-border);
|
|
418
|
-
border-radius: 8px;
|
|
419
|
-
text-decoration: none;
|
|
420
|
-
color: var(--color-text-body);
|
|
421
|
-
font-size: var(--t4);
|
|
422
|
-
font-weight: 600;
|
|
423
|
-
line-height: 1.3;
|
|
424
|
-
box-shadow: var(--teach-shadow);
|
|
425
|
-
transition: border-color 0.12s, background 0.12s;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
.nav > a:hover { border-color: var(--color-accent); background: var(--color-teach-accent-bg); }
|
|
429
|
-
.nav > .end { color: var(--color-muted); border-style: dashed; font-weight: 400; box-shadow: none; }
|
|
430
|
-
.nav > :only-child { flex: 0 1 calc(50% - 0.3rem); }
|
|
431
|
-
.nav > .to-next:only-child { margin-left: auto; }
|
|
432
|
-
|
|
433
|
-
.nav .lbl {
|
|
434
|
-
display: block;
|
|
435
|
-
font-size: var(--t6);
|
|
436
|
-
font-weight: 700;
|
|
437
|
-
letter-spacing: 0;
|
|
438
|
-
color: var(--color-muted);
|
|
439
|
-
margin-bottom: 0.15rem;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
.nav > a:hover .lbl { color: var(--color-accent); }
|
|
443
|
-
.nav .to-next { text-align: right; }
|
|
444
|
-
.nav .to-index { flex: 0 0 auto; text-align: center; }
|
|
445
|
-
|
|
446
|
-
/* ---- Masthead contents ---- */
|
|
447
|
-
|
|
448
|
-
/* Sticky position and stacking both live on \`.bar\` now, so nothing here
|
|
449
|
-
restates either. */
|
|
450
|
-
/* A breadcrumb rather than a row of links. The gap tightens because the
|
|
451
|
-
separators now carry the spacing the gap used to. */
|
|
452
|
-
.mast-left { display: flex; align-items: center; gap: 0.3rem; min-width: 0; flex-wrap: wrap; cursor: default; }
|
|
453
|
-
|
|
454
|
-
/* \`--rule\` measured 1.27:1 here. It is a border token, and a border is a
|
|
455
|
-
shape a reader infers rather than a glyph they resolve, so it is the wrong
|
|
456
|
-
value for a character even a decorative one. */
|
|
457
|
-
.crumb-sep {
|
|
458
|
-
color: var(--color-muted);
|
|
459
|
-
font-size: var(--t4);
|
|
460
|
-
margin: 0 0.1rem;
|
|
461
|
-
user-select: none;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/* One arrow, on the first segment, since that is the only one that goes up. */
|
|
465
|
-
.crumb-back {
|
|
466
|
-
margin-right: 0.3rem;
|
|
467
|
-
font-size: var(--t4);
|
|
468
|
-
line-height: 1;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
.crumb { display: inline-flex; align-items: center; }
|
|
472
|
-
|
|
473
|
-
/* The label and its caret are one component with two zones, like a split
|
|
474
|
-
button: the label navigates, the caret opens the menu, and one surface fills
|
|
475
|
-
for both so the pair reads as a single chip rather than two loose pieces.
|
|
476
|
-
The pointer covers the gap between them, which is interactive on both sides. */
|
|
477
|
-
.crumb-item {
|
|
478
|
-
display: inline-flex;
|
|
479
|
-
align-items: center;
|
|
480
|
-
gap: 0;
|
|
481
|
-
border-radius: 7px;
|
|
482
|
-
padding: 0.1rem 0.15rem 0.1rem 0.35rem;
|
|
483
|
-
cursor: pointer;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
.crumb-item:has(details.jump):hover,
|
|
487
|
-
.crumb-item:has(details.jump[open]) { background: var(--color-chrome); }
|
|
488
|
-
|
|
489
|
-
/* Accent reports where you are. A crumb is navigation you can take, so it is
|
|
490
|
-
neutral, and \`.mast a\` keeps the accent for any other bar link. */
|
|
491
|
-
.mast a.crumb { color: var(--color-text-secondary); padding: 0.1rem 0.15rem 0.1rem 0; }
|
|
492
|
-
.mast a.crumb:hover { text-decoration: none; }
|
|
493
|
-
.crumb-item:hover a.crumb { color: var(--color-text); }
|
|
494
|
-
|
|
495
|
-
.crumb-here { color: var(--color-text); font-weight: 650; }
|
|
496
|
-
|
|
497
|
-
/* \`align-items: center\` centres boxes, and a label's line box carries
|
|
498
|
-
half-leading above the cap and descender room below, so its capitals sit
|
|
499
|
-
above its own centre by an amount that scales with the font size. Trimming
|
|
500
|
-
the label to its cap band fixes the box rather than nudging the mark. It
|
|
501
|
-
lands on a span holding the text alone, since the link's own \`inline-flex\`
|
|
502
|
-
ignores it. Firefox ships no \`text-box\` and keeps the caret a pixel low. */
|
|
503
|
-
.crumb-t, .crumb-here, .sb-ws .ws-name {
|
|
504
|
-
display: block;
|
|
505
|
-
text-box: trim-both cap alphabetic;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/* The caret is the whole control, so it carries the 24 pixel minimum on its
|
|
509
|
-
own rather than inheriting a label's width. It fills nothing itself, since
|
|
510
|
-
the chip around it does. */
|
|
511
|
-
.crumb-item > .jump > summary {
|
|
512
|
-
min-width: 1.5rem;
|
|
513
|
-
min-height: 1.5rem;
|
|
514
|
-
display: inline-flex;
|
|
515
|
-
align-items: center;
|
|
516
|
-
justify-content: center;
|
|
517
|
-
padding: 0.15rem 0.3rem 0.15rem 0;
|
|
518
|
-
border: 0;
|
|
519
|
-
background: transparent;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
/* A bar link is its own tap target rather than a line of text, so it carries
|
|
523
|
-
the 24 pixel minimum the keyboard rule sets. */
|
|
524
|
-
.mast a,
|
|
525
|
-
.mast summary {
|
|
526
|
-
min-height: 1.6rem;
|
|
527
|
-
display: inline-flex;
|
|
528
|
-
align-items: center;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
.mast .pos {
|
|
532
|
-
font-family: var(--teach-mono);
|
|
533
|
-
font-size: 0.75rem;
|
|
534
|
-
color: var(--color-muted);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
.mast-right { display: flex; align-items: center; gap: 0.2rem; margin-left: auto; }
|
|
538
|
-
|
|
539
|
-
/* No border, at the fold control's size and radius, so every control on the
|
|
540
|
-
bar is drawn one way. */
|
|
541
|
-
.theme {
|
|
542
|
-
width: 1.75rem;
|
|
543
|
-
height: 1.75rem;
|
|
544
|
-
display: inline-flex;
|
|
545
|
-
align-items: center;
|
|
546
|
-
justify-content: center;
|
|
547
|
-
padding: 0;
|
|
548
|
-
border: 0;
|
|
549
|
-
border-radius: 6px;
|
|
550
|
-
background: transparent;
|
|
551
|
-
color: var(--color-muted);
|
|
552
|
-
cursor: pointer;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
.theme:hover { color: var(--color-text); background: var(--color-chrome); }
|
|
556
|
-
.theme svg { width: 0.95rem; height: 0.95rem; display: block; }
|
|
557
|
-
.theme .moon { display: none; }
|
|
558
|
-
:root[data-theme="dark"] .theme .sun { display: none; }
|
|
559
|
-
:root[data-theme="dark"] .theme .moon { display: block; }
|
|
560
|
-
|
|
561
|
-
@media (prefers-color-scheme: dark) {
|
|
562
|
-
:root:not([data-theme="light"]) .theme .sun { display: none; }
|
|
563
|
-
:root:not([data-theme="light"]) .theme .moon { display: block; }
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/* ---- The course sidebar ---- */
|
|
567
|
-
|
|
568
|
-
/* A segmented progress strip used to sit here, one \`flex: 1\` segment per
|
|
569
|
-
lesson, and it stopped working at the scale a reader most needs it: fifty
|
|
570
|
-
lessons rendered as a row of dots. The sidebar reports course position
|
|
571
|
-
instead, at any length, and the bar reports position inside the lesson. */
|
|
572
|
-
|
|
573
|
-
/* The toggle in the masthead, left of the breadcrumb. */
|
|
574
|
-
.sb-fold {
|
|
575
|
-
flex: none;
|
|
576
|
-
width: 1.75rem;
|
|
577
|
-
height: 1.75rem;
|
|
578
|
-
margin-right: 0.35rem;
|
|
579
|
-
font: inherit;
|
|
580
|
-
font-size: var(--t5);
|
|
581
|
-
line-height: 1;
|
|
582
|
-
color: var(--color-muted);
|
|
583
|
-
background: transparent;
|
|
584
|
-
border: 0;
|
|
585
|
-
border-radius: 6px;
|
|
586
|
-
cursor: pointer;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
.sb-fold:hover { color: var(--color-text); background: var(--color-chrome); }
|
|
590
|
-
|
|
591
|
-
.sb {
|
|
592
|
-
flex: 0 0 var(--sb-w, 16rem);
|
|
593
|
-
width: var(--sb-w, 16rem);
|
|
594
|
-
position: sticky;
|
|
595
|
-
top: 0;
|
|
596
|
-
align-self: flex-start;
|
|
597
|
-
height: 100vh;
|
|
598
|
-
display: flex;
|
|
599
|
-
flex-direction: column;
|
|
600
|
-
overflow: visible;
|
|
601
|
-
background: transparent;
|
|
602
|
-
font-family: var(--teach-sans);
|
|
603
|
-
transition: flex-basis 0.14s, width 0.14s;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
html.sb-shut .sb { flex-basis: 0; width: 0; overflow: hidden; }
|
|
607
|
-
|
|
608
|
-
.sb-top {
|
|
609
|
-
flex: none;
|
|
610
|
-
display: flex;
|
|
611
|
-
align-items: center;
|
|
612
|
-
height: var(--teach-mast-h);
|
|
613
|
-
padding: 0 0.95rem;
|
|
614
|
-
border-bottom: 1px solid var(--color-border);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
.sb-ws { position: relative; }
|
|
618
|
-
|
|
619
|
-
.sb-ws > summary {
|
|
620
|
-
list-style: none;
|
|
621
|
-
cursor: pointer;
|
|
622
|
-
display: inline-flex;
|
|
623
|
-
align-items: center;
|
|
624
|
-
gap: 0.35rem;
|
|
625
|
-
padding: 0.25rem 0.3rem;
|
|
626
|
-
border-radius: 6px;
|
|
627
|
-
font-size: var(--t4);
|
|
628
|
-
font-weight: 650;
|
|
629
|
-
color: var(--color-text);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
.sb-ws > summary::-webkit-details-marker { display: none; }
|
|
633
|
-
.sb-ws > summary:hover, .sb-ws[open] > summary { background: var(--color-chrome); color: var(--color-text); }
|
|
634
|
-
.sb-ws .car { color: var(--color-muted); display: inline-flex; align-items: center; }
|
|
635
|
-
|
|
636
|
-
/* The top band is as tall as the masthead and a mission title can run to a
|
|
637
|
-
sentence, so the name stops at two lines rather than overflowing the rule.
|
|
638
|
-
Nav carries the whole title in the element's \`title\` attribute. The cap
|
|
639
|
-
trim the crumb labels share is dropped here, since it ends the box at the
|
|
640
|
-
last baseline and the clip would cut every descender on the second line. */
|
|
641
|
-
.sb-ws { min-width: 0; }
|
|
642
|
-
.sb-ws .ws-name {
|
|
643
|
-
display: -webkit-box;
|
|
644
|
-
-webkit-box-orient: vertical;
|
|
645
|
-
-webkit-line-clamp: 2;
|
|
646
|
-
line-clamp: 2;
|
|
647
|
-
overflow: hidden;
|
|
648
|
-
min-width: 0;
|
|
649
|
-
line-height: 1.2;
|
|
650
|
-
text-box: normal;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
.sb-wl {
|
|
654
|
-
position: absolute;
|
|
655
|
-
top: calc(100% + 0.35rem);
|
|
656
|
-
left: 0;
|
|
657
|
-
z-index: 40;
|
|
658
|
-
min-width: 13rem;
|
|
659
|
-
background: var(--color-background);
|
|
660
|
-
border: 1px solid var(--color-border);
|
|
661
|
-
border-radius: 9px;
|
|
662
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
|
|
663
|
-
padding: 0.3rem;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
:root[data-theme="dark"] .sb-wl { box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5); }
|
|
667
|
-
|
|
668
|
-
/* The popover chrome comes from \`.sb-wl\`, so the list nested inside it drops
|
|
669
|
-
its own rather than drawing a second border inside the first. */
|
|
670
|
-
.sb-wl .jump-list {
|
|
671
|
-
position: static;
|
|
672
|
-
min-width: 0;
|
|
673
|
-
max-width: none;
|
|
674
|
-
border: 0;
|
|
675
|
-
box-shadow: none;
|
|
676
|
-
padding: 0;
|
|
677
|
-
background: transparent;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
.sb-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-bottom: 1rem; }
|
|
681
|
-
|
|
682
|
-
.sb-meta {
|
|
683
|
-
padding: 1rem 1.25rem 0.55rem;
|
|
684
|
-
color: var(--color-muted);
|
|
685
|
-
font-size: var(--t6);
|
|
686
|
-
font-variant-numeric: tabular-nums;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
.sb-filter { padding: 0 0.95rem 0.6rem; }
|
|
690
|
-
|
|
691
|
-
.sb-filter input {
|
|
692
|
-
width: 100%;
|
|
693
|
-
box-sizing: border-box;
|
|
694
|
-
font: inherit;
|
|
695
|
-
font-size: var(--t6);
|
|
696
|
-
color: var(--color-text);
|
|
697
|
-
background: transparent;
|
|
698
|
-
border: 1px solid var(--color-border);
|
|
699
|
-
border-radius: 7px;
|
|
700
|
-
padding: 0.3rem 0.55rem;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
.sb-filter input:focus { outline: none; border-color: var(--color-accent); }
|
|
704
|
-
|
|
705
|
-
.sb-list { list-style: none; margin: 0; padding: 0 0.7rem; }
|
|
706
|
-
.sb-list li { margin: 0; }
|
|
707
|
-
.sb-list li.hide { display: none; }
|
|
708
|
-
|
|
709
|
-
.sb-l {
|
|
710
|
-
position: relative;
|
|
711
|
-
display: flex;
|
|
712
|
-
gap: 0.55rem;
|
|
713
|
-
align-items: baseline;
|
|
714
|
-
padding: 0.38rem 0.55rem;
|
|
715
|
-
border-radius: 6px;
|
|
716
|
-
text-decoration: none;
|
|
717
|
-
color: var(--color-text-body);
|
|
718
|
-
font-size: var(--t5);
|
|
719
|
-
line-height: 1.45;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
.sb-l:hover { background: var(--color-chrome); color: var(--color-text); }
|
|
723
|
-
|
|
724
|
-
.sb-n {
|
|
725
|
-
flex: none;
|
|
726
|
-
font-family: var(--teach-mono);
|
|
727
|
-
font-size: var(--t6);
|
|
728
|
-
color: var(--color-muted);
|
|
729
|
-
font-variant-numeric: tabular-nums;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
.sb-on { color: var(--color-text); font-weight: 650; }
|
|
733
|
-
|
|
734
|
-
.sb-on::before {
|
|
735
|
-
content: "";
|
|
736
|
-
position: absolute;
|
|
737
|
-
left: -0.6rem;
|
|
738
|
-
top: 0.28rem;
|
|
739
|
-
bottom: 0.28rem;
|
|
740
|
-
width: 2px;
|
|
741
|
-
border-radius: 2px;
|
|
742
|
-
background: var(--color-accent);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
.sb-on .sb-n { color: var(--color-accent); }
|
|
746
|
-
|
|
747
|
-
.sb-empty { padding: 0.3rem 1.2rem; color: var(--color-muted); font-size: var(--t6); }
|
|
748
|
-
|
|
749
|
-
.sb-foot {
|
|
750
|
-
flex: none;
|
|
751
|
-
padding: 0.7rem 1.25rem;
|
|
752
|
-
border-top: 1px solid var(--color-border);
|
|
753
|
-
color: var(--color-muted);
|
|
754
|
-
font-size: var(--t6);
|
|
755
|
-
font-variant-numeric: tabular-nums;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
/* The panel is resizable between a width that still fits a lesson title and one
|
|
759
|
-
that would start eating the reading measure. */
|
|
760
|
-
.sb-grip {
|
|
761
|
-
position: absolute;
|
|
762
|
-
top: 0;
|
|
763
|
-
bottom: 0;
|
|
764
|
-
right: -3px;
|
|
765
|
-
width: 7px;
|
|
766
|
-
z-index: 25;
|
|
767
|
-
padding: 0;
|
|
768
|
-
border: 0;
|
|
769
|
-
background: transparent;
|
|
770
|
-
cursor: col-resize;
|
|
771
|
-
touch-action: none;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
.sb-grip::before {
|
|
775
|
-
content: "";
|
|
776
|
-
position: absolute;
|
|
777
|
-
left: 3px;
|
|
778
|
-
top: 50%;
|
|
779
|
-
transform: translateY(-50%);
|
|
780
|
-
width: 1px;
|
|
781
|
-
height: 2.2rem;
|
|
782
|
-
border-radius: 1px;
|
|
783
|
-
background: var(--color-border);
|
|
784
|
-
opacity: 0;
|
|
785
|
-
transition: opacity 0.12s;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
.sb-grip:hover::before, .sb-grip:focus-visible::before, html.sb-drag .sb-grip::before {
|
|
789
|
-
opacity: 1;
|
|
790
|
-
background: var(--color-accent);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
.sb-grip:focus-visible { outline: none; }
|
|
794
|
-
html.sb-drag { cursor: col-resize; user-select: none; }
|
|
795
|
-
html.sb-shut .sb-grip { display: none; }
|
|
796
|
-
|
|
797
|
-
/* Both belong to the overlay and the script appends both on every page, so
|
|
798
|
-
each is hidden until the breakpoint hands it a job. Without this the close
|
|
799
|
-
control renders as an unstyled button stretched across the column, and the
|
|
800
|
-
scrim becomes a flex item of the body. */
|
|
801
|
-
.sb-close, .sb-scrim { display: none; }
|
|
802
|
-
|
|
803
|
-
/* ---- Footer navigation, the only place it appears ---- */
|
|
804
|
-
|
|
805
|
-
.nav {
|
|
806
|
-
max-width: calc(var(--teach-measure) + 3rem);
|
|
807
|
-
margin: 4rem auto 0;
|
|
808
|
-
padding: 0 1.5rem;
|
|
809
|
-
box-sizing: border-box;
|
|
810
|
-
align-items: stretch;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
.nav > a, .nav > span { display: flex; flex-direction: column; justify-content: center; }
|
|
814
|
-
|
|
815
|
-
.nav .arrow {
|
|
816
|
-
font-family: var(--teach-mono);
|
|
817
|
-
color: var(--color-muted);
|
|
818
|
-
padding-right: 0.35rem;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
.nav > a:hover .arrow { color: var(--color-accent); }
|
|
822
|
-
|
|
823
|
-
/* ---- Jump menu: random access, where the foot nav is sequential ---- */
|
|
824
|
-
|
|
825
|
-
.jump { position: relative; }
|
|
826
|
-
|
|
827
|
-
.jump summary {
|
|
828
|
-
list-style: none;
|
|
829
|
-
cursor: pointer;
|
|
830
|
-
font-family: var(--teach-mono);
|
|
831
|
-
font-size: 0.75rem;
|
|
832
|
-
color: var(--color-muted);
|
|
833
|
-
padding: 0.25rem 0.55rem;
|
|
834
|
-
border: 1px solid transparent;
|
|
835
|
-
border-radius: 6px;
|
|
836
|
-
display: inline-flex;
|
|
837
|
-
align-items: center;
|
|
838
|
-
gap: 0.4rem;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
.jump summary::-webkit-details-marker { display: none; }
|
|
842
|
-
/* The caret says "this is a menu" and nothing else, so it is drawn at full
|
|
843
|
-
strength in every state and stays muted. A reveal on hover hid the only
|
|
844
|
-
route into the menu from a reader who cannot hover, and an affordance is not
|
|
845
|
-
a state, so it takes no accent on hover or while open. */
|
|
846
|
-
.jump summary .caret { color: var(--color-muted); }
|
|
847
|
-
|
|
848
|
-
.jump-list {
|
|
849
|
-
position: absolute;
|
|
850
|
-
top: calc(100% + 0.4rem);
|
|
851
|
-
left: 0;
|
|
852
|
-
z-index: 20;
|
|
853
|
-
background: var(--color-background);
|
|
854
|
-
border: 1px solid var(--color-border);
|
|
855
|
-
border-radius: 9px;
|
|
856
|
-
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
|
|
857
|
-
padding: 0.25rem;
|
|
858
|
-
list-style: none;
|
|
859
|
-
margin: 0;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
:root[data-theme="dark"] .jump-list { box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5); }
|
|
863
|
-
|
|
864
|
-
/* One row recipe wherever the menu is mounted, on the steps the lesson list
|
|
865
|
-
beside it already reads: the label at \`--t5\` and the numeral at \`--t6\` mono.
|
|
866
|
-
The weight is restated because \`.mast a\` sets 600 for the bar's own links. */
|
|
867
|
-
.jump-list a {
|
|
868
|
-
display: grid;
|
|
869
|
-
grid-template-columns: 1.65rem 1fr auto;
|
|
870
|
-
gap: 0.55rem;
|
|
871
|
-
align-items: center;
|
|
872
|
-
padding: 0.38rem 0.55rem;
|
|
873
|
-
border-radius: 6px;
|
|
874
|
-
text-decoration: none;
|
|
875
|
-
color: var(--color-text-body);
|
|
876
|
-
font-size: var(--t5);
|
|
877
|
-
font-weight: 400;
|
|
878
|
-
line-height: 1.45;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
.jump-list a:hover { background: var(--color-chrome); }
|
|
882
|
-
.jump-list .n { font-family: var(--teach-mono); font-size: var(--t6); color: var(--color-muted); }
|
|
883
|
-
/* The third column used to hold a status dot that read the same on every row,
|
|
884
|
-
so it reported nothing. It is a per-entry trailing slot now: the workspace
|
|
885
|
-
menu puts a lesson count there and the lesson menu leaves it empty. */
|
|
886
|
-
.jump-list .ct {
|
|
887
|
-
font-family: var(--teach-mono);
|
|
888
|
-
font-size: var(--t6);
|
|
889
|
-
color: var(--color-muted);
|
|
890
|
-
font-variant-numeric: tabular-nums;
|
|
891
|
-
text-align: right;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
.jump-list li.at a { background: var(--color-teach-accent-bg); }
|
|
895
|
-
.jump-list li.at .n { color: var(--color-accent); }
|
|
896
|
-
.jump-list li.soon a { color: var(--color-muted); }
|
|
897
|
-
|
|
898
|
-
.jump.ws summary { color: var(--color-text-secondary); }
|
|
899
|
-
|
|
900
|
-
.jump-list li.all { border-top: 1px solid var(--color-border); margin-top: 0.25rem; padding-top: 0.25rem; }
|
|
901
|
-
.jump-list li.all a { color: var(--color-accent); }
|
|
902
|
-
.jump-list li.all .n { color: var(--color-accent); }
|
|
903
|
-
|
|
904
|
-
/* The menu fits its content above an 11rem floor rather than padding three
|
|
905
|
-
short titles out to a wide panel. It is anchored to its summary, so the
|
|
906
|
-
clamp keeps a long row wrapping on screen without a breakpoint. */
|
|
907
|
-
.jump-list {
|
|
908
|
-
min-width: min(11rem, calc(100vw - 2rem));
|
|
909
|
-
max-width: calc(100vw - 2rem);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
/* The route back to the full listing sits above the workspaces it lists. */
|
|
913
|
-
.jump-list li.head a { color: var(--color-text-secondary); font-weight: 600; }
|
|
914
|
-
.jump-list li.head { border-bottom: 1px solid var(--color-border); margin-bottom: 0.3rem; padding-bottom: 0.3rem; }
|
|
915
|
-
|
|
916
|
-
/* An unwritten lesson and a stub workspace are inert, so they render inert.
|
|
917
|
-
Reduced opacity alone reads as a rendering fault, so the cursor says it too. */
|
|
918
|
-
.jump-list li.soon a,
|
|
919
|
-
.toc li.soon a,
|
|
920
|
-
[aria-disabled="true"] {
|
|
921
|
-
opacity: 0.55;
|
|
922
|
-
cursor: not-allowed;
|
|
923
|
-
pointer-events: none;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
@media (max-width: 640px) {
|
|
927
|
-
.bar { padding: 0 1rem; }
|
|
928
|
-
main { padding-left: 1rem; padding-right: 1rem; }
|
|
929
|
-
.mast { font-size: 0.75rem; }
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
@media (max-width: 640px) {
|
|
933
|
-
.jump-list {
|
|
934
|
-
position: fixed;
|
|
935
|
-
left: 1rem;
|
|
936
|
-
right: 1rem;
|
|
937
|
-
top: auto;
|
|
938
|
-
min-width: 0;
|
|
939
|
-
max-width: none;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
/* ---- What gives way below 1100px ---- */
|
|
944
|
-
|
|
945
|
-
/* Hiding the sidebar outright leaves the toggle painted exactly where it cannot
|
|
946
|
-
act, and it deletes the filter and the per-lesson outline, which the
|
|
947
|
-
breadcrumb caret does not carry. The panel becomes an overlay instead, and it
|
|
948
|
-
states its own ground because \`.sb\` is transparent by design for a column
|
|
949
|
-
sitting on the page rather than floating over the lesson. */
|
|
950
|
-
@media (max-width: ${TEACH_SIDEBAR_BREAKPOINT}px) {
|
|
951
|
-
.pane { border-left: 0; }
|
|
952
|
-
|
|
953
|
-
.sb {
|
|
954
|
-
position: fixed;
|
|
955
|
-
top: 0;
|
|
956
|
-
left: 0;
|
|
957
|
-
bottom: 0;
|
|
958
|
-
z-index: 60;
|
|
959
|
-
width: min(21rem, 86vw);
|
|
960
|
-
height: 100dvh;
|
|
961
|
-
overflow-y: auto;
|
|
962
|
-
background: var(--color-background);
|
|
963
|
-
border-right: 1px solid var(--color-border);
|
|
964
|
-
box-shadow: 0 0 40px rgb(0 0 0 / 35%);
|
|
965
|
-
transform: translateX(-100%);
|
|
966
|
-
transition: transform 0.18s;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
html:not(.sb-shut) .sb { transform: none; }
|
|
970
|
-
|
|
971
|
-
/* The wide rule collapses a shut panel to \`width: 0\` and outranks a bare
|
|
972
|
-
\`.sb\` in here, which produced a one-pixel panel whose contents spilled out
|
|
973
|
-
and intercepted every click on the toggle. The shut state restates the
|
|
974
|
-
width so the transform is what moves the panel off screen. */
|
|
975
|
-
html.sb-shut .sb { width: min(21rem, 86vw); flex-basis: auto; overflow-y: auto; }
|
|
976
|
-
|
|
977
|
-
/* A transform moves the panel out of sight and leaves every control inside it
|
|
978
|
-
in the tab order, so the first Tab from a shut page landed on the workspace
|
|
979
|
-
switcher inside an invisible panel. Visibility takes it out of the
|
|
980
|
-
sequence, delayed on the way out so the slide still shows. */
|
|
981
|
-
html.sb-shut .sb { visibility: hidden; transition: transform 0.18s, visibility 0s linear 0.18s; }
|
|
982
|
-
html:not(.sb-shut) .sb { visibility: visible; transition: transform 0.18s, visibility 0s; }
|
|
983
|
-
|
|
984
|
-
.sb-grip { display: none; }
|
|
985
|
-
|
|
986
|
-
/* The panel covers the masthead and the toggle that opened it, so it carries
|
|
987
|
-
its own way out rather than leaving the scrim as the only route on a phone.
|
|
988
|
-
It carries no border, at 1.75rem to match \`.sb-fold\` and \`.theme\`, which also clears
|
|
989
|
-
the 24 pixel pointer target floor. */
|
|
990
|
-
.sb-close {
|
|
991
|
-
display: inline-flex;
|
|
992
|
-
align-items: center;
|
|
993
|
-
justify-content: center;
|
|
994
|
-
position: absolute;
|
|
995
|
-
top: 0.55rem;
|
|
996
|
-
right: 0.6rem;
|
|
997
|
-
z-index: 61;
|
|
998
|
-
appearance: none;
|
|
999
|
-
width: 1.75rem;
|
|
1000
|
-
height: 1.75rem;
|
|
1001
|
-
font: inherit;
|
|
1002
|
-
font-size: var(--t5);
|
|
1003
|
-
line-height: 1;
|
|
1004
|
-
color: var(--color-muted);
|
|
1005
|
-
background: transparent;
|
|
1006
|
-
border: 0;
|
|
1007
|
-
border-radius: 6px;
|
|
1008
|
-
cursor: pointer;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
.sb-close:hover { color: var(--color-text); background: var(--color-chrome); }
|
|
1012
|
-
|
|
1013
|
-
.sb-scrim {
|
|
1014
|
-
display: block;
|
|
1015
|
-
position: fixed;
|
|
1016
|
-
inset: 0;
|
|
1017
|
-
z-index: 55;
|
|
1018
|
-
background: rgb(0 0 0 / 45%);
|
|
1019
|
-
opacity: 0;
|
|
1020
|
-
pointer-events: none;
|
|
1021
|
-
transition: opacity 0.18s;
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
html:not(.sb-shut) .sb-scrim { opacity: 1; pointer-events: auto; }
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
@media (prefers-reduced-motion: reduce) {
|
|
1028
|
-
.sb, .sb-scrim, .sb-grip::before { transition: none; }
|
|
1029
|
-
}`,
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
const TEACH_ARTICLE: Component = {
|
|
1033
|
-
name: 'teach-article',
|
|
1034
|
-
note: [
|
|
1035
|
-
'The base reading-column styles a teach page renders prose in: type, panels,',
|
|
1036
|
-
'code, callouts, tables, the workspace contents listing, and the focus ring.',
|
|
1037
|
-
'Recovered alongside teach-chrome from the same two files.',
|
|
1038
|
-
].join('\n '),
|
|
1039
|
-
reads: [
|
|
1040
|
-
'--color-border',
|
|
1041
|
-
'--color-surface',
|
|
1042
|
-
'--color-text',
|
|
1043
|
-
'--color-text-body',
|
|
1044
|
-
'--color-text-secondary',
|
|
1045
|
-
'--color-muted',
|
|
1046
|
-
'--color-accent',
|
|
1047
|
-
'--teach-hand',
|
|
1048
|
-
'--teach-mono',
|
|
1049
|
-
...TEACH_SYNTAX.map(({ name }) => syntaxProperty(name)),
|
|
1050
|
-
'--t1',
|
|
1051
|
-
'--t2',
|
|
1052
|
-
'--t3',
|
|
1053
|
-
'--t4',
|
|
1054
|
-
'--t5',
|
|
1055
|
-
'--t6',
|
|
1056
|
-
],
|
|
1057
|
-
rules: `/* ---- Type ---- */
|
|
1058
|
-
|
|
1059
|
-
h1 {
|
|
1060
|
-
font-size: var(--t1);
|
|
1061
|
-
line-height: 1.1;
|
|
1062
|
-
font-weight: 800;
|
|
1063
|
-
letter-spacing: -0.022em;
|
|
1064
|
-
margin: 0 0 0.55rem;
|
|
1065
|
-
text-wrap: balance;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
h2 {
|
|
1069
|
-
font-size: var(--t2);
|
|
1070
|
-
line-height: 1.3;
|
|
1071
|
-
font-weight: 700;
|
|
1072
|
-
letter-spacing: -0.012em;
|
|
1073
|
-
margin: 3rem 0 0.85rem;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
h3 { font-size: var(--t3); line-height: 1.55; font-weight: 700; margin: 1.9rem 0 0.4rem; }
|
|
1077
|
-
|
|
1078
|
-
p { margin: 0 0 1.15rem; }
|
|
1079
|
-
|
|
1080
|
-
.lede {
|
|
1081
|
-
font-size: var(--t3);
|
|
1082
|
-
line-height: 1.55;
|
|
1083
|
-
color: var(--color-text-secondary);
|
|
1084
|
-
margin-bottom: 2rem;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
a { color: var(--color-accent); }
|
|
1088
|
-
|
|
1089
|
-
em { font-style: italic; }
|
|
1090
|
-
|
|
1091
|
-
/* ---- Panels ---- */
|
|
1092
|
-
|
|
1093
|
-
.assumes, .progress {
|
|
1094
|
-
font-size: var(--t5);
|
|
1095
|
-
line-height: 1.6;
|
|
1096
|
-
color: var(--color-text-secondary);
|
|
1097
|
-
background: var(--color-surface);
|
|
1098
|
-
border-radius: 7px;
|
|
1099
|
-
padding: 0.9rem 1.15rem;
|
|
1100
|
-
margin: 0 0 2.5rem;
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
.assumes b, .progress b {
|
|
1104
|
-
display: block;
|
|
1105
|
-
color: var(--color-muted);
|
|
1106
|
-
font-weight: 700;
|
|
1107
|
-
letter-spacing: 0;
|
|
1108
|
-
font-size: var(--t6);
|
|
1109
|
-
margin-bottom: 0.2rem;
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
/* ---- Code ---- */
|
|
1113
|
-
|
|
1114
|
-
code {
|
|
1115
|
-
font-family: var(--teach-mono);
|
|
1116
|
-
font-size: 0.85em;
|
|
1117
|
-
background: var(--color-surface);
|
|
1118
|
-
padding: 0.13em 0.38em;
|
|
1119
|
-
border-radius: 4px;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
pre {
|
|
1123
|
-
font-family: var(--teach-mono);
|
|
1124
|
-
font-size: var(--t5);
|
|
1125
|
-
line-height: 1.6;
|
|
1126
|
-
background: var(--color-surface);
|
|
1127
|
-
padding: 1.15rem 1.3rem;
|
|
1128
|
-
border-radius: 7px;
|
|
1129
|
-
overflow-x: auto;
|
|
1130
|
-
margin: 0 0 1.5rem;
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
pre code { background: none; padding: 0; font-size: 1em; }
|
|
1134
|
-
|
|
1135
|
-
/* Build-time highlight.js spans. Every class reads a --teach-syntax-* token,
|
|
1136
|
-
so the dark and light values switch with the ground rather than per rule. */
|
|
1137
|
-
.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-built_in, .hljs-doctag { color: var(--teach-syntax-keyword); }
|
|
1138
|
-
.hljs-string, .hljs-regexp, .hljs-addition, .hljs-template-tag, .hljs-symbol { color: var(--teach-syntax-string); }
|
|
1139
|
-
.hljs-number, .hljs-type, .hljs-selector-class, .hljs-selector-id { color: var(--teach-syntax-number); }
|
|
1140
|
-
.hljs-title, .hljs-section, .hljs-name, .hljs-bullet { color: var(--teach-syntax-title); }
|
|
1141
|
-
.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable, .hljs-property { color: var(--teach-syntax-attr); }
|
|
1142
|
-
.hljs-comment, .hljs-quote { color: var(--teach-syntax-comment); font-style: italic; }
|
|
1143
|
-
.hljs-meta, .hljs-deletion, .hljs-subst { color: var(--teach-syntax-meta); }
|
|
1144
|
-
.hljs-emphasis { font-style: italic; }
|
|
1145
|
-
.hljs-strong { font-weight: 700; }
|
|
1146
|
-
|
|
1147
|
-
/* ---- Callout ---- */
|
|
1148
|
-
|
|
1149
|
-
.hard {
|
|
1150
|
-
background: var(--color-teach-accent-bg);
|
|
1151
|
-
border-radius: 7px;
|
|
1152
|
-
padding: 1.1rem 1.3rem;
|
|
1153
|
-
margin: 2rem 0;
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
.hard-label {
|
|
1157
|
-
font-family: var(--teach-hand);
|
|
1158
|
-
font-size: var(--t3);
|
|
1159
|
-
color: var(--color-accent);
|
|
1160
|
-
display: block;
|
|
1161
|
-
margin-bottom: 0.35rem;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
.hard p:last-child { margin-bottom: 0; }
|
|
1165
|
-
|
|
1166
|
-
/* ---- Tables ---- */
|
|
1167
|
-
|
|
1168
|
-
table { width: 100%; border-collapse: collapse; font-size: var(--t4); margin: 0 0 1.5rem; }
|
|
1169
|
-
th, td { text-align: right; padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--color-border); }
|
|
1170
|
-
th:first-child, td:first-child { text-align: left; }
|
|
1171
|
-
|
|
1172
|
-
th {
|
|
1173
|
-
font-weight: 700;
|
|
1174
|
-
font-size: var(--t5);
|
|
1175
|
-
letter-spacing: 0;
|
|
1176
|
-
color: var(--color-muted);
|
|
1177
|
-
border-bottom-color: var(--color-border);
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
tbody tr.mark td { background: var(--color-teach-accent-bg); }
|
|
1181
|
-
.scroll { overflow-x: auto; }
|
|
1182
|
-
|
|
1183
|
-
/* ---- Contents ---- */
|
|
1184
|
-
|
|
1185
|
-
.toc { list-style: none; padding: 0; margin: 0 0 2.5rem; }
|
|
1186
|
-
.toc li + li { border-top: 1px solid var(--color-border); }
|
|
1187
|
-
|
|
1188
|
-
.toc a {
|
|
1189
|
-
display: grid;
|
|
1190
|
-
grid-template-columns: 2.25rem 1fr auto;
|
|
1191
|
-
gap: 0 1.1rem;
|
|
1192
|
-
align-items: baseline;
|
|
1193
|
-
padding: 1rem 0.7rem;
|
|
1194
|
-
text-decoration: none;
|
|
1195
|
-
color: var(--color-text-body);
|
|
1196
|
-
border-radius: 7px;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
.toc a:hover { background: var(--color-surface); }
|
|
1200
|
-
.toc .num { font-family: var(--teach-mono); font-size: var(--t5); color: var(--color-muted); }
|
|
1201
|
-
.toc b { font-size: var(--t3); font-weight: 700; letter-spacing: -0.012em; }
|
|
1202
|
-
|
|
1203
|
-
.toc .blurb {
|
|
1204
|
-
grid-column: 2;
|
|
1205
|
-
font-size: var(--t4);
|
|
1206
|
-
line-height: 1.55;
|
|
1207
|
-
color: var(--color-text-secondary);
|
|
1208
|
-
margin-top: 0.1rem;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
/* A word, not a pill and not a dot. A mark reading the same on every live row
|
|
1212
|
-
reports nothing, so the dot retired here as it did from the jump menus.
|
|
1213
|
-
Sentence case and no tracking, per the UI copy rule. */
|
|
1214
|
-
.toc .state {
|
|
1215
|
-
font-size: var(--t5);
|
|
1216
|
-
font-weight: 500;
|
|
1217
|
-
letter-spacing: 0;
|
|
1218
|
-
text-transform: none;
|
|
1219
|
-
color: var(--color-muted);
|
|
1220
|
-
white-space: nowrap;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
/* A link leaving the page says so with a mark beside it rather than with a
|
|
1224
|
-
badge spelling out the sentence. */
|
|
1225
|
-
.toc .ext {
|
|
1226
|
-
font-size: var(--t4);
|
|
1227
|
-
color: var(--color-muted);
|
|
1228
|
-
margin-left: 0.35rem;
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
.toc a:hover .ext { color: var(--color-accent); }
|
|
1232
|
-
|
|
1233
|
-
ol.succ {
|
|
1234
|
-
margin: 0 0 2.5rem;
|
|
1235
|
-
padding-left: 1.35rem;
|
|
1236
|
-
font-size: var(--t4);
|
|
1237
|
-
line-height: 1.55;
|
|
1238
|
-
color: var(--color-text-secondary);
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
ol.succ li { margin-bottom: 0.4rem; }
|
|
1242
|
-
.toc li.soon b { color: var(--color-muted); font-weight: 400; }
|
|
1243
|
-
|
|
1244
|
-
/* ---- Focus is one token, and it is the accent ---- */
|
|
1245
|
-
|
|
1246
|
-
:where(a, button, summary, input, .opt):focus-visible {
|
|
1247
|
-
outline: 2px solid var(--color-accent);
|
|
1248
|
-
outline-offset: 2px;
|
|
1249
|
-
border-radius: 5px;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
/* A summary carries its own border, so the ring sits outside it rather than
|
|
1253
|
-
doubling the box. */
|
|
1254
|
-
.jump summary:focus-visible { outline-offset: 1px; }
|
|
1255
|
-
|
|
1256
|
-
/* The browser default ring is white on a dark ground, which reads as a second
|
|
1257
|
-
accent. Suppressing it only where the token above replaces it. */
|
|
1258
|
-
:where(a, button, summary, input, .opt):focus:not(:focus-visible) { outline: none; }
|
|
1259
|
-
|
|
1260
|
-
/* The week plan. Its markup shipped with no rule matching any of its five
|
|
1261
|
-
classes, so it rendered as a run of inline spans with the ordinal, the
|
|
1262
|
-
title, the description, and the duration all touching. */
|
|
1263
|
-
|
|
1264
|
-
.road { margin: 1.75rem 0 2.5rem; }
|
|
1265
|
-
|
|
1266
|
-
.road-row {
|
|
1267
|
-
display: grid;
|
|
1268
|
-
grid-template-columns: 1.75rem 1fr auto;
|
|
1269
|
-
gap: 0.9rem;
|
|
1270
|
-
align-items: baseline;
|
|
1271
|
-
padding: 0.85rem 0;
|
|
1272
|
-
border-bottom: 1px solid var(--color-border);
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
.road-row:last-child { border-bottom: 0; }
|
|
1276
|
-
|
|
1277
|
-
.road-n {
|
|
1278
|
-
font-family: var(--teach-mono);
|
|
1279
|
-
font-size: var(--t5);
|
|
1280
|
-
color: var(--color-accent);
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
.road-b { display: block; }
|
|
1284
|
-
.road-b b { display: block; margin-bottom: 0.2rem; }
|
|
1285
|
-
.road-b span { color: var(--color-text-secondary); font-size: var(--t4); line-height: 1.55; }
|
|
1286
|
-
|
|
1287
|
-
.road-t {
|
|
1288
|
-
font-family: var(--teach-mono);
|
|
1289
|
-
font-size: var(--t5);
|
|
1290
|
-
color: var(--color-muted);
|
|
1291
|
-
white-space: nowrap;
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
/* The closing line is a paragraph rather than a row, so it keeps the column
|
|
1295
|
-
the rows set rather than sitting flush against the measure. */
|
|
1296
|
-
.road + p > b:first-child { color: var(--color-text); }
|
|
1297
|
-
|
|
1298
|
-
@media (max-width: 640px) {
|
|
1299
|
-
.road-row { grid-template-columns: 1.5rem 1fr; }
|
|
1300
|
-
.road-t { grid-column: 2; }
|
|
1301
|
-
}`,
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
const TEACH_QUIZ: Component = {
|
|
1305
|
-
name: 'teach-quiz',
|
|
1306
|
-
note: [
|
|
1307
|
-
'The recognition quiz a lesson embeds: the option list, its right/wrong/chosen',
|
|
1308
|
-
'states, and the feedback block. `canon teach nav` layers a small stepper block',
|
|
1309
|
-
'of its own on top of this for the non-legacy option shape.',
|
|
1310
|
-
].join('\n '),
|
|
1311
|
-
reads: [
|
|
1312
|
-
'--color-border',
|
|
1313
|
-
'--color-surface',
|
|
1314
|
-
'--color-text',
|
|
1315
|
-
'--color-text-secondary',
|
|
1316
|
-
'--color-muted',
|
|
1317
|
-
'--color-accent',
|
|
1318
|
-
'--teach-sans',
|
|
1319
|
-
'--teach-mono',
|
|
1320
|
-
'--teach-shadow',
|
|
1321
|
-
'--color-teach-accent-bg',
|
|
1322
|
-
'--t4',
|
|
1323
|
-
'--t5',
|
|
1324
|
-
'--t6',
|
|
1325
|
-
],
|
|
1326
|
-
rules: `/* ---- Quiz ---- */
|
|
1327
|
-
|
|
1328
|
-
.quiz { margin-top: 3rem; border-top: 1px solid var(--color-border); padding-top: 1.75rem; }
|
|
1329
|
-
.quiz > h2:first-child { margin-top: 0; }
|
|
1330
|
-
.q { margin: 0 0 2.5rem; }
|
|
1331
|
-
.q-stem { font-weight: 700; margin-bottom: 0.85rem; }
|
|
1332
|
-
|
|
1333
|
-
.opt {
|
|
1334
|
-
display: flex;
|
|
1335
|
-
gap: 0.75rem;
|
|
1336
|
-
align-items: baseline;
|
|
1337
|
-
border: 1px solid var(--color-border);
|
|
1338
|
-
border-radius: 8px;
|
|
1339
|
-
padding: 0.7rem 0.95rem;
|
|
1340
|
-
margin-bottom: 0.45rem;
|
|
1341
|
-
cursor: pointer;
|
|
1342
|
-
font-family: var(--teach-sans);
|
|
1343
|
-
font-size: var(--t4);
|
|
1344
|
-
background: transparent;
|
|
1345
|
-
box-sizing: border-box;
|
|
1346
|
-
width: 100%;
|
|
1347
|
-
text-align: left;
|
|
1348
|
-
color: inherit;
|
|
1349
|
-
line-height: 1.45;
|
|
1350
|
-
box-shadow: var(--teach-shadow);
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
.opt::before {
|
|
1354
|
-
content: attr(data-k);
|
|
1355
|
-
font-family: var(--teach-mono);
|
|
1356
|
-
font-size: var(--t6);
|
|
1357
|
-
color: var(--color-muted);
|
|
1358
|
-
border: 1px solid var(--color-border);
|
|
1359
|
-
border-radius: 5px;
|
|
1360
|
-
padding: 0.05rem 0.38rem;
|
|
1361
|
-
flex: 0 0 auto;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
.opt:hover { border-color: var(--color-accent); background: var(--color-teach-accent-bg); }
|
|
1365
|
-
.opt[data-state="right"] { border-color: var(--color-accent); background: var(--color-teach-accent-bg); }
|
|
1366
|
-
.opt[data-state="right"]::before { color: var(--color-accent); border-color: var(--color-accent); }
|
|
1367
|
-
.opt[data-state="wrong"] { opacity: 0.38; }
|
|
1368
|
-
|
|
1369
|
-
/* The option the learner actually picked stays legible and says so.
|
|
1370
|
-
A wrong pick that fades like an option nobody considered destroys the
|
|
1371
|
-
contrast between what they thought and what is true, which is the
|
|
1372
|
-
moment the correction lands. */
|
|
1373
|
-
|
|
1374
|
-
.opt[data-state="chosen"] {
|
|
1375
|
-
opacity: 1;
|
|
1376
|
-
border-color: var(--color-muted);
|
|
1377
|
-
border-style: dashed;
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
.opt[data-state="chosen"]::after {
|
|
1381
|
-
content: "Your answer";
|
|
1382
|
-
margin-left: auto;
|
|
1383
|
-
font-size: var(--t6);
|
|
1384
|
-
font-weight: 700;
|
|
1385
|
-
letter-spacing: 0;
|
|
1386
|
-
color: var(--color-muted);
|
|
1387
|
-
align-self: center;
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
.opt[data-state="right"]::after {
|
|
1391
|
-
content: "Correct";
|
|
1392
|
-
margin-left: auto;
|
|
1393
|
-
font-size: var(--t6);
|
|
1394
|
-
font-weight: 700;
|
|
1395
|
-
letter-spacing: 0;
|
|
1396
|
-
color: var(--color-accent);
|
|
1397
|
-
align-self: center;
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
.fb {
|
|
1401
|
-
display: none;
|
|
1402
|
-
font-size: var(--t4);
|
|
1403
|
-
line-height: 1.55;
|
|
1404
|
-
color: var(--color-text-secondary);
|
|
1405
|
-
background: var(--color-surface);
|
|
1406
|
-
border-radius: 7px;
|
|
1407
|
-
padding: 0.85rem 1.05rem;
|
|
1408
|
-
margin-top: 0.55rem;
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
.fb.show { display: block; }
|
|
1412
|
-
.fb b { color: var(--color-text); font-weight: 700; }
|
|
1413
|
-
|
|
1414
|
-
footer {
|
|
1415
|
-
margin-top: 4rem;
|
|
1416
|
-
padding-top: 1.4rem;
|
|
1417
|
-
border-top: 1px solid var(--color-border);
|
|
1418
|
-
font-size: var(--t5);
|
|
1419
|
-
line-height: 1.6;
|
|
1420
|
-
color: var(--color-muted);
|
|
1421
|
-
}`,
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
const TEACH_GLOSSARY: Component = {
|
|
1425
|
-
name: 'teach-glossary',
|
|
1426
|
-
note: [
|
|
1427
|
-
'The filterable glossary view a workspace root carries: the term count, the',
|
|
1428
|
-
'filter input, and the scrolling term list.',
|
|
1429
|
-
].join('\n '),
|
|
1430
|
-
reads: [
|
|
1431
|
-
'--color-background',
|
|
1432
|
-
'--color-border',
|
|
1433
|
-
'--color-text-body',
|
|
1434
|
-
'--color-text-secondary',
|
|
1435
|
-
'--color-muted',
|
|
1436
|
-
'--color-accent',
|
|
1437
|
-
'--teach-sans',
|
|
1438
|
-
'--teach-mono',
|
|
1439
|
-
'--t4',
|
|
1440
|
-
'--t5',
|
|
1441
|
-
],
|
|
1442
|
-
rules: `/* ---- Glossary view, generated from the markdown source ---- */
|
|
1443
|
-
|
|
1444
|
-
/* No border, sitting on the page ground, with the count beside it. The border and the
|
|
1445
|
-
reading surface are what made the control look like something to fill in,
|
|
1446
|
-
and the count is what says it filters the list under it. */
|
|
1447
|
-
.filter-row {
|
|
1448
|
-
display: flex;
|
|
1449
|
-
align-items: baseline;
|
|
1450
|
-
gap: 1rem;
|
|
1451
|
-
margin: 0 0 1rem;
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
.filter {
|
|
1455
|
-
flex: 1;
|
|
1456
|
-
min-width: 0;
|
|
1457
|
-
font-family: var(--teach-sans);
|
|
1458
|
-
font-size: var(--t4);
|
|
1459
|
-
color: var(--color-text-body);
|
|
1460
|
-
background: transparent;
|
|
1461
|
-
border: 0;
|
|
1462
|
-
padding: 0.3rem 0;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
.filter-count {
|
|
1466
|
-
font-family: var(--teach-mono);
|
|
1467
|
-
font-size: var(--t5);
|
|
1468
|
-
color: var(--color-muted);
|
|
1469
|
-
white-space: nowrap;
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
/* The \`:where(...)\` focus token has zero specificity, so a \`.filter:focus\`
|
|
1473
|
-
rule outranks it and leaves a keyboard reaching the control with no ring.
|
|
1474
|
-
The ring stays the token's and only the pointer ring is dropped. */
|
|
1475
|
-
.filter:focus:not(:focus-visible) { outline: none; }
|
|
1476
|
-
.filter::placeholder { color: var(--color-muted); }
|
|
1477
|
-
|
|
1478
|
-
/* A fixed scroll region rather than a collapsing list. Filtering a list that
|
|
1479
|
-
sets page height makes the page jump under the reader, and the same fixed
|
|
1480
|
-
region is what lets the glossary grow past what one screen shows without
|
|
1481
|
-
the page growing with it. */
|
|
1482
|
-
|
|
1483
|
-
/* A scroll region is a tab stop in its own right, so it takes the same ring
|
|
1484
|
-
as everything else rather than the browser's white default. */
|
|
1485
|
-
.gloss:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; border-radius: 5px; }
|
|
1486
|
-
|
|
1487
|
-
.gloss {
|
|
1488
|
-
height: clamp(14rem, 46vh, 32rem);
|
|
1489
|
-
overflow-y: auto;
|
|
1490
|
-
margin-bottom: 2.5rem;
|
|
1491
|
-
border-bottom: 1px solid var(--color-border);
|
|
1492
|
-
scrollbar-width: thin;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
/* Scrollbar styling is global rather than per component. It was scoped to the
|
|
1496
|
-
glossary, so the outline rail, the code blocks, and the scrolling tables all
|
|
1497
|
-
fell back to the browser default, which paints a light track on a dark page
|
|
1498
|
-
and is the one element on screen taking no color from these tokens. */
|
|
1499
|
-
|
|
1500
|
-
.gloss .empty {
|
|
1501
|
-
display: none;
|
|
1502
|
-
padding: 1.2rem 0;
|
|
1503
|
-
color: var(--color-muted);
|
|
1504
|
-
font-size: var(--t4);
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
.gloss.none .empty { display: block; }
|
|
1508
|
-
|
|
1509
|
-
.gterm {
|
|
1510
|
-
padding: 0.7rem 0;
|
|
1511
|
-
border-bottom: 1px solid var(--color-border);
|
|
1512
|
-
font-size: var(--t4);
|
|
1513
|
-
line-height: 1.55;
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
.gloss-group {
|
|
1517
|
-
margin: 1.1rem 0 0.4rem;
|
|
1518
|
-
font-family: var(--teach-sans);
|
|
1519
|
-
font-size: var(--t5);
|
|
1520
|
-
font-weight: 600;
|
|
1521
|
-
color: var(--color-muted);
|
|
1522
|
-
letter-spacing: 0;
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
.gloss-group:first-of-type { margin-top: 0; }
|
|
1526
|
-
|
|
1527
|
-
.gterm b { font-weight: 700; }
|
|
1528
|
-
/* The markdown source separates a term from its definition with a colon,
|
|
1529
|
-
and the house standard bans an em dash outright, so the view mirrors
|
|
1530
|
-
the source rather than inventing punctuation for it. */
|
|
1531
|
-
/* The space is part of the generated content, since the term and its
|
|
1532
|
-
definition are adjacent elements with no whitespace between them in the
|
|
1533
|
-
markup and the colon rendered flush against the first word. */
|
|
1534
|
-
.gterm b::after { content: ": "; color: var(--color-muted); font-weight: 400; white-space: pre; }
|
|
1535
|
-
.gterm span { color: var(--color-text-secondary); }
|
|
1536
|
-
|
|
1537
|
-
ol.succ {
|
|
1538
|
-
margin: 0 0 2.5rem;
|
|
1539
|
-
padding-left: 1.35rem;
|
|
1540
|
-
font-size: var(--t4);
|
|
1541
|
-
line-height: 1.55;
|
|
1542
|
-
color: var(--color-text-secondary);
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
ol.succ li { margin-bottom: 0.4rem; }
|
|
1546
|
-
.toc li.soon b { color: var(--color-muted); font-weight: 400; }
|
|
1547
|
-
|
|
1548
|
-
/* A filter that matches nothing needs a way back, not just a sentence. */
|
|
1549
|
-
.gloss .empty .clear {
|
|
1550
|
-
font-family: var(--teach-sans);
|
|
1551
|
-
font-size: var(--t4);
|
|
1552
|
-
color: var(--color-accent);
|
|
1553
|
-
background: none;
|
|
1554
|
-
border: 0;
|
|
1555
|
-
border-bottom: 1px solid currentColor;
|
|
1556
|
-
padding: 0;
|
|
1557
|
-
margin-left: 0.35rem;
|
|
1558
|
-
cursor: pointer;
|
|
1559
|
-
}`,
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
const TEACH_OUTLINE: Component = {
|
|
1563
|
-
name: 'teach-outline',
|
|
1564
|
-
note: [
|
|
1565
|
-
'The in-lesson outline, folded under the lesson being read in the course',
|
|
1566
|
-
'sidebar. It used to be a fixed rail in the gutter the reading measure left',
|
|
1567
|
-
'over, which reported the same thing twice once the sidebar carried the',
|
|
1568
|
-
'course and disappeared entirely below 1420px.',
|
|
1569
|
-
].join('\n '),
|
|
1570
|
-
reads: ['--color-text', '--color-text-secondary', '--color-accent', '--t6'],
|
|
1571
|
-
rules: `/* ---- In-lesson outline, folded under the lesson being read ---- */
|
|
1572
|
-
|
|
1573
|
-
/* The rail this replaces was \`position: fixed\` in the right-hand gutter, which
|
|
1574
|
-
only existed above 1420px and reported nothing at all below it. Folding the
|
|
1575
|
-
headings under the current lesson puts the course and the lesson in one
|
|
1576
|
-
column, so a reader tracks both in one place at any window width. */
|
|
1577
|
-
.sb-out { list-style: none; margin: 0.1rem 0 0.55rem; padding: 0 0 0 1.45rem; }
|
|
1578
|
-
|
|
1579
|
-
.sb-out a {
|
|
1580
|
-
display: block;
|
|
1581
|
-
padding: 0.18rem 0.5rem;
|
|
1582
|
-
text-decoration: none;
|
|
1583
|
-
color: var(--color-text-secondary);
|
|
1584
|
-
font-size: var(--t6);
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
.sb-out a:hover { color: var(--color-text); }
|
|
1588
|
-
.sb-out a.on { color: var(--color-accent); }`,
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
const TEACH_REFERENCES: Component = {
|
|
1592
|
-
name: 'teach-references',
|
|
1593
|
-
note: [
|
|
1594
|
-
'The numbered citation marks and reference list a lesson closes with, and the',
|
|
1595
|
-
'rendered view of a promoted reference page.',
|
|
1596
|
-
].join('\n '),
|
|
1597
|
-
reads: [
|
|
1598
|
-
'--color-background',
|
|
1599
|
-
'--color-border',
|
|
1600
|
-
'--color-text-secondary',
|
|
1601
|
-
'--color-muted',
|
|
1602
|
-
'--color-accent',
|
|
1603
|
-
'--teach-mono',
|
|
1604
|
-
'--color-teach-accent-bg',
|
|
1605
|
-
'--t5',
|
|
1606
|
-
'--t6',
|
|
1607
|
-
],
|
|
1608
|
-
rules: `footer {
|
|
1609
|
-
margin-top: 4rem;
|
|
1610
|
-
padding-top: 1.4rem;
|
|
1611
|
-
border-top: 1px solid var(--color-border);
|
|
1612
|
-
font-size: var(--t5);
|
|
1613
|
-
line-height: 1.6;
|
|
1614
|
-
color: var(--color-muted);
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
/* ---- References, numbered rather than narrated ---- */
|
|
1618
|
-
|
|
1619
|
-
sup.cite {
|
|
1620
|
-
font-family: var(--teach-mono);
|
|
1621
|
-
font-size: var(--t6);
|
|
1622
|
-
line-height: 0;
|
|
1623
|
-
margin-left: 0.1em;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
sup.cite a {
|
|
1627
|
-
text-decoration: none;
|
|
1628
|
-
padding: 0.05em 0.25em;
|
|
1629
|
-
border-radius: 3px;
|
|
1630
|
-
background: var(--color-teach-accent-bg);
|
|
1631
|
-
color: var(--color-accent);
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
sup.cite a:hover { background: var(--color-accent); color: var(--color-background); }
|
|
1635
|
-
|
|
1636
|
-
ol.refs {
|
|
1637
|
-
list-style: none;
|
|
1638
|
-
counter-reset: r;
|
|
1639
|
-
padding: 0;
|
|
1640
|
-
margin: 0.9rem 0 1.1rem;
|
|
1641
|
-
font-size: var(--t5);
|
|
1642
|
-
line-height: 1.6;
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
ol.refs li {
|
|
1646
|
-
counter-increment: r;
|
|
1647
|
-
position: relative;
|
|
1648
|
-
padding-left: 2.1rem;
|
|
1649
|
-
margin-bottom: 0.5rem;
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
ol.refs li::before {
|
|
1653
|
-
content: "[" counter(r) "]";
|
|
1654
|
-
position: absolute;
|
|
1655
|
-
left: 0;
|
|
1656
|
-
font-family: var(--teach-mono);
|
|
1657
|
-
color: var(--color-accent);
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
ol.refs cite { font-style: italic; color: var(--color-text-secondary); }
|
|
1661
|
-
ol.refs a { word-break: break-word; }
|
|
1662
|
-
|
|
1663
|
-
footer .own {
|
|
1664
|
-
margin: 0;
|
|
1665
|
-
padding-top: 0.7rem;
|
|
1666
|
-
border-top: 1px solid var(--color-border);
|
|
1667
|
-
color: var(--color-muted);
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
main.ref { padding-bottom: 4rem; }
|
|
1671
|
-
main.ref h1 { margin-top: 1.5rem; }
|
|
1672
|
-
main.ref h2 { margin-top: 2.5rem; }
|
|
1673
|
-
main.ref ol, main.ref ul { padding-left: 1.4rem; }
|
|
1674
|
-
main.ref li { margin-bottom: 0.5rem; }
|
|
1675
|
-
main.ref blockquote {
|
|
1676
|
-
margin: 1.5rem 0;
|
|
1677
|
-
padding: 0.1rem 0 0.1rem 1.1rem;
|
|
1678
|
-
border-left: 3px solid var(--color-border);
|
|
1679
|
-
color: var(--color-text-secondary);
|
|
1680
|
-
}`,
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
export const TEACH_COMPONENTS: readonly Component[] = [
|
|
1684
|
-
TEACH_CHROME,
|
|
1685
|
-
TEACH_ARTICLE,
|
|
1686
|
-
TEACH_QUIZ,
|
|
1687
|
-
TEACH_GLOSSARY,
|
|
1688
|
-
TEACH_OUTLINE,
|
|
1689
|
-
TEACH_REFERENCES,
|
|
1690
|
-
]
|
|
1691
|
-
|
|
1692
|
-
/**
|
|
1693
|
-
* The generic components every design consumer gets, plus the teach chrome,
|
|
1694
|
-
* for the one caller that wants both: a teach workspace stylesheet.
|
|
1695
|
-
*/
|
|
1696
|
-
export const TEACH_STYLESHEET_COMPONENTS: readonly Component[] = [
|
|
1697
|
-
...COMPONENTS,
|
|
1698
|
-
...TEACH_COMPONENTS,
|
|
1699
|
-
]
|