@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.
Files changed (201) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +11 -13
  3. package/claude/.claude-plugin/plugin.json +1 -1
  4. package/claude/skills/canon-cli/SKILL.md +5 -8
  5. package/claude/skills/context-fold/REQUIREMENT.md +0 -1
  6. package/claude/skills/design-identity/REQUIREMENT.md +2 -2
  7. package/claude/skills/design-identity/SKILL.md +6 -6
  8. package/claude/skills/design-record/REQUIREMENT.md +2 -2
  9. package/claude/skills/design-record/SKILL.md +4 -4
  10. package/claude/skills/design-record/references/folder.md +1 -1
  11. package/claude/skills/design-taste/REQUIREMENT.md +1 -1
  12. package/claude/skills/design-taste/references/kinds.md +0 -1
  13. package/claude/skills/draw-figure/REQUIREMENT.md +3 -4
  14. package/claude/skills/draw-figure/SKILL.md +5 -6
  15. package/claude/skills/draw-figure/references/figures.md +0 -1
  16. package/claude/skills/draw-figure/references/hand-drawn.md +1 -1
  17. package/claude/skills/pick-by-eye/REQUIREMENT.md +4 -5
  18. package/claude/skills/pick-by-eye/SKILL.md +34 -41
  19. package/claude/skills/pick-by-eye/references/board.md +42 -0
  20. package/claude/skills/pick-by-eye/references/live-arms.md +3 -3
  21. package/claude/skills/plan-groundwork/SKILL.md +1 -1
  22. package/claude/skills/teach-craft/REQUIREMENT.md +30 -0
  23. package/claude/skills/teach-craft/SKILL.md +70 -0
  24. package/claude/skills/teach-craft/references/adopted.md +113 -0
  25. package/claude/skills/teach-craft/references/lesson-craft.md +60 -0
  26. package/claude/skills/{teach-workspace → teach-craft}/references/pedagogy.md +41 -6
  27. package/docs/agents/audits.md +0 -1
  28. package/docs/agents/install-and-sync.md +0 -8
  29. package/docs/target/migrations.md +16 -2
  30. package/docs/target/projects.md +1 -1
  31. package/docs/workflow/parallel-features.md +1 -1
  32. package/docs/workflow/skill-map-on-request.md +10 -22
  33. package/docs/workflow/skill-map.md +11 -14
  34. package/docs/workflow/visual-design-workflow.md +1 -4
  35. package/governance/detect.toml +1 -6
  36. package/governance/renames.toml +0 -1
  37. package/package.json +2 -8
  38. package/scripts/core/regen-web-previews.ts +2 -67
  39. package/src/audits/catalog.ts +0 -1
  40. package/src/browser/engine.ts +3 -6
  41. package/src/capture/render.ts +1 -1
  42. package/src/claude/cases/authoring.ts +0 -12
  43. package/src/claude/cases/misc.ts +0 -22
  44. package/src/claude/cases/workflow.ts +2 -38
  45. package/src/claude/skills-audit.ts +1 -1
  46. package/src/cli/help.ts +0 -10
  47. package/src/cli.ts +0 -8
  48. package/src/commands/design.ts +16 -315
  49. package/src/commands/records.ts +0 -1
  50. package/src/commands/serve.ts +2 -2
  51. package/src/commands/sync.ts +4 -5
  52. package/src/design/components.ts +8 -1514
  53. package/src/design/css.ts +5 -6
  54. package/src/design/fonts.ts +3 -5
  55. package/src/design/regen.ts +5 -32
  56. package/src/design/render.ts +1 -1
  57. package/src/design/tokens.ts +3 -2
  58. package/src/driver/drive.ts +3 -4
  59. package/src/driver/steps.ts +2 -5
  60. package/src/gate/stages.ts +4 -10
  61. package/src/gov/audit.ts +1 -6
  62. package/src/inventory/walk.ts +1 -1
  63. package/src/markdown/scope.ts +6 -35
  64. package/src/records/validate.ts +4 -176
  65. package/src/sandbox/equivalence.ts +0 -5
  66. package/src/shipped/references.ts +1 -2
  67. package/src/sync/check.ts +4 -29
  68. package/src/sync/stamp.ts +6 -6
  69. package/src/sync/target.ts +1 -4
  70. package/tooling/claude/reference.md +1 -1
  71. package/tooling/claude/seeds/canon/DESIGN.md +1 -1
  72. package/claude/skills/canvas-board/REQUIREMENT.md +0 -35
  73. package/claude/skills/canvas-board/SKILL.md +0 -143
  74. package/claude/skills/canvas-board/references/canvas.md +0 -170
  75. package/claude/skills/draft-and-pick/REQUIREMENT.md +0 -26
  76. package/claude/skills/draft-and-pick/SKILL.md +0 -11
  77. package/claude/skills/draft-doc/REQUIREMENT.md +0 -26
  78. package/claude/skills/draft-doc/SKILL.md +0 -11
  79. package/claude/skills/draft-figure/REQUIREMENT.md +0 -26
  80. package/claude/skills/draft-figure/SKILL.md +0 -11
  81. package/claude/skills/draft-identity/REQUIREMENT.md +0 -26
  82. package/claude/skills/draft-identity/SKILL.md +0 -11
  83. package/claude/skills/draft-ready/REQUIREMENT.md +0 -26
  84. package/claude/skills/draft-ready/SKILL.md +0 -11
  85. package/claude/skills/draft-screencast/REQUIREMENT.md +0 -26
  86. package/claude/skills/draft-screencast/SKILL.md +0 -11
  87. package/claude/skills/draft-slides/REQUIREMENT.md +0 -26
  88. package/claude/skills/draft-slides/SKILL.md +0 -11
  89. package/claude/skills/make-deck/REQUIREMENT.md +0 -36
  90. package/claude/skills/make-deck/SKILL.md +0 -80
  91. package/claude/skills/make-deck/references/deck.md +0 -183
  92. package/claude/skills/read-frames/REQUIREMENT.md +0 -30
  93. package/claude/skills/read-frames/SKILL.md +0 -55
  94. package/claude/skills/record-screencast/REQUIREMENT.md +0 -35
  95. package/claude/skills/record-screencast/SKILL.md +0 -99
  96. package/claude/skills/script-screencast/REQUIREMENT.md +0 -34
  97. package/claude/skills/script-screencast/SKILL.md +0 -70
  98. package/claude/skills/script-screencast/references/beats-template.md +0 -119
  99. package/claude/skills/teach-workspace/REQUIREMENT.md +0 -34
  100. package/claude/skills/teach-workspace/SKILL.md +0 -196
  101. package/claude/skills/teach-workspace/references/glossary.md +0 -90
  102. package/claude/skills/teach-workspace/references/lesson-build.md +0 -106
  103. package/claude/skills/teach-workspace/references/lesson-craft.md +0 -135
  104. package/claude/skills/teach-workspace/references/promotion-fold.md +0 -35
  105. package/claude/skills/teach-workspace/references/promotion.md +0 -116
  106. package/claude/skills/teach-workspace/references/revisit.md +0 -28
  107. package/claude/skills/teach-workspace/references/teach.md +0 -211
  108. package/claude/skills/video-craft/REQUIREMENT.md +0 -35
  109. package/claude/skills/video-craft/SKILL.md +0 -96
  110. package/claude/skills/video-craft/references/adopted.md +0 -110
  111. package/governance/rules/ui/canvas.md +0 -13
  112. package/src/canvas/address.ts +0 -227
  113. package/src/canvas/arrange.ts +0 -66
  114. package/src/canvas/capture.ts +0 -391
  115. package/src/canvas/client/app.tsx +0 -188
  116. package/src/canvas/client/inspector/align-grid.tsx +0 -162
  117. package/src/canvas/client/inspector/color-field.tsx +0 -398
  118. package/src/canvas/client/inspector/color-inputs.tsx +0 -139
  119. package/src/canvas/client/inspector/color-picker.tsx +0 -668
  120. package/src/canvas/client/inspector/color-space.ts +0 -240
  121. package/src/canvas/client/inspector/field.tsx +0 -263
  122. package/src/canvas/client/inspector/fields.css +0 -763
  123. package/src/canvas/client/inspector/section.tsx +0 -27
  124. package/src/canvas/client/inspector/size-field.tsx +0 -69
  125. package/src/canvas/client/inspector/token-select.tsx +0 -109
  126. package/src/canvas/client/inspector/values.ts +0 -297
  127. package/src/canvas/client/inspector.tsx +0 -692
  128. package/src/canvas/client/keys.ts +0 -71
  129. package/src/canvas/client/layers.tsx +0 -118
  130. package/src/canvas/client/main.tsx +0 -71
  131. package/src/canvas/client/pages-panel.tsx +0 -212
  132. package/src/canvas/client/panel-handle.tsx +0 -90
  133. package/src/canvas/client/selection.tsx +0 -199
  134. package/src/canvas/client/shell.css +0 -762
  135. package/src/canvas/client/state.ts +0 -792
  136. package/src/canvas/client/surface.tsx +0 -803
  137. package/src/canvas/client/theme-panel.tsx +0 -55
  138. package/src/canvas/client/tools.tsx +0 -153
  139. package/src/canvas/content.ts +0 -781
  140. package/src/canvas/edit.ts +0 -575
  141. package/src/canvas/editing.ts +0 -203
  142. package/src/canvas/fit.ts +0 -238
  143. package/src/canvas/global.d.ts +0 -15
  144. package/src/canvas/history.ts +0 -116
  145. package/src/canvas/index.html +0 -20
  146. package/src/canvas/server.ts +0 -766
  147. package/src/canvas/tokens.ts +0 -221
  148. package/src/commands/canvas.ts +0 -1097
  149. package/src/commands/demo.ts +0 -542
  150. package/src/commands/slides.ts +0 -184
  151. package/src/commands/teach.ts +0 -1401
  152. package/src/demo/beats.ts +0 -135
  153. package/src/demo/compile.ts +0 -350
  154. package/src/demo/container.ts +0 -219
  155. package/src/demo/cursors.ts +0 -55
  156. package/src/demo/drive.ts +0 -442
  157. package/src/demo/pointer.ts +0 -178
  158. package/src/demo/theme.ts +0 -112
  159. package/src/demo/timeline.ts +0 -64
  160. package/src/design/adapter.ts +0 -59
  161. package/src/design/base.css +0 -203
  162. package/src/design/brand-favicon.ts +0 -18
  163. package/src/design/neutral.ts +0 -127
  164. package/src/design/project-fonts.ts +0 -392
  165. package/src/slides/convert/chart.ts +0 -165
  166. package/src/slides/convert/deck.ts +0 -244
  167. package/src/slides/convert/export.ts +0 -567
  168. package/src/slides/convert/master.ts +0 -198
  169. package/src/slides/convert/shapes.ts +0 -804
  170. package/src/slides/convert/svg.ts +0 -38
  171. package/src/slides/convert/walk.ts +0 -671
  172. package/src/slides/decks.ts +0 -110
  173. package/src/slides/open.ts +0 -18
  174. package/src/slides/package/fonts.ts +0 -301
  175. package/src/slides/package/motion.ts +0 -316
  176. package/src/slides/package/test-face.ts +0 -113
  177. package/src/teach/browser/compile.ts +0 -44
  178. package/src/teach/browser/dismiss.ts +0 -76
  179. package/src/teach/browser/focus-line.ts +0 -19
  180. package/src/teach/browser/glossary-filter.ts +0 -44
  181. package/src/teach/browser/head.ts +0 -30
  182. package/src/teach/browser/quiz.ts +0 -19
  183. package/src/teach/browser/reload.ts +0 -9
  184. package/src/teach/browser/sidebar.ts +0 -233
  185. package/src/teach/browser/theme.ts +0 -18
  186. package/src/teach/components/code.tsx +0 -24
  187. package/src/teach/components/heading.tsx +0 -11
  188. package/src/teach/components/list.tsx +0 -17
  189. package/src/teach/components/paragraph.tsx +0 -29
  190. package/src/teach/components/refs.tsx +0 -28
  191. package/src/teach/cross-references.ts +0 -78
  192. package/src/teach/fonts.ts +0 -16
  193. package/src/teach/highlight.ts +0 -71
  194. package/src/teach/html/jsx-dev-runtime.ts +0 -13
  195. package/src/teach/html/jsx-runtime.ts +0 -113
  196. package/src/teach/html/raw.ts +0 -28
  197. package/src/teach/lesson.ts +0 -178
  198. package/src/teach/nav.ts +0 -1337
  199. package/src/teach/render.ts +0 -240
  200. package/src/teach/workspace.ts +0 -1164
  201. package/src/teach/write-if-changed.ts +0 -17
package/src/demo/theme.ts DELETED
@@ -1,112 +0,0 @@
1
- import { readdirSync, readFileSync } from 'node:fs'
2
- import { join } from 'node:path'
3
- import { cursorHotspot, type Cursor, type CursorSet } from '@/demo/pointer'
4
-
5
- /**
6
- * Reads a cursor theme folder so `--cursor` can point at one. Spike 3 measured
7
- * the browser decoding a Windows cursor resource directly, so nothing here
8
- * converts anything: the bytes go into a data URI and the header supplies the
9
- * hotspot.
10
- *
11
- * Three of the nineteen states a theme carries are read. A drag, a resize, or a
12
- * wait shows the default arrow where a real session would show something else,
13
- * and the two animated states have no still frame to draw at all.
14
- */
15
-
16
- const CURSOR_MIME = 'image/x-icon'
17
- const EXTENSION = /\.cur$/i
18
-
19
- /** Ordered per state, most specific first, so an exact name beats a longer one. */
20
- const NAMES: Record<string, readonly string[]> = {
21
- default: ['arrow', 'normal', 'default'],
22
- pointer: ['link', 'hand', 'pointer'],
23
- text: ['ibeam', 'beam', 'text'],
24
- }
25
-
26
- export type CursorFiles = Partial<Record<string, string>>
27
-
28
- /**
29
- * Picks one file per state out of a directory listing. Matching runs on the
30
- * name rather than on the contents because a theme states its intent there, and
31
- * a shorter match wins so `Arrow.cur` beats `Arrow Alternate.cur`.
32
- */
33
- export function matchCursorFiles(files: readonly string[]): CursorFiles {
34
- const cursors = files.filter((file) => EXTENSION.test(file))
35
- const matched: CursorFiles = {}
36
-
37
- for (const [state, names] of Object.entries(NAMES)) {
38
- const found = names
39
- .flatMap((name) =>
40
- cursors.filter((file) => file.toLowerCase().includes(name)),
41
- )
42
- .sort((left, right) => left.length - right.length)[0]
43
- if (found) matched[state] = found
44
- }
45
-
46
- return matched
47
- }
48
-
49
- export type ThemeLoad =
50
- | { status: 'loaded'; cursors: CursorSet; states: string[] }
51
- | { status: 'failed'; reason: string }
52
-
53
- /**
54
- * Falls back to the bundled artwork per state rather than per theme, so a
55
- * folder carrying an arrow and no hand still contributes its arrow.
56
- */
57
- export function loadCursorTheme(dir: string, fallback: CursorSet): ThemeLoad {
58
- let listing: string[]
59
- try {
60
- listing = readdirNames(dir)
61
- } catch (error) {
62
- return {
63
- status: 'failed',
64
- reason: `${dir} could not be read: ${message(error)}`,
65
- }
66
- }
67
-
68
- const matched = matchCursorFiles(listing)
69
- const states = Object.keys(matched)
70
- if (!states.length) {
71
- return {
72
- status: 'failed',
73
- reason: `${dir} holds no .cur file named after a pointer, link, or text state`,
74
- }
75
- }
76
-
77
- const cursors: Record<string, Cursor> = { ...fallback }
78
- for (const [state, file] of Object.entries(matched)) {
79
- if (!file) continue
80
- const loaded = readCursor(join(dir, file))
81
- if (loaded) cursors[state] = loaded
82
- }
83
-
84
- return { status: 'loaded', cursors, states }
85
- }
86
-
87
- function readCursor(path: string): Cursor | undefined {
88
- let bytes: Uint8Array
89
- try {
90
- bytes = readFileSync(path)
91
- } catch {
92
- return undefined
93
- }
94
-
95
- const hotspot = cursorHotspot(bytes)
96
- if (!hotspot) return undefined
97
-
98
- return {
99
- image: `data:${CURSOR_MIME};base64,${Buffer.from(bytes).toString('base64')}`,
100
- hotspot,
101
- }
102
- }
103
-
104
- function readdirNames(dir: string): string[] {
105
- return readdirSync(dir, { withFileTypes: true })
106
- .filter((entry) => entry.isFile())
107
- .map((entry) => entry.name)
108
- }
109
-
110
- function message(error: unknown): string {
111
- return error instanceof Error ? error.message : String(error)
112
- }
@@ -1,64 +0,0 @@
1
- import { mkdirSync, writeFileSync } from 'node:fs'
2
- import { basename, dirname, extname, join } from 'node:path'
3
- import type { DemoStep } from '@/demo/compile'
4
-
5
- /**
6
- * A step's target in viewport pixels, which are the video frame's own
7
- * coordinates because the recording is sized to the viewport.
8
- */
9
- export interface TargetBox {
10
- readonly x: number
11
- readonly y: number
12
- readonly width: number
13
- readonly height: number
14
- }
15
-
16
- /** One step as the run clocked it, in wall-clock milliseconds. */
17
- export interface StepTiming {
18
- readonly index: number
19
- readonly kind: DemoStep['kind']
20
- readonly startedAt: number
21
- readonly endedAt: number
22
- readonly box?: TargetBox
23
- }
24
-
25
- /**
26
- * Times count from the recording's start rather than the first step, since
27
- * that is the clock a composition places the take on. A step ends once its
28
- * hold has played, so the span covers what the viewer sees of it.
29
- */
30
- export interface TimelineEntry {
31
- readonly index: number
32
- readonly kind: DemoStep['kind']
33
- readonly startMs: number
34
- readonly endMs: number
35
- readonly box?: TargetBox
36
- }
37
-
38
- export function timelineEntry(
39
- timing: StepTiming,
40
- recordingStart: number,
41
- ): TimelineEntry {
42
- return {
43
- index: timing.index,
44
- kind: timing.kind,
45
- startMs: Math.round(timing.startedAt - recordingStart),
46
- endMs: Math.round(timing.endedAt - recordingStart),
47
- ...(timing.box ? { box: timing.box } : {}),
48
- }
49
- }
50
-
51
- export function timelinePath(videoPath: string): string {
52
- return join(
53
- dirname(videoPath),
54
- `${basename(videoPath, extname(videoPath))}.timeline.json`,
55
- )
56
- }
57
-
58
- export function writeTimeline(
59
- path: string,
60
- entries: readonly TimelineEntry[],
61
- ): void {
62
- mkdirSync(dirname(path), { recursive: true })
63
- writeFileSync(path, `${JSON.stringify({ entries }, null, 2)}\n`)
64
- }
@@ -1,59 +0,0 @@
1
- import { basename, join } from 'node:path'
2
- import { DESIGN_BASE_CSS } from '@/design/regen'
3
- import type { InstalledFile, SyncAdapter } from '@/sync/engine'
4
-
5
- /**
6
- * The channel a design value reaches a target on.
7
- *
8
- * Base plus a target-owned override, reconciled by the three-way merge the sync
9
- * engine already implements. Overwrite was dropped before this was built and
10
- * the reason is a measurement rather than an argument: a headless sync in
11
- * another domain deleted a deploy job, replaced a screenshot harness, and
12
- * reverted a shipped fix, because naming a stack was read as consent to lose an
13
- * edit inside it.
14
- *
15
- * Layering is what bounds what the merge has to arbitrate. Nothing a project
16
- * wrote lives in the base, so the base changes freely, and the merge shrinks to
17
- * the properties a target actually overrode.
18
- */
19
-
20
- /** The folder a design install writes into, under the target's `.claude/`. */
21
- export const DESIGN_INSTALL_DIR = join('.claude', 'design')
22
-
23
- /**
24
- * Where a target's own values go. `projectSubdir` makes every file here
25
- * project-authored by location rather than by the name inference, so an
26
- * override named `base.css` is still the target's and never overwritten.
27
- *
28
- * It ships absent rather than empty. An empty override is a file a project did
29
- * not ask for and did not write, the merge already handles a missing side, and
30
- * an empty file invites a target to fill it before it has an opinion.
31
- */
32
- export const DESIGN_PROJECT_SUBDIR = 'project'
33
-
34
- export function designSourceDir(root: string): string {
35
- return join(root, 'src', 'design')
36
- }
37
-
38
- export function createDesignAdapter(root: string): SyncAdapter {
39
- const base = join(root, DESIGN_BASE_CSS)
40
-
41
- return {
42
- banner: 'canon design sync',
43
- label: 'design',
44
- missingMessage:
45
- "No design surface found in target. Run 'canon design install' first.",
46
- unit: 'changes',
47
- installPattern: '**/*.css',
48
- installedRoot: (target: string) => join(target, DESIGN_INSTALL_DIR),
49
- /**
50
- * One shipped file, matched by name the way the gov adapter matches a rule,
51
- * so a base that moved inside the toolkit still syncs into the place the
52
- * target already holds it.
53
- */
54
- locateSource: (file: InstalledFile) =>
55
- basename(file.path) === basename(base) ? base : undefined,
56
- projectSubdir: DESIGN_PROJECT_SUBDIR,
57
- stamp: { domain: 'design', toolkitRoot: root },
58
- }
59
- }
@@ -1,203 +0,0 @@
1
- /* Generated by `canon design regen` from src/design/neutral.ts. Do not edit.
2
- This is the base half of a design install. A target overrides a value in
3
- .claude/design/project/, which the three-way merge leaves alone, so this
4
- file changes freely and nothing a project wrote lives in it. */
5
-
6
- :root {
7
- --color-background: #0e0e0e;
8
- --color-surface: #141414;
9
- --color-chrome: #1a1a1a;
10
- --color-border: #292929;
11
- --color-text: #dddddd;
12
- --color-text-body: #b2b2b2;
13
- --color-text-secondary: #8e8e8e;
14
- --color-muted: #7f7f7f;
15
- --color-accent: #ececec;
16
- --color-success: #61c454;
17
- --color-light-background: #fafafa;
18
- --color-light-surface: #f1f1f1;
19
- --color-light-chrome: #e8e8e8;
20
- --color-light-text: #252525;
21
- --color-light-text-body: #444444;
22
- --color-light-text-secondary: #5f5f5f;
23
- --color-light-muted: #6c6c6c;
24
- --color-light-accent: #191919;
25
- --color-light-success: #2d6b22;
26
- --color-light-border: #d4d4d4;
27
- --space-xs: 0.25rem;
28
- --space-sm: 0.5rem;
29
- --space-md: 0.875rem;
30
- --space-lg: 1.5rem;
31
- --space-xl: 2.5rem;
32
- --space-2xl: 4rem;
33
- --space-3xl: 6rem;
34
- --t0: 3.175rem;
35
- --t1: 2.375rem;
36
- --t2: 1.375rem;
37
- --t3: 1.125rem;
38
- --t4: 0.9375rem;
39
- --t5: 0.8125rem;
40
- --t6: 0.6875rem;
41
- --type-display-family: Geist Variable, DejaVu Sans, sans-serif;
42
- --type-display-size: 2.375rem;
43
- --type-display-lh: 1.3;
44
- --type-page-display-family: Geist Variable, DejaVu Sans, sans-serif;
45
- --type-page-display-size: 3.175rem;
46
- --type-page-display-lh: 1.1;
47
- --type-heading-family: Geist Variable, DejaVu Sans, sans-serif;
48
- --type-heading-size: 1.375rem;
49
- --type-heading-lh: 1.3;
50
- --type-body-family: Geist Variable, DejaVu Sans, sans-serif;
51
- --type-body-size: 0.9375rem;
52
- --type-body-lh: 1.65;
53
- --type-label-family: Geist Variable, DejaVu Sans, sans-serif;
54
- --type-label-size: 0.8125rem;
55
- --type-label-lh: 1.45;
56
- --type-code-family: Noto Sans Mono, DejaVu Sans Mono, monospace;
57
- --type-code-size: 0.8125rem;
58
- --type-code-lh: 1.3;
59
- --radius-frame: 12px;
60
- --radius-panel: 10px;
61
- --radius-action: 7px;
62
- --radius-pill: 999px;
63
- --radius-marker: 999px;
64
- }
65
-
66
- [data-theme='light'] {
67
- --color-background: var(--color-light-background);
68
- --color-surface: var(--color-light-surface);
69
- --color-chrome: var(--color-light-chrome);
70
- --color-text: var(--color-light-text);
71
- --color-text-body: var(--color-light-text-body);
72
- --color-text-secondary: var(--color-light-text-secondary);
73
- --color-muted: var(--color-light-muted);
74
- --color-accent: var(--color-light-accent);
75
- --color-success: var(--color-light-success);
76
- --color-border: var(--color-light-border);
77
- }
78
-
79
- /* status
80
- A dot and a word, not a pill. A bordered uppercase chip is a second
81
- vocabulary for a fact the dot already carries, so the marker is the only
82
- status shape and `.is-done` is the only variant. */
83
-
84
- .status {
85
- display: inline-flex;
86
- align-items: center;
87
- gap: 0.42rem;
88
- font-size: 0.75rem;
89
- font-weight: 500;
90
- letter-spacing: 0;
91
- text-transform: none;
92
- color: var(--color-muted);
93
- white-space: nowrap;
94
- }
95
-
96
- .status::before {
97
- content: '';
98
- width: 6px;
99
- height: 6px;
100
- flex: none;
101
- border-radius: var(--radius-marker);
102
- background: var(--color-border);
103
- }
104
-
105
- .status.is-done::before {
106
- background: var(--color-accent);
107
- }
108
-
109
- /* scrollbar
110
- Every scrolling region takes the same bar, the page included. Scoping it
111
- to one component leaves its neighbors on the browser default, which is
112
- what reads as two designs on one page. `scrollbar-color` covers Firefox
113
- and the `::-webkit-` rules cover the rest, both from the same two tokens
114
- so the two engines cannot drift apart. */
115
-
116
- * {
117
- scrollbar-width: thin;
118
- scrollbar-color: var(--color-border) transparent;
119
- }
120
-
121
- *::-webkit-scrollbar {
122
- width: 10px;
123
- height: 10px;
124
- }
125
-
126
- *::-webkit-scrollbar-track {
127
- background: transparent;
128
- }
129
-
130
- *::-webkit-scrollbar-thumb {
131
- border-radius: var(--radius-marker);
132
- background: var(--color-border);
133
- /* Inset by painting a border in the page color, which is what keeps the
134
- thumb off the edges without a second element. */
135
- border: 3px solid var(--color-background);
136
- }
137
-
138
- *::-webkit-scrollbar-thumb:hover {
139
- background: var(--color-muted);
140
- }
141
-
142
- *::-webkit-scrollbar-corner {
143
- background: transparent;
144
- }
145
-
146
- /* hand-drawn-figure
147
- The hand-drawn SVG diagram figure and its caption, sized wider than the
148
- reading measure and cleared past an outline rail on a wide viewport. */
149
-
150
- /* ---- Figures: hand-drawn, and wider than the measure ---- */
151
-
152
- :root {
153
- --figure-hand: 'Virgil', 'Excalifont', cursive;
154
- --figure-wide: 64rem;
155
- }
156
-
157
- figure {
158
- margin: 2.75rem 0;
159
- width: var(--figure-wide);
160
- max-width: 92vw;
161
- margin-left: 50%;
162
- transform: translateX(-50%);
163
- }
164
-
165
- figure svg {
166
- width: 100%;
167
- height: auto;
168
- display: block;
169
- }
170
-
171
- /* CSS beats an SVG presentation attribute, so the diagrams pick up the
172
- hand face without editing a single lesson. */
173
- figure svg text {
174
- font-family: var(--figure-hand);
175
- }
176
-
177
- figcaption {
178
- font-family: var(--figure-hand);
179
- font-size: 1rem;
180
- color: var(--color-muted);
181
- margin: 1rem auto 0;
182
- line-height: 1.5;
183
- max-width: 52rem;
184
- }
185
-
186
- /* A figure breaks the measure and must still clear an outline rail. Deriving
187
- the ceiling from the viewport does not hold, since a rail positioned from
188
- the centre keeps overlapping as the window widens, so the ceiling is fixed
189
- instead. */
190
- @media (min-width: 1421px) {
191
- figure {
192
- max-width: 54rem;
193
- }
194
- }
195
-
196
- @media (max-width: 640px) {
197
- figure {
198
- width: 100%;
199
- max-width: 100%;
200
- margin-left: 0;
201
- transform: none;
202
- }
203
- }
@@ -1,18 +0,0 @@
1
- import { readFileSync } from 'node:fs'
2
- import { join } from 'node:path'
3
- import { FAVICON_COLORS, renderFavicon } from '@/design/favicon'
4
- import { PROJECT_ROOT } from '@/roots/project'
5
-
6
- const BRAND_MARK = 'assets/brand/mark.svg'
7
-
8
- /**
9
- * The brand icon, built from the mark and the favicon's own pair, the same two
10
- * inputs `web/public/favicon.svg` is generated from. Every surface that shows
11
- * the icon reads it here rather than keeping a copy that drifts from the mark.
12
- * It sits apart from `favicon.ts` because the card build imports that module
13
- * through an `@` alias pointed at `web/src`, so it must import nothing.
14
- */
15
- export function brandFavicon(): string {
16
- const mark = readFileSync(join(PROJECT_ROOT, BRAND_MARK), 'utf8')
17
- return renderFavicon(mark, FAVICON_COLORS)
18
- }
@@ -1,127 +0,0 @@
1
- import type { ColorToken, DesignTokens } from '@/design/tokens'
2
- import { TOKENS } from '@/design/tokens'
3
-
4
- /**
5
- * The token set `canon design install` hands a target, achromatic so the only
6
- * palette a target renders is one it wrote under `.claude/design/project/`.
7
- * Type, spacing, and radii are this repository's by spread, and only the color
8
- * list is a second copy, held to the same roles by `neutral.test.ts`.
9
- *
10
- * Grounds, surface, chrome, and border take the OKLCH lightness of the toolkit
11
- * role they replace at chroma 0. Text roles are solved against `surface` for
12
- * the contrast targets `TOKENS` records (13.5, 8.6, 5.6, 4.6), and the accent
13
- * for 15.5, past `text` so it still reads apart from body copy. The state
14
- * roles keep the toolkit's values, since a state color is a meaning rather
15
- * than a palette.
16
- */
17
-
18
- const DARK_GROUNDS = ['background', 'surface'] as const
19
- const LIGHT_GROUNDS = ['light-background', 'light-surface'] as const
20
-
21
- function toolkitRole(role: string): ColorToken {
22
- const token = TOKENS.color.find((candidate) => candidate.role === role)
23
- if (token === undefined) throw new Error(`TOKENS declares no ${role} role`)
24
- return token
25
- }
26
-
27
- const NEUTRAL_COLOR: readonly ColorToken[] = [
28
- { role: 'background', intent: 'page canvas', value: '#0e0e0e' },
29
- {
30
- role: 'surface',
31
- intent: 'cards, panels, raised blocks',
32
- value: '#141414',
33
- },
34
- {
35
- role: 'chrome',
36
- intent: 'the window titlebar, one step above the canvas',
37
- value: '#1a1a1a',
38
- },
39
- { role: 'border', intent: 'every rule and panel edge', value: '#292929' },
40
- {
41
- role: 'text',
42
- intent: 'headings, counts, emphasized runs',
43
- value: '#dddddd',
44
- grounds: DARK_GROUNDS,
45
- },
46
- {
47
- role: 'text-body',
48
- intent: 'default body copy',
49
- value: '#b2b2b2',
50
- grounds: DARK_GROUNDS,
51
- },
52
- {
53
- role: 'text-secondary',
54
- intent: 'labels, captions, supporting copy',
55
- value: '#8e8e8e',
56
- grounds: DARK_GROUNDS,
57
- },
58
- {
59
- role: 'muted',
60
- intent: 'the faintest step, trailing notes',
61
- value: '#7f7f7f',
62
- grounds: DARK_GROUNDS,
63
- },
64
- {
65
- role: 'accent',
66
- intent: 'install command, mark, primary action',
67
- value: '#ececec',
68
- grounds: DARK_GROUNDS,
69
- },
70
- toolkitRole('success'),
71
- toolkitRole('warning'),
72
- toolkitRole('error'),
73
- {
74
- role: 'light-background',
75
- intent: 'page canvas on a light ground',
76
- value: '#fafafa',
77
- },
78
- {
79
- role: 'light-surface',
80
- intent: 'cards and panels on a light ground',
81
- value: '#f1f1f1',
82
- },
83
- {
84
- role: 'light-chrome',
85
- intent: 'the window titlebar, one step above the canvas',
86
- value: '#e8e8e8',
87
- },
88
- {
89
- role: 'light-text',
90
- intent: 'primary text on a light ground',
91
- value: '#252525',
92
- grounds: LIGHT_GROUNDS,
93
- },
94
- {
95
- role: 'light-text-body',
96
- intent: 'default body copy on a light ground',
97
- value: '#444444',
98
- grounds: LIGHT_GROUNDS,
99
- },
100
- {
101
- role: 'light-text-secondary',
102
- intent: 'labels, captions, supporting copy on a light ground',
103
- value: '#5f5f5f',
104
- grounds: LIGHT_GROUNDS,
105
- },
106
- {
107
- role: 'light-muted',
108
- intent: 'secondary text on a light ground',
109
- value: '#6c6c6c',
110
- grounds: LIGHT_GROUNDS,
111
- },
112
- {
113
- role: 'light-accent',
114
- intent: 'links and primary action on light',
115
- value: '#191919',
116
- grounds: LIGHT_GROUNDS,
117
- },
118
- toolkitRole('light-success'),
119
- {
120
- role: 'light-border',
121
- intent: 'rules and panel edges on light',
122
- value: '#d4d4d4',
123
- verify: true,
124
- },
125
- ]
126
-
127
- export const NEUTRAL_TOKENS: DesignTokens = { ...TOKENS, color: NEUTRAL_COLOR }