@motion-proto/live-tokens 0.72.1 → 0.74.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 (81) hide show
  1. package/.claude/skills/live-tokens-build-page/SKILL.md +37 -2
  2. package/.claude/skills/live-tokens-build-page/references/layout-sources.md +48 -0
  3. package/.claude/skills/live-tokens-check-compliance/SKILL.md +2 -2
  4. package/.claude/skills/live-tokens-create-component/SKILL.md +4 -4
  5. package/.claude/skills/live-tokens-create-theme/SKILL.md +80 -0
  6. package/.claude/skills/live-tokens-create-theme/references/design-directions.md +94 -0
  7. package/.claude/skills/live-tokens-pick-component/SKILL.md +1 -1
  8. package/.claude/skills/live-tokens-set-colors/SKILL.md +140 -0
  9. package/.claude/skills/live-tokens-set-colors/references/color-anchors.md +80 -0
  10. package/.claude/skills/{live-tokens-adjust-geometry → live-tokens-set-geometry}/SKILL.md +12 -7
  11. package/.claude/skills/live-tokens-set-geometry/references/geometry-anchors.md +61 -0
  12. package/.claude/skills/{live-tokens-pair-fonts → live-tokens-set-type}/SKILL.md +18 -15
  13. package/.claude/skills/live-tokens-set-type/references/type-anchors.md +60 -0
  14. package/CHANGELOG.md +141 -0
  15. package/README.md +24 -15
  16. package/bin/check-page.mjs +3 -3
  17. package/bin/cli.mjs +92 -55
  18. package/bin/lib/liveState.mjs +110 -0
  19. package/bin/save-theme.mjs +177 -0
  20. package/bin/set-colors.mjs +191 -0
  21. package/bin/{adjust.mjs → set-geometry.mjs} +18 -50
  22. package/bin/{set-fonts.mjs → set-type.mjs} +21 -55
  23. package/dist-plugin/{chunk-RIXO2E55.js → chunk-7VRTBGJT.js} +1 -1
  24. package/dist-plugin/{chunk-YLCOIGQC.js → chunk-V3YF6CGT.js} +56 -2
  25. package/dist-plugin/index.cjs +58 -3
  26. package/dist-plugin/index.js +11 -11
  27. package/dist-plugin/migrateData/index.cjs +56 -1
  28. package/dist-plugin/migrateData/index.js +2 -2
  29. package/dist-plugin/{generateColorsAndType → setColors}/index.cjs +1109 -1071
  30. package/dist-plugin/{generateColorsAndType → setColors}/index.d.cts +33 -24
  31. package/dist-plugin/{generateColorsAndType → setColors}/index.d.ts +33 -24
  32. package/dist-plugin/{generateColorsAndType → setColors}/index.js +45 -63
  33. package/dist-plugin/{adjust → setGeometry}/index.cjs +60 -5
  34. package/dist-plugin/{adjust → setGeometry}/index.d.cts +1 -1
  35. package/dist-plugin/{adjust → setGeometry}/index.d.ts +1 -1
  36. package/dist-plugin/{adjust → setGeometry}/index.js +1 -1
  37. package/dist-plugin/{fontPairing → setType}/index.cjs +4 -4
  38. package/dist-plugin/{fontPairing → setType}/index.d.cts +1 -1
  39. package/dist-plugin/{fontPairing → setType}/index.d.ts +1 -1
  40. package/dist-plugin/{themeTypes-DSV3Zisf.d.cts → themeTypes-BxRtuN5V.d.cts} +1 -1
  41. package/dist-plugin/{themeTypes-DSV3Zisf.d.ts → themeTypes-BxRtuN5V.d.ts} +1 -1
  42. package/package.json +10 -2
  43. package/src/editor/core/themes/{generateColorsAndType.ts → buildColors.ts} +82 -96
  44. package/src/editor/core/themes/migrations/2026-09-03-drop-legacy-component-keys.ts +62 -0
  45. package/src/editor/core/themes/migrations/index.ts +2 -0
  46. package/src/editor/docs/content/creating-components.md +13 -0
  47. package/src/editor/docs/content/themes-workflow.md +2 -2
  48. package/src/editor/docs/content.generated.ts +2 -2
  49. package/src/editor/overlay/LiveEditorOverlay.svelte +519 -28
  50. package/src/editor/skill-atlas/SkillAtlas.svelte +836 -0
  51. package/src/editor/skill-atlas/SkillAtlas.svelte.d.ts +4 -0
  52. package/src/editor/skill-atlas/SourcePane.svelte +364 -0
  53. package/src/editor/skill-atlas/TreeNodeCard.svelte +206 -0
  54. package/src/editor/skill-atlas/skillSources.generated.ts +45 -0
  55. package/src/editor/skill-atlas/skillSources.ts +1 -0
  56. package/src/editor/skill-atlas/skillTrees.ts +3844 -0
  57. package/src/editor/skill-atlas/types.ts +65 -0
  58. package/src/live-tokens/data/colors-and-type/autumn.json +1 -37
  59. package/src/live-tokens/data/colors-and-type/default.json +1 -37
  60. package/src/live-tokens/data/colors-and-type/halloween.json +1 -37
  61. package/src/live-tokens/data/colors-and-type/midnight-study.json +1 -37
  62. package/src/live-tokens/data/colors-and-type/ocean.json +1 -37
  63. package/src/live-tokens/data/colors-and-type/royal-velvet.json +1 -37
  64. package/src/live-tokens/data/colors-and-type/sketchy.json +1 -37
  65. package/src/live-tokens/data/colors-and-type/spring-meadow.json +1 -37
  66. package/src/live-tokens/data/colors-and-type/sunset.json +1 -37
  67. package/src/live-tokens/data/themes/autumn.json +1 -37
  68. package/src/live-tokens/data/themes/halloween.json +1 -37
  69. package/src/live-tokens/data/themes/midnight-study.json +1 -37
  70. package/src/live-tokens/data/themes/ocean.json +1 -37
  71. package/src/live-tokens/data/themes/royal-velvet.json +1 -37
  72. package/src/live-tokens/data/themes/sketchy.json +1 -37
  73. package/src/live-tokens/data/themes/spring-meadow.json +1 -37
  74. package/src/live-tokens/data/themes/sunset.json +1 -37
  75. package/src/live-tokens/data/tokens.generated.css +0 -36
  76. package/.claude/skills/live-tokens-generate-theme/SKILL.md +0 -156
  77. package/.claude/skills/live-tokens-generate-theme/references/mood-vocabulary.md +0 -43
  78. package/.claude/skills/live-tokens-generate-theme/references/named-themes.md +0 -18
  79. package/.claude/skills/live-tokens-generate-theme/references/style-vocabulary.md +0 -35
  80. package/bin/generate-theme.mjs +0 -260
  81. /package/dist-plugin/{fontPairing → setType}/index.js +0 -0
@@ -1,35 +0,0 @@
1
- # Style vocabulary: idioms, eras, and genres
2
-
3
- Read this when the brief names a design idiom, a decade, or a genre. A mood
4
- moves dials; an idiom sets constraints, and it overrides the generic defaults in
5
- SKILL.md: the polished-UI Accent at 45% of Brand's chroma is right for a vague
6
- brief and wrong for Bauhaus.
7
-
8
- Entries are starting points, not seeds to copy: apply the chroma budget,
9
- per-role bands, and canvas commitment rules on top of them. Riso, Memphis, and
10
- brutalist break the chroma budget on purpose. Break it in the one layer the
11
- style is about and hold the rest of the ground tier down.
12
-
13
- The table runs modernist, digital, quiet, print, expressive.
14
-
15
- | Brief | Anchors (L, C, H) | Type | Geometry |
16
- |---|---|---|---|
17
- | Swiss, International | near-white canvas (0.97, 0.01, 0), or true black for the poster reading; one red Brand (0.55, 0.22, 27) as the only hue on screen; Neutral at C 0.005, untinted on purpose; monochromatic | one rational neo-grotesque across both slots | radius none, tight gaps, hairline borders |
18
- | Bauhaus | paper canvas (0.95, 0.02, 85) under primaries at full commitment: red (0.58, 0.21, 27), blue (0.48, 0.20, 264), yellow (0.86, 0.17, 90); triadic | geometric sans, heavy display | radius none on containers, radius full on buttons alone, so the circle reads as a decision |
19
- | Mid-century modern | canvas (0.90, 0.05, 75), mustard (0.75, 0.13, 85), teal (0.55, 0.10, 195), burnt orange (0.60, 0.15, 45), walnut neutrals H 60; nothing over C 0.16; compound | dynamic sans, or a geometric display over a dynamic body | radius +1 to +2, padding +1, no borders |
20
- | Art deco, opulent, luxurious | near-black canvas (0.20, 0.02, 280), gold (0.78, 0.13, 88), jade (0.60, 0.10, 165); dark, one metallic accent, everything else grey | rational high-contrast serif display, quiet body | sharp, padding +1, thin borders |
21
- | Terminal, phosphor | canvas (0.16, 0.01, 150), phosphor green Brand (0.80, 0.16, 145), amber Accent (0.80, 0.13, 80); monochromatic, dark, gradient off | mono in both slots | radius none, tight padding, a 1px border on everything |
22
- | Cyberpunk, neon noir, futuristic | canvas (0.18, 0.04, 300), magenta Brand (0.78, 0.18, 330), cyan Accent (0.82, 0.12, 200); complementary, dark, canvasGradient on for the glow | wide geometric display, neutral body | sharp, tight gaps |
23
- | Vaporwave | sunset canvas (0.88, 0.06, 330), pink (0.72, 0.16, 350), cyan (0.80, 0.11, 205), lilac Special; light, gradient on | wide retro display, serif welcome | rounder, airier |
24
- | Y2K, bubble | chrome canvas (0.96, 0.015, 240), electric blue Brand (0.62, 0.18, 255), lime Accent (0.85, 0.17, 130) | geometric sans, heavy display | radius full on buttons with the padding a pill needs, generous spacing |
25
- | Blueprint | canvas (0.35, 0.07, 245), pale rules (0.90, 0.02, 240), one warm accent (0.75, 0.14, 60); dark | mono, or a technical grotesque; a mono body works here and almost nowhere else | radius none, 1px borders, tight grid |
26
- | Scandinavian, hygge | chalk canvas (0.96, 0.012, 70), sage Brand (0.60, 0.06, 150), clay Accent (0.70, 0.08, 40); nothing above C 0.10; analogous | dynamic sans on both sides | soft radius, padding +1, hairline borders |
27
- | Japandi, wabi-sabi | unbleached paper canvas (0.93, 0.025, 80), ink Brand (0.35, 0.02, 250), one earth Accent (0.62, 0.09, 45); near-monochrome | rational serif display over a quiet humanist body | radius sm, padding +2, no borders |
28
- | Cottagecore, botanical | cream canvas (0.94, 0.04, 85), moss (0.55, 0.10, 135), dusty rose (0.70, 0.09, 15), butter (0.85, 0.11, 95); warm analogous | dynamic serif display over humanist sans | rounder, airier |
29
- | Editorial, magazine | paper canvas (0.97, 0.015, 85), ink neutrals, one strong Brand (0.50, 0.18, 20) carried by rules and pull quotes | high-contrast serif display over a text serif or humanist sans | sharp, padding +1, hairline rules |
30
- | Newsprint, broadsheet | grey-warm canvas (0.91, 0.02, 80), near-black ink, nothing chromatic above C 0.10 | rational serif in both slots | radius none, tight gaps, hairline rules |
31
- | Risograph, zine | paper canvas (0.94, 0.03, 80) with two flat spot inks, fluoro pink (0.68, 0.22, 5) and blue (0.52, 0.18, 260); complementary, no midtones between them | expressive display over a plain workhorse | radius none, borders +2 |
32
- | Corporate, professional, trustworthy | canvas (0.97, 0.012, 250), navy Brand (0.48, 0.12, 255), teal Accent (0.60, 0.09, 195), conventional statuses | rational sans body with a rational serif or same-family display | leave it alone |
33
- | Brutalist | pure canvas, (0.98, 0, 0) or (0.15, 0, 0), with Neutral at C 0, untinted because that is the point; one alarming Brand (0.58, 0.24, 27) | neo-grotesque or mono at heavy weight | radius none, borders +2 to +3, tight padding |
34
- | Memphis, postmodern | pastel canvas (0.95, 0.03, 60) carrying full-chroma primaries and a hot pink Special (0.70, 0.20, 350); tetradic or square, four hue families on purpose | geometric display, plain body | targeted rather than global: pill buttons against radius-none cards |
35
- | Industrial, workshop, gritty | concrete canvas (0.88, 0.008, 250), or (0.22, 0.01, 250) dark, safety orange Brand (0.68, 0.18, 50), steel neutrals | condensed grotesque display over a plain body | radius sm, thick borders, tight padding |
@@ -1,260 +0,0 @@
1
- // `live-tokens generate-theme` worker.
2
- //
3
- // Reads a seed brief (JSON), builds a full validated colors-and-type layer via
4
- // the compiled engine (dist-plugin/generateColorsAndType — the CLI never imports
5
- // TS sources), enforces the AA contrast gate, and saves the result as a theme:
6
- // <themesDir>/<slug>.json, the document that carries the whole theme by value.
7
- // Unless --no-activate it then opens that theme the way the dev server's apply
8
- // door does — existing `_working` buffers are cleared and
9
- // `themes/_active.json` names it. Nothing else moves, so generating a theme
10
- // cannot change what the site ships.
11
- //
12
- // Non-color content (shadows, component aliases, fonts) carries forward from
13
- // the live state so generation only replaces the color identity. Swatch
14
- // gradients ride along too when user-tuned; stock ones are rebuilt from the new
15
- // theme's families by the engine.
16
-
17
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
18
- import { dirname, join, relative, resolve } from 'node:path';
19
- import { fileURLToPath } from 'node:url';
20
-
21
- const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
22
- const ENGINE = resolve(pkgRoot, 'dist-plugin/generateColorsAndType/index.js');
23
- const packageDataDir = join(pkgRoot, 'src/live-tokens/data');
24
- // Source of truth: src/editor/core/themes/themeTypes.ts, which
25
- // normalizeTheme.ts re-exports. This copy cannot import TS, so
26
- // `check:preset-themes` and generate-theme.test.ts are what catch a drift.
27
- const THEME_SCHEMA_VERSION = 5;
28
-
29
- async function loadEngine() {
30
- if (!existsSync(ENGINE)) {
31
- throw new Error(
32
- `theme engine not found at ${relative(process.cwd(), ENGINE)}. ` +
33
- `Build the plugin first (npm run build:plugin).`,
34
- );
35
- }
36
- return import(ENGINE);
37
- }
38
-
39
- function readJson(path) {
40
- return JSON.parse(readFileSync(path, 'utf8'));
41
- }
42
-
43
- function readJsonIfExists(path) {
44
- try {
45
- return existsSync(path) ? readJson(path) : null;
46
- } catch {
47
- return null;
48
- }
49
- }
50
-
51
- /** Local dir first, then the installed package's shipped copy — the same
52
- * fallback order the dev server's reads use. */
53
- function readData(localDir, packageSubdir, fileName) {
54
- return (
55
- readJsonIfExists(join(localDir, `${fileName}.json`)) ??
56
- readJsonIfExists(join(packageDataDir, packageSubdir, `${fileName}.json`))
57
- );
58
- }
59
-
60
- function componentNames(componentConfigsDir) {
61
- if (!existsSync(componentConfigsDir)) return [];
62
- return readdirSync(componentConfigsDir, { withFileTypes: true })
63
- .filter((e) => e.isDirectory())
64
- .map((e) => e.name)
65
- .sort();
66
- }
67
-
68
- /** `_fileName` and `_source` are read-door markers, never part of a document. */
69
- function stripMarkers(value) {
70
- if (!value || typeof value !== 'object') return value;
71
- const { _fileName, _source, ...rest } = value;
72
- return rest;
73
- }
74
-
75
- /** A component's derived default, local tree first, then the installed
76
- * package's shipped copy — same fallback order every other read here uses. */
77
- function defaultComponentConfig(componentConfigsDir, comp) {
78
- return readData(join(componentConfigsDir, comp), `component-configs/${comp}`, 'default');
79
- }
80
-
81
- /**
82
- * The look the new theme carries forward. Without --carry-from that is the LIVE
83
- * state — buffer first, then the open theme, then the shipped default — because
84
- * what the user sees is what they expect to keep. With it, the named theme,
85
- * then the shipped default for anything that theme omits, so an incomplete
86
- * source never produces an incomplete result.
87
- */
88
- function resolveCarrySource({ carryFrom, colorsAndTypeDir, componentConfigsDir, themesDir }) {
89
- const comps = componentNames(componentConfigsDir);
90
-
91
- if (carryFrom) {
92
- const theme = readData(themesDir, 'themes', carryFrom);
93
- if (!theme) throw new Error(`--carry-from theme "${carryFrom}" not found`);
94
- const componentConfigs = {};
95
- for (const comp of comps) {
96
- const cfg = theme.componentConfigs?.[comp] ?? defaultComponentConfig(componentConfigsDir, comp);
97
- if (cfg) componentConfigs[comp] = stripMarkers(cfg);
98
- }
99
- return { colorsAndType: stripMarkers(theme.colorsAndType), componentConfigs };
100
- }
101
-
102
- const activeSlug = readJsonIfExists(join(themesDir, '_active.json'))?.activeFile ?? 'default';
103
- const activeTheme = readData(themesDir, 'themes', activeSlug);
104
- const colorsAndType = stripMarkers(
105
- readJsonIfExists(join(colorsAndTypeDir, '_working.json')) ??
106
- activeTheme?.colorsAndType ??
107
- readData(colorsAndTypeDir, 'colors-and-type', 'default'),
108
- );
109
-
110
- // A working buffer is an unsaved delta from the active theme; absent theme
111
- // component entries fall through to the component defaults. Mirrors
112
- // `bin/adjust.mjs`'s `readLiveConfigs` and the dev server's
113
- // `resolveLiveComponentConfig` — all three layers, in the same order.
114
- const componentConfigs = {};
115
- for (const comp of comps) {
116
- const live =
117
- readJsonIfExists(join(componentConfigsDir, comp, '_working.json')) ??
118
- activeTheme?.componentConfigs?.[comp] ??
119
- defaultComponentConfig(componentConfigsDir, comp);
120
- if (live) componentConfigs[comp] = stripMarkers(live);
121
- }
122
- return { colorsAndType, componentConfigs };
123
- }
124
-
125
- /** `engine` is a test seam; the CLI always runs the compiled bundle. */
126
- export async function runGenerateTheme({
127
- briefPath,
128
- activate = true,
129
- dryRun = false,
130
- carryFrom,
131
- root = process.cwd(),
132
- colorsAndTypeDir,
133
- componentConfigsDir,
134
- themesDir,
135
- engine,
136
- } = {}) {
137
- const { buildColorsAndTypeFromSeeds, resolveDataDirs, CURRENT_COMPONENT_SCHEMA_VERSION } =
138
- engine ?? (await loadEngine());
139
-
140
- const briefFull = resolve(root, briefPath);
141
- if (!existsSync(briefFull)) {
142
- throw new Error(`brief not found at ${relative(root, briefFull)}`);
143
- }
144
- let brief;
145
- try {
146
- brief = readJson(briefFull);
147
- } catch (err) {
148
- throw new Error(`brief is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
149
- }
150
-
151
- const dirs =
152
- colorsAndTypeDir && componentConfigsDir && themesDir
153
- ? { colorsAndTypeDir, componentConfigsDir, themesDir }
154
- : resolveDataDirs();
155
-
156
- const previousActive = readJsonIfExists(join(dirs.themesDir, '_active.json'))?.activeFile ?? 'default';
157
- const carry = resolveCarrySource({ carryFrom, ...dirs });
158
-
159
- const { colorsAndType, slug, report } = buildColorsAndTypeFromSeeds(
160
- brief,
161
- {
162
- cssVariables: carry.colorsAndType?.cssVariables,
163
- fontSources: carry.colorsAndType?.fontSources,
164
- fontStacks: carry.colorsAndType?.fontStacks,
165
- gradients: carry.colorsAndType?.gradients,
166
- },
167
- new Date().toISOString(),
168
- );
169
-
170
- const themePath = join(dirs.themesDir, `${slug}.json`);
171
- const existing = readJsonIfExists(themePath);
172
- const theme = {
173
- name: colorsAndType.name,
174
- createdAt: existing?.createdAt ?? colorsAndType.createdAt,
175
- updatedAt: colorsAndType.updatedAt,
176
- schemaVersion: THEME_SCHEMA_VERSION,
177
- colorsAndType,
178
- componentConfigs: carry.componentConfigs,
179
- componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION,
180
- };
181
-
182
- if (!dryRun) {
183
- mkdirSync(dirs.themesDir, { recursive: true });
184
- writeFileSync(themePath, JSON.stringify(theme, null, 2) + '\n');
185
- if (activate) applyTheme(slug, dirs);
186
- }
187
-
188
- return {
189
- name: theme.name,
190
- slug,
191
- themePath,
192
- existed: existing !== null,
193
- activated: activate && !dryRun,
194
- previousActive,
195
- carriedFrom: carryFrom ?? null,
196
- componentsCarried: Object.keys(carry.componentConfigs).length,
197
- dryRun,
198
- report,
199
- };
200
- }
201
-
202
- /** The apply door's write set, reproduced: clear every working delta and point
203
- * `themes/_active.json` at the open document. Production is untouched. */
204
- function applyTheme(slug, dirs) {
205
- const colorsWorking = join(dirs.colorsAndTypeDir, '_working.json');
206
- if (existsSync(colorsWorking)) rmSync(colorsWorking);
207
-
208
- for (const comp of componentNames(dirs.componentConfigsDir)) {
209
- const workingPath = join(dirs.componentConfigsDir, comp, '_working.json');
210
- if (existsSync(workingPath)) rmSync(workingPath);
211
- }
212
-
213
- writeFileSync(join(dirs.themesDir, '_active.json'), JSON.stringify({ activeFile: slug }));
214
- }
215
-
216
- export function formatGenerateThemeResult(result) {
217
- const root = process.cwd();
218
- const lines = [];
219
- const wrote = result.dryRun ? 'Would write' : result.existed ? 'Updated' : 'Created';
220
- lines.push(`${wrote} theme "${result.name}" → ${relative(root, result.themePath)}`);
221
- lines.push(
222
- `Carried forward from ${result.carriedFrom ? `theme "${result.carriedFrom}"` : 'the live state'}: ` +
223
- `fonts, gradients and ${result.componentsCarried} component config(s).`,
224
- );
225
-
226
- lines.push(`\nContrast report (${result.report.scheme} scheme):`);
227
- const width = Math.max(...result.report.checks.map((c) => c.textVar.length));
228
- for (const c of result.report.checks) {
229
- const mark = c.pass ? '✓' : '✗';
230
- const fixed = c.corrected ? ' (auto-corrected)' : '';
231
- lines.push(
232
- ` ${mark} ${c.textVar.padEnd(width)} ${c.ratio.toFixed(2).padStart(6)}:1 vs ${c.against} floor ${c.floor}:1${fixed}`,
233
- );
234
- }
235
- if (result.report.failures.length > 0) {
236
- lines.push(`\nUnmet floors — adjust the brief's seeds and re-run:`);
237
- for (const f of result.report.failures) lines.push(` ! ${f}`);
238
- }
239
-
240
- lines.push(
241
- result.report.gradients === 'carried'
242
- ? '\nGradients: kept your tuned swatch gradients.'
243
- : '\nGradients: swatch tokens rebuilt from the theme families.',
244
- );
245
- lines.push(`Shadows: ${result.report.shadows}; carried geometry kept.`);
246
- if (result.report.canvasGradient) {
247
- lines.push(`Canvas sky: ${result.report.canvasGradient}.`);
248
- }
249
-
250
- if (result.activated) {
251
- lines.push(
252
- `\nOpened "${result.slug}" (previously open: "${result.previousActive}"). ` +
253
- `Reload the app to see it; switch back any time from Load in the editor's Theme panel. ` +
254
- `Adopt it there to publish it to tokens.generated.css.`,
255
- );
256
- } else if (!result.dryRun) {
257
- lines.push(`\nNot opened (--no-activate). Load "${result.slug}" from the editor's Theme panel to see it.`);
258
- }
259
- return lines.join('\n');
260
- }
File without changes