@erclx/canon 4.110.0 → 4.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/assets/brand/mark.svg +10 -0
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/auto-ship/REQUIREMENT.md +1 -0
  4. package/claude/skills/auto-ship/SKILL.md +12 -2
  5. package/claude/skills/canon-operator/REQUIREMENT.md +2 -2
  6. package/claude/skills/canon-operator/SKILL.md +3 -4
  7. package/claude/skills/git-pr/REQUIREMENT.md +2 -0
  8. package/claude/skills/git-pr/SKILL.md +21 -3
  9. package/claude/skills/target-check/references/domains.md +2 -2
  10. package/claude/skills/teach-workspace/SKILL.md +1 -1
  11. package/docs/agents/install-and-sync.md +4 -13
  12. package/docs/agents/teach.md +3 -1
  13. package/docs/target-projects.md +0 -2
  14. package/docs/workflow/ai-workflow.md +7 -11
  15. package/package.json +2 -1
  16. package/scripts/core/capture-home.sh +59 -0
  17. package/src/claude/cases/setup.ts +2 -23
  18. package/src/commands/sync.ts +0 -16
  19. package/src/design/components.ts +153 -152
  20. package/src/design/favicon.ts +8 -0
  21. package/src/design/tokens.ts +1 -1
  22. package/src/shipped/references.ts +1 -2
  23. package/src/sync/reverse.ts +3 -74
  24. package/src/teach/nav.ts +55 -13
  25. package/claude/skills/migration-claude-md/REQUIREMENT.md +0 -40
  26. package/claude/skills/migration-claude-md/SKILL.md +0 -79
  27. package/claude/skills/migration-context/REQUIREMENT.md +0 -36
  28. package/claude/skills/migration-context/SKILL.md +0 -97
  29. package/claude/skills/migration-standards-drop/REQUIREMENT.md +0 -55
  30. package/claude/skills/migration-standards-drop/SKILL.md +0 -113
  31. package/claude/skills/migration-superseded/REQUIREMENT.md +0 -44
  32. package/claude/skills/migration-superseded/SKILL.md +0 -115
@@ -223,6 +223,7 @@ const TEACH_CHROME: Component = {
223
223
  reads: [
224
224
  '--color-background',
225
225
  '--color-surface',
226
+ '--color-chrome',
226
227
  '--color-border',
227
228
  '--color-text',
228
229
  '--color-text-body',
@@ -232,7 +233,6 @@ const TEACH_CHROME: Component = {
232
233
  '--teach-sans',
233
234
  '--teach-mono',
234
235
  '--teach-measure',
235
- '--teach-chrome',
236
236
  '--teach-shadow',
237
237
  '--color-teach-accent-bg',
238
238
  '--type-body-family',
@@ -247,8 +247,7 @@ const TEACH_CHROME: Component = {
247
247
  --teach-hand: 'Virgil', 'Excalifont', cursive;
248
248
  --teach-mono: var(--type-code-family);
249
249
  --teach-measure: 52rem;
250
- --teach-chrome: 52rem;
251
- --teach-mast-h: 4.4rem;
250
+ --teach-mast-h: 3.5rem;
252
251
  --teach-shadow: 0 1px 2px rgba(20, 20, 20, 0.04);
253
252
  --color-teach-accent-bg: color-mix(in srgb, var(--color-accent) 14%, var(--color-background));
254
253
 
@@ -292,7 +291,7 @@ body {
292
291
  color: var(--color-text-body);
293
292
  font-family: var(--teach-sans);
294
293
  font-size: var(--t3);
295
- line-height: 1.62;
294
+ line-height: 1.55;
296
295
  -webkit-font-smoothing: antialiased;
297
296
  }
298
297
 
@@ -328,6 +327,7 @@ html.sb-shut .pane { border-left: 0; }
328
327
  background: var(--color-background);
329
328
  border-bottom: 1px solid var(--color-border);
330
329
  padding: 0 1.5rem;
330
+ cursor: default;
331
331
  }
332
332
 
333
333
  /* The reading bar rides the masthead's own bottom edge rather than arriving as
@@ -345,37 +345,26 @@ html.sb-shut .pane { border-left: 0; }
345
345
  }
346
346
 
347
347
  /* The height is the token rather than the sum of two paddings, so the sidebar's
348
- own top row lands on the same seam the bar's bottom border draws. */
348
+ own top row lands on the same seam the bar's bottom border draws. The row
349
+ spans the window rather than the reading measure: capped, the toggle and the
350
+ theme control floated in from the edges by an amount that changed per page. */
349
351
  .mast {
350
- max-width: var(--teach-chrome);
351
- margin: 0 auto;
352
+ max-width: none;
353
+ margin: 0;
352
354
  min-height: var(--teach-mast-h);
353
- padding: 0;
355
+ padding: 0 0.9rem;
354
356
  display: flex;
355
357
  justify-content: space-between;
356
358
  align-items: center;
357
- gap: 1rem;
359
+ gap: 0.55rem;
358
360
  font-size: var(--t5);
359
361
  color: var(--color-muted);
362
+ cursor: default;
360
363
  }
361
364
 
362
365
  .mast a { color: var(--color-accent); text-decoration: none; font-weight: 600; }
363
366
  .mast a:hover { text-decoration: underline; }
364
367
 
365
- .theme {
366
- font-family: var(--teach-sans);
367
- font-size: 0.75rem;
368
- font-weight: 600;
369
- color: var(--color-muted);
370
- background: transparent;
371
- border: 1px solid var(--color-border);
372
- border-radius: 99px;
373
- padding: 0.25rem 0.7rem;
374
- cursor: pointer;
375
- }
376
-
377
- .theme:hover { border-color: var(--color-accent); color: var(--color-accent); }
378
-
379
368
  .nav {
380
369
  width: 100%;
381
370
  margin: 0;
@@ -404,10 +393,9 @@ html.sb-shut .pane { border-left: 0; }
404
393
 
405
394
  .nav .lbl {
406
395
  display: block;
407
- font-size: 0.6563rem;
396
+ font-size: var(--t6);
408
397
  font-weight: 700;
409
- letter-spacing: 0.09em;
410
- text-transform: uppercase;
398
+ letter-spacing: 0;
411
399
  color: var(--color-muted);
412
400
  margin-bottom: 0.15rem;
413
401
  }
@@ -422,7 +410,7 @@ html.sb-shut .pane { border-left: 0; }
422
410
  restates either. */
423
411
  /* A breadcrumb rather than a row of links. The gap tightens because the
424
412
  separators now carry the spacing the gap used to. */
425
- .mast-left { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
413
+ .mast-left { display: flex; align-items: center; gap: 0.3rem; min-width: 0; flex-wrap: wrap; cursor: default; }
426
414
 
427
415
  /* \`--rule\` measured 1.27:1 here. It is a border token, and a border is a
428
416
  shape a reader infers rather than a glyph they resolve, so it is the wrong
@@ -430,6 +418,7 @@ html.sb-shut .pane { border-left: 0; }
430
418
  .crumb-sep {
431
419
  color: var(--color-muted);
432
420
  font-size: var(--t4);
421
+ margin: 0 0.1rem;
433
422
  user-select: none;
434
423
  }
435
424
 
@@ -442,27 +431,55 @@ html.sb-shut .pane { border-left: 0; }
442
431
 
443
432
  .crumb { display: inline-flex; align-items: center; }
444
433
 
445
- /* The label and its caret are one segment visually and two controls in fact:
446
- the label navigates, the caret opens the menu. */
447
- .crumb-item { display: inline-flex; align-items: center; gap: 0.1rem; }
434
+ /* The label and its caret are one component with two zones, like a split
435
+ button: the label navigates, the caret opens the menu, and one surface fills
436
+ for both so the pair reads as a single chip rather than two loose pieces.
437
+ The pointer covers the gap between them, which is interactive on both sides. */
438
+ .crumb-item {
439
+ display: inline-flex;
440
+ align-items: center;
441
+ gap: 0;
442
+ border-radius: 7px;
443
+ padding: 0.1rem 0.15rem 0.1rem 0.35rem;
444
+ cursor: pointer;
445
+ }
446
+
447
+ .crumb-item:has(details.jump):hover,
448
+ .crumb-item:has(details.jump[open]) { background: var(--color-chrome); }
448
449
 
449
- .crumb-here { color: var(--color-text); font-weight: 600; }
450
+ /* Accent reports where you are. A crumb is navigation you can take, so it is
451
+ neutral, and \`.mast a\` keeps the accent for any other bar link. */
452
+ .mast a.crumb { color: var(--color-text-secondary); padding: 0.1rem 0.15rem 0.1rem 0; }
453
+ .mast a.crumb:hover { text-decoration: none; }
454
+ .crumb-item:hover a.crumb { color: var(--color-text); }
450
455
 
451
- /* The caret is now the whole control, so it carries the 24 pixel minimum on
452
- its own rather than inheriting a label's width. */
456
+ .crumb-here { color: var(--color-text); font-weight: 650; }
457
+
458
+ /* \`align-items: center\` centres boxes, and a label's line box carries
459
+ half-leading above the cap and descender room below, so its capitals sit
460
+ above its own centre by an amount that scales with the font size. Trimming
461
+ the label to its cap band fixes the box rather than nudging the mark. It
462
+ lands on a span holding the text alone, since the link's own \`inline-flex\`
463
+ ignores it. Firefox ships no \`text-box\` and keeps the caret a pixel low. */
464
+ .crumb-t, .crumb-here, .sb-ws .ws-name {
465
+ display: block;
466
+ text-box: trim-both cap alphabetic;
467
+ }
468
+
469
+ /* The caret is the whole control, so it carries the 24 pixel minimum on its
470
+ own rather than inheriting a label's width. It fills nothing itself, since
471
+ the chip around it does. */
453
472
  .crumb-item > .jump > summary {
454
473
  min-width: 1.5rem;
455
474
  min-height: 1.5rem;
456
475
  display: inline-flex;
457
476
  align-items: center;
458
477
  justify-content: center;
459
- padding: 0;
478
+ padding: 0.15rem 0.3rem 0.15rem 0;
460
479
  border: 0;
461
480
  background: transparent;
462
481
  }
463
482
 
464
- .crumb-item > .jump > summary:hover { background: var(--color-surface); border-radius: 5px; }
465
-
466
483
  /* A bar link is its own tap target rather than a line of text, so it carries
467
484
  the 24 pixel minimum the a11y rule sets. */
468
485
  .mast a,
@@ -478,23 +495,25 @@ html.sb-shut .pane { border-left: 0; }
478
495
  color: var(--color-muted);
479
496
  }
480
497
 
481
- .mast-right { display: flex; align-items: center; gap: 0.85rem; }
498
+ .mast-right { display: flex; align-items: center; gap: 0.2rem; margin-left: auto; }
482
499
 
500
+ /* No border, at the fold control's size and radius, so every control on the
501
+ bar is drawn one way. */
483
502
  .theme {
484
- width: 1.9rem;
485
- height: 1.9rem;
503
+ width: 1.75rem;
504
+ height: 1.75rem;
486
505
  display: inline-flex;
487
506
  align-items: center;
488
507
  justify-content: center;
489
508
  padding: 0;
490
- border: 1px solid var(--color-border);
491
- border-radius: 99px;
509
+ border: 0;
510
+ border-radius: 6px;
492
511
  background: transparent;
493
512
  color: var(--color-muted);
494
513
  cursor: pointer;
495
514
  }
496
515
 
497
- .theme:hover { border-color: var(--color-accent); color: var(--color-accent); }
516
+ .theme:hover { color: var(--color-text); background: var(--color-chrome); }
498
517
  .theme svg { width: 0.95rem; height: 0.95rem; display: block; }
499
518
  .theme .moon { display: none; }
500
519
  :root[data-theme="dark"] .theme .sun { display: none; }
@@ -528,7 +547,7 @@ html.sb-shut .pane { border-left: 0; }
528
547
  cursor: pointer;
529
548
  }
530
549
 
531
- .sb-fold:hover { color: var(--color-text); background: var(--color-surface); }
550
+ .sb-fold:hover { color: var(--color-text); background: var(--color-chrome); }
532
551
 
533
552
  .sb {
534
553
  flex: 0 0 var(--sb-w, 16rem);
@@ -564,7 +583,7 @@ html.sb-shut .sb { flex-basis: 0; width: 0; overflow: hidden; }
564
583
  display: inline-flex;
565
584
  align-items: center;
566
585
  gap: 0.35rem;
567
- padding: 0.2rem 0.4rem;
586
+ padding: 0.25rem 0.3rem;
568
587
  border-radius: 6px;
569
588
  font-size: var(--t4);
570
589
  font-weight: 650;
@@ -572,7 +591,7 @@ html.sb-shut .sb { flex-basis: 0; width: 0; overflow: hidden; }
572
591
  }
573
592
 
574
593
  .sb-ws > summary::-webkit-details-marker { display: none; }
575
- .sb-ws > summary:hover, .sb-ws[open] > summary { background: var(--color-surface); color: var(--color-text); }
594
+ .sb-ws > summary:hover, .sb-ws[open] > summary { background: var(--color-chrome); color: var(--color-text); }
576
595
  .sb-ws .car { color: var(--color-muted); display: inline-flex; align-items: center; }
577
596
 
578
597
  .sb-wl {
@@ -585,7 +604,7 @@ html.sb-shut .sb { flex-basis: 0; width: 0; overflow: hidden; }
585
604
  border: 1px solid var(--color-border);
586
605
  border-radius: 9px;
587
606
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
588
- padding: 0.25rem;
607
+ padding: 0.3rem;
589
608
  }
590
609
 
591
610
  :root[data-theme="dark"] .sb-wl { box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5); }
@@ -644,7 +663,7 @@ html.sb-shut .sb { flex-basis: 0; width: 0; overflow: hidden; }
644
663
  line-height: 1.45;
645
664
  }
646
665
 
647
- .sb-l:hover { background: var(--color-surface); color: var(--color-text); }
666
+ .sb-l:hover { background: var(--color-chrome); color: var(--color-text); }
648
667
 
649
668
  .sb-n {
650
669
  flex: none;
@@ -764,46 +783,47 @@ html.sb-shut .sb-grip { display: none; }
764
783
  }
765
784
 
766
785
  .jump summary::-webkit-details-marker { display: none; }
767
- .jump summary:hover { border-color: var(--color-accent); color: var(--color-accent); }
768
- /* The caret says "this is a menu" and nothing else. It used to flip to say
769
- "the menu is open", which the open panel underneath already says, so the
770
- motion carried no information a reader could not already see. */
771
- .jump summary .caret { opacity: 0.55; }
772
- .jump summary:hover .caret { opacity: 1; }
773
- .jump[open] summary { border-color: var(--color-accent); color: var(--color-accent); }
774
- .jump[open] summary .caret { opacity: 1; }
786
+ /* The caret says "this is a menu" and nothing else, so it is drawn at full
787
+ strength in every state and stays muted. A reveal on hover hid the only
788
+ route into the menu from a reader who cannot hover, and an affordance is not
789
+ a state, so it takes no accent on hover or while open. */
790
+ .jump summary .caret { color: var(--color-muted); }
775
791
 
776
792
  .jump-list {
777
793
  position: absolute;
778
794
  top: calc(100% + 0.4rem);
779
795
  left: 0;
780
796
  z-index: 20;
781
- min-width: 21rem;
782
797
  background: var(--color-background);
783
798
  border: 1px solid var(--color-border);
784
799
  border-radius: 9px;
785
800
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14);
786
- padding: 0.35rem;
801
+ padding: 0.25rem;
787
802
  list-style: none;
788
803
  margin: 0;
789
804
  }
790
805
 
791
806
  :root[data-theme="dark"] .jump-list { box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5); }
792
807
 
808
+ /* One row recipe wherever the menu is mounted, on the steps the lesson list
809
+ beside it already reads: the label at \`--t5\` and the numeral at \`--t6\` mono.
810
+ The weight is restated because \`.mast a\` sets 600 for the bar's own links. */
793
811
  .jump-list a {
794
812
  display: grid;
795
813
  grid-template-columns: 1.65rem 1fr auto;
796
- gap: 0.7rem;
814
+ gap: 0.55rem;
797
815
  align-items: center;
798
- padding: 0.45rem 0.55rem;
816
+ padding: 0.38rem 0.55rem;
799
817
  border-radius: 6px;
800
818
  text-decoration: none;
801
819
  color: var(--color-text-body);
802
- font-size: 0.875rem;
820
+ font-size: var(--t5);
821
+ font-weight: 400;
822
+ line-height: 1.45;
803
823
  }
804
824
 
805
- .jump-list a:hover { background: var(--color-surface); }
806
- .jump-list .n { font-family: var(--teach-mono); font-size: 0.75rem; color: var(--color-muted); }
825
+ .jump-list a:hover { background: var(--color-chrome); }
826
+ .jump-list .n { font-family: var(--teach-mono); font-size: var(--t6); color: var(--color-muted); }
807
827
  /* The third column used to hold a status dot that read the same on every row,
808
828
  so it reported nothing. It is a per-entry trailing slot now: the workspace
809
829
  menu puts a lesson count there and the lesson menu leaves it empty. */
@@ -825,11 +845,11 @@ html.sb-shut .sb-grip { display: none; }
825
845
  .jump-list li.all a { color: var(--color-accent); }
826
846
  .jump-list li.all .n { color: var(--color-accent); }
827
847
 
828
- /* The menu is \`min-width: 21rem\` and anchored to its summary, which puts its
829
- right edge 35 pixels past a 390 pixel viewport. Clamping the width and
830
- letting a long row wrap keeps it on screen without a breakpoint. */
848
+ /* The menu fits its content above an 11rem floor rather than padding three
849
+ short titles out to a wide panel. It is anchored to its summary, so the
850
+ clamp keeps a long row wrapping on screen without a breakpoint. */
831
851
  .jump-list {
832
- min-width: min(21rem, calc(100vw - 2rem));
852
+ min-width: min(11rem, calc(100vw - 2rem));
833
853
  max-width: calc(100vw - 2rem);
834
854
  }
835
855
 
@@ -850,8 +870,7 @@ html.sb-shut .sb-grip { display: none; }
850
870
  @media (max-width: 640px) {
851
871
  .bar { padding: 0 1rem; }
852
872
  main { padding-left: 1rem; padding-right: 1rem; }
853
- .mast { font-size: 0.75rem; gap: 0.5rem; }
854
- .mast-left { gap: 0.5rem; row-gap: 0.35rem; }
873
+ .mast { font-size: 0.75rem; }
855
874
  }
856
875
 
857
876
  @media (max-width: 640px) {
@@ -933,7 +952,7 @@ html.sb-shut .sb-grip { display: none; }
933
952
  cursor: pointer;
934
953
  }
935
954
 
936
- .sb-close:hover { color: var(--color-text); background: var(--color-surface); }
955
+ .sb-close:hover { color: var(--color-text); background: var(--color-chrome); }
937
956
 
938
957
  .sb-scrim {
939
958
  display: block;
@@ -971,6 +990,9 @@ const TEACH_ARTICLE: Component = {
971
990
  '--color-accent',
972
991
  '--teach-hand',
973
992
  '--teach-mono',
993
+ '--t1',
994
+ '--t2',
995
+ '--t3',
974
996
  '--t4',
975
997
  '--t5',
976
998
  '--t6',
@@ -978,8 +1000,8 @@ const TEACH_ARTICLE: Component = {
978
1000
  rules: `/* ---- Type ---- */
979
1001
 
980
1002
  h1 {
981
- font-size: 2.15rem;
982
- line-height: 1.14;
1003
+ font-size: var(--t1);
1004
+ line-height: 1.1;
983
1005
  font-weight: 800;
984
1006
  letter-spacing: -0.022em;
985
1007
  margin: 0 0 0.55rem;
@@ -987,19 +1009,20 @@ h1 {
987
1009
  }
988
1010
 
989
1011
  h2 {
990
- font-size: 1.3rem;
1012
+ font-size: var(--t2);
1013
+ line-height: 1.3;
991
1014
  font-weight: 700;
992
1015
  letter-spacing: -0.012em;
993
1016
  margin: 3rem 0 0.85rem;
994
1017
  }
995
1018
 
996
- h3 { font-size: 1.03rem; font-weight: 700; margin: 1.9rem 0 0.4rem; }
1019
+ h3 { font-size: var(--t3); line-height: 1.55; font-weight: 700; margin: 1.9rem 0 0.4rem; }
997
1020
 
998
1021
  p { margin: 0 0 1.15rem; }
999
1022
 
1000
1023
  .lede {
1001
- font-size: 1.1875rem;
1002
- line-height: 1.5;
1024
+ font-size: var(--t3);
1025
+ line-height: 1.55;
1003
1026
  color: var(--color-text-secondary);
1004
1027
  margin-bottom: 2rem;
1005
1028
  }
@@ -1011,8 +1034,8 @@ em { font-style: italic; }
1011
1034
  /* ---- Panels ---- */
1012
1035
 
1013
1036
  .assumes, .progress {
1014
- font-size: 0.8438rem;
1015
- line-height: 1.7;
1037
+ font-size: var(--t5);
1038
+ line-height: 1.6;
1016
1039
  color: var(--color-text-secondary);
1017
1040
  background: var(--color-surface);
1018
1041
  border-radius: 7px;
@@ -1024,8 +1047,7 @@ em { font-style: italic; }
1024
1047
  display: block;
1025
1048
  color: var(--color-muted);
1026
1049
  font-weight: 700;
1027
- letter-spacing: 0.06em;
1028
- text-transform: uppercase;
1050
+ letter-spacing: 0;
1029
1051
  font-size: var(--t6);
1030
1052
  margin-bottom: 0.2rem;
1031
1053
  }
@@ -1043,7 +1065,7 @@ code {
1043
1065
  pre {
1044
1066
  font-family: var(--teach-mono);
1045
1067
  font-size: var(--t5);
1046
- line-height: 1.7;
1068
+ line-height: 1.6;
1047
1069
  background: var(--color-surface);
1048
1070
  padding: 1.15rem 1.3rem;
1049
1071
  border-radius: 7px;
@@ -1064,7 +1086,7 @@ pre code { background: none; padding: 0; font-size: 1em; }
1064
1086
 
1065
1087
  .hard-label {
1066
1088
  font-family: var(--teach-hand);
1067
- font-size: 1.0625rem;
1089
+ font-size: var(--t3);
1068
1090
  color: var(--color-accent);
1069
1091
  display: block;
1070
1092
  margin-bottom: 0.35rem;
@@ -1074,15 +1096,14 @@ pre code { background: none; padding: 0; font-size: 1em; }
1074
1096
 
1075
1097
  /* ---- Tables ---- */
1076
1098
 
1077
- table { width: 100%; border-collapse: collapse; font-size: 0.875rem; margin: 0 0 1.5rem; }
1099
+ table { width: 100%; border-collapse: collapse; font-size: var(--t4); margin: 0 0 1.5rem; }
1078
1100
  th, td { text-align: right; padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--color-border); }
1079
1101
  th:first-child, td:first-child { text-align: left; }
1080
1102
 
1081
1103
  th {
1082
1104
  font-weight: 700;
1083
- font-size: 0.75rem;
1084
- letter-spacing: 0.04em;
1085
- text-transform: uppercase;
1105
+ font-size: var(--t5);
1106
+ letter-spacing: 0;
1086
1107
  color: var(--color-muted);
1087
1108
  border-bottom-color: var(--color-border);
1088
1109
  }
@@ -1107,26 +1128,22 @@ tbody tr.mark td { background: var(--color-teach-accent-bg); }
1107
1128
  }
1108
1129
 
1109
1130
  .toc a:hover { background: var(--color-surface); }
1110
- .toc .num { font-family: var(--teach-mono); font-size: var(--t5); color: var(--color-accent); }
1111
- .toc b { font-size: 1.0625rem; font-weight: 700; letter-spacing: -0.012em; }
1131
+ .toc .num { font-family: var(--teach-mono); font-size: var(--t5); color: var(--color-muted); }
1132
+ .toc b { font-size: var(--t3); font-weight: 700; letter-spacing: -0.012em; }
1112
1133
 
1113
1134
  .toc .blurb {
1114
1135
  grid-column: 2;
1115
1136
  font-size: var(--t4);
1116
- line-height: 1.5;
1137
+ line-height: 1.55;
1117
1138
  color: var(--color-text-secondary);
1118
1139
  margin-top: 0.1rem;
1119
1140
  }
1120
1141
 
1121
- /* A dot and a word, not a pill. The jump menus already say written and
1122
- planned with a filled or hollow dot, so the bordered uppercase badge was a
1123
- second vocabulary for a fact the design system already had one for. Sentence
1124
- case and no tracking, per the UI copy rule. */
1142
+ /* A word, not a pill and not a dot. A mark reading the same on every live row
1143
+ reports nothing, so the dot retired here as it did from the jump menus.
1144
+ Sentence case and no tracking, per the UI copy rule. */
1125
1145
  .toc .state {
1126
- display: inline-flex;
1127
- align-items: center;
1128
- gap: 0.42rem;
1129
- font-size: 0.75rem;
1146
+ font-size: var(--t5);
1130
1147
  font-weight: 500;
1131
1148
  letter-spacing: 0;
1132
1149
  text-transform: none;
@@ -1134,24 +1151,10 @@ tbody tr.mark td { background: var(--color-teach-accent-bg); }
1134
1151
  white-space: nowrap;
1135
1152
  }
1136
1153
 
1137
- .toc .state::before {
1138
- content: "";
1139
- width: 6px;
1140
- height: 6px;
1141
- border-radius: 99px;
1142
- background: var(--color-border);
1143
- flex: none;
1144
- }
1145
-
1146
- .toc .state.done { color: var(--color-text-secondary); }
1147
- .toc .state.done::before { background: var(--color-accent); }
1148
- .toc .state.next { color: var(--color-text-secondary); }
1149
- .toc .state.next::before { background: var(--color-accent); }
1150
-
1151
1154
  /* A link leaving the page says so with a mark beside it rather than with a
1152
1155
  badge spelling out the sentence. */
1153
1156
  .toc .ext {
1154
- font-size: 0.875rem;
1157
+ font-size: var(--t4);
1155
1158
  color: var(--color-muted);
1156
1159
  margin-left: 0.35rem;
1157
1160
  }
@@ -1162,7 +1165,7 @@ ol.succ {
1162
1165
  margin: 0 0 2.5rem;
1163
1166
  padding-left: 1.35rem;
1164
1167
  font-size: var(--t4);
1165
- line-height: 1.6;
1168
+ line-height: 1.55;
1166
1169
  color: var(--color-text-secondary);
1167
1170
  }
1168
1171
 
@@ -1185,10 +1188,6 @@ ol.succ li { margin-bottom: 0.4rem; }
1185
1188
  accent. Suppressing it only where the token above replaces it. */
1186
1189
  :where(a, button, summary, input, .opt):focus:not(:focus-visible) { outline: none; }
1187
1190
 
1188
- @media (max-width: 640px) {
1189
- body { font-size: 1.0625rem; }
1190
- }
1191
-
1192
1191
  /* The week plan. Its markup shipped with no rule matching any of its five
1193
1192
  classes, so it rendered as a run of inline spans with the ordinal, the
1194
1193
  title, the description, and the duration all touching. */
@@ -1218,7 +1217,7 @@ ol.succ li { margin-bottom: 0.4rem; }
1218
1217
 
1219
1218
  .road-t {
1220
1219
  font-family: var(--teach-mono);
1221
- font-size: 0.75rem;
1220
+ font-size: var(--t5);
1222
1221
  color: var(--color-muted);
1223
1222
  white-space: nowrap;
1224
1223
  }
@@ -1253,6 +1252,7 @@ const TEACH_QUIZ: Component = {
1253
1252
  '--color-teach-accent-bg',
1254
1253
  '--t4',
1255
1254
  '--t5',
1255
+ '--t6',
1256
1256
  ],
1257
1257
  rules: `/* ---- Quiz ---- */
1258
1258
 
@@ -1284,7 +1284,7 @@ const TEACH_QUIZ: Component = {
1284
1284
  .opt::before {
1285
1285
  content: attr(data-k);
1286
1286
  font-family: var(--teach-mono);
1287
- font-size: 0.7188rem;
1287
+ font-size: var(--t6);
1288
1288
  color: var(--color-muted);
1289
1289
  border: 1px solid var(--color-border);
1290
1290
  border-radius: 5px;
@@ -1309,23 +1309,21 @@ const TEACH_QUIZ: Component = {
1309
1309
  }
1310
1310
 
1311
1311
  .opt[data-state="chosen"]::after {
1312
- content: "your answer";
1312
+ content: "Your answer";
1313
1313
  margin-left: auto;
1314
- font-size: 0.6563rem;
1314
+ font-size: var(--t6);
1315
1315
  font-weight: 700;
1316
- letter-spacing: 0.07em;
1317
- text-transform: uppercase;
1316
+ letter-spacing: 0;
1318
1317
  color: var(--color-muted);
1319
1318
  align-self: center;
1320
1319
  }
1321
1320
 
1322
1321
  .opt[data-state="right"]::after {
1323
- content: "correct";
1322
+ content: "Correct";
1324
1323
  margin-left: auto;
1325
- font-size: 0.6563rem;
1324
+ font-size: var(--t6);
1326
1325
  font-weight: 700;
1327
- letter-spacing: 0.07em;
1328
- text-transform: uppercase;
1326
+ letter-spacing: 0;
1329
1327
  color: var(--color-accent);
1330
1328
  align-self: center;
1331
1329
  }
@@ -1374,34 +1372,37 @@ const TEACH_GLOSSARY: Component = {
1374
1372
  ],
1375
1373
  rules: `/* ---- Glossary view, generated from the markdown source ---- */
1376
1374
 
1377
- h2 .count {
1378
- font-family: var(--teach-mono);
1379
- font-size: 0.75rem;
1380
- font-weight: 400;
1381
- color: var(--color-muted);
1382
- border: 1px solid var(--color-border);
1383
- border-radius: 99px;
1384
- padding: 0.1rem 0.5rem;
1385
- margin-left: 0.5rem;
1386
- vertical-align: middle;
1375
+ /* No border, sitting on the page ground, with the count beside it. The border and the
1376
+ reading surface are what made the control look like something to fill in,
1377
+ and the count is what says it filters the list under it. */
1378
+ .filter-row {
1379
+ display: flex;
1380
+ align-items: baseline;
1381
+ gap: 1rem;
1382
+ margin: 0 0 1rem;
1387
1383
  }
1388
1384
 
1389
1385
  .filter {
1390
- width: 100%;
1386
+ flex: 1;
1387
+ min-width: 0;
1391
1388
  font-family: var(--teach-sans);
1392
1389
  font-size: var(--t4);
1393
1390
  color: var(--color-text-body);
1394
- background: var(--color-background);
1395
- border: 1px solid var(--color-border);
1396
- border-radius: 7px;
1397
- padding: 0.55rem 0.8rem;
1398
- margin: 0 0 1rem;
1391
+ background: transparent;
1392
+ border: 0;
1393
+ padding: 0.3rem 0;
1399
1394
  }
1400
1395
 
1401
- /* Scoped to pointer focus. Written as \`.filter:focus\` it outranked the
1402
- \`:where(...)\` focus token, whose specificity is zero, and the filter was the
1403
- one control on the page that a keyboard reached with no ring at all. */
1404
- .filter:focus { border-color: var(--color-accent); }
1396
+ .filter-count {
1397
+ font-family: var(--teach-mono);
1398
+ font-size: var(--t5);
1399
+ color: var(--color-muted);
1400
+ white-space: nowrap;
1401
+ }
1402
+
1403
+ /* The \`:where(...)\` focus token has zero specificity, so a \`.filter:focus\`
1404
+ rule outranks it and leaves a keyboard reaching the control with no ring.
1405
+ The ring stays the token's and only the pointer ring is dropped. */
1405
1406
  .filter:focus:not(:focus-visible) { outline: none; }
1406
1407
  .filter::placeholder { color: var(--color-muted); }
1407
1408
 
@@ -1449,8 +1450,7 @@ h2 .count {
1449
1450
  font-size: var(--t5);
1450
1451
  font-weight: 600;
1451
1452
  color: var(--color-muted);
1452
- text-transform: uppercase;
1453
- letter-spacing: 0.02em;
1453
+ letter-spacing: 0;
1454
1454
  }
1455
1455
 
1456
1456
  .gloss-group:first-of-type { margin-top: 0; }
@@ -1469,7 +1469,7 @@ ol.succ {
1469
1469
  margin: 0 0 2.5rem;
1470
1470
  padding-left: 1.35rem;
1471
1471
  font-size: var(--t4);
1472
- line-height: 1.6;
1472
+ line-height: 1.55;
1473
1473
  color: var(--color-text-secondary);
1474
1474
  }
1475
1475
 
@@ -1479,7 +1479,7 @@ ol.succ li { margin-bottom: 0.4rem; }
1479
1479
  /* A filter that matches nothing needs a way back, not just a sentence. */
1480
1480
  .gloss .empty .clear {
1481
1481
  font-family: var(--teach-sans);
1482
- font-size: 0.875rem;
1482
+ font-size: var(--t4);
1483
1483
  color: var(--color-accent);
1484
1484
  background: none;
1485
1485
  border: 0;
@@ -1534,6 +1534,7 @@ const TEACH_REFERENCES: Component = {
1534
1534
  '--teach-mono',
1535
1535
  '--color-teach-accent-bg',
1536
1536
  '--t5',
1537
+ '--t6',
1537
1538
  ],
1538
1539
  rules: `footer {
1539
1540
  margin-top: 4rem;
@@ -1548,7 +1549,7 @@ const TEACH_REFERENCES: Component = {
1548
1549
 
1549
1550
  sup.cite {
1550
1551
  font-family: var(--teach-mono);
1551
- font-size: 0.6875em;
1552
+ font-size: var(--t6);
1552
1553
  line-height: 0;
1553
1554
  margin-left: 0.1em;
1554
1555
  }
@@ -1569,7 +1570,7 @@ ol.refs {
1569
1570
  padding: 0;
1570
1571
  margin: 0.9rem 0 1.1rem;
1571
1572
  font-size: var(--t5);
1572
- line-height: 1.55;
1573
+ line-height: 1.6;
1573
1574
  }
1574
1575
 
1575
1576
  ol.refs li {