@motion-proto/live-tokens 0.55.0 → 0.56.1

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 (38) hide show
  1. package/.claude/skills/{live-tokens-adjust-shape-space → live-tokens-adjust-geometry}/SKILL.md +5 -5
  2. package/.claude/skills/live-tokens-build-page/SKILL.md +2 -0
  3. package/.claude/skills/live-tokens-create-component/SKILL.md +20 -357
  4. package/.claude/skills/live-tokens-create-component/references/fixed-overlays.md +3 -0
  5. package/.claude/skills/live-tokens-create-component/references/intrinsics.md +58 -0
  6. package/.claude/skills/live-tokens-create-component/references/linked-siblings.md +70 -0
  7. package/.claude/skills/live-tokens-generate-theme/SKILL.md +79 -98
  8. package/.claude/skills/live-tokens-generate-theme/references/named-themes.md +18 -0
  9. package/.claude/skills/live-tokens-pair-fonts/SKILL.md +88 -0
  10. package/.claude/skills/live-tokens-pick-component/SKILL.md +4 -1
  11. package/CHANGELOG.md +108 -0
  12. package/README.md +20 -7
  13. package/bin/cli.mjs +41 -3
  14. package/bin/set-fonts.mjs +280 -0
  15. package/dist-plugin/adjust/index.d.cts +2 -2
  16. package/dist-plugin/adjust/index.d.ts +2 -2
  17. package/dist-plugin/fontPairing/index.cjs +411 -0
  18. package/dist-plugin/fontPairing/index.d.cts +109 -0
  19. package/dist-plugin/fontPairing/index.d.ts +109 -0
  20. package/dist-plugin/fontPairing/index.js +320 -0
  21. package/dist-plugin/generateColorsAndType/index.d.cts +2 -2
  22. package/dist-plugin/generateColorsAndType/index.d.ts +2 -2
  23. package/dist-plugin/index-4N-Orzzi.d.cts +3 -0
  24. package/dist-plugin/index-4N-Orzzi.d.ts +3 -0
  25. package/dist-plugin/{index-DpTIRZ2H.d.cts → themeTypes-BAqtv4XO.d.cts} +1 -3
  26. package/dist-plugin/{index-DpTIRZ2H.d.ts → themeTypes-BAqtv4XO.d.ts} +1 -3
  27. package/package.json +3 -2
  28. package/src/editor/core/fonts/applyFontPairing.ts +157 -0
  29. package/src/editor/core/fonts/fontPairing.ts +13 -6
  30. package/src/editor/core/fonts/googleFontsUrl.ts +123 -0
  31. package/src/editor/core/fonts/weightCoverage.ts +92 -0
  32. package/src/editor/docs/content/themes-workflow.md +39 -0
  33. package/src/editor/docs/content.generated.ts +1 -1
  34. package/src/editor/ui/ColorEditPanel.svelte +16 -1
  35. package/src/editor/ui/PaletteEditor.svelte +124 -31
  36. package/src/editor/ui/ProjectFontsSection.svelte +24 -36
  37. package/src/editor/ui/palette/OverridesPanel.svelte +5 -2
  38. package/src/editor/ui/palette/PaletteBase.svelte +13 -0
@@ -8,6 +8,7 @@
8
8
  parseFontFaceText,
9
9
  type ParsedFamily,
10
10
  } from '../core/fonts/fontParse';
11
+ import { resolveGoogleFont, type ResolvedGoogleFont } from '../core/fonts/googleFontsUrl';
11
12
  import UIPillButton from './UIPillButton.svelte';
12
13
  import UISegmentedControl from './UISegmentedControl.svelte';
13
14
  import UIInfoPopover from './UIInfoPopover.svelte';
@@ -19,7 +20,7 @@
19
20
  let nameInput = $state('');
20
21
  let nameError = $state('');
21
22
  let nameDiscovering = $state(false);
22
- let nameParsed: ParsedFamily[] | null = $state(null);
23
+ let nameResolved: ResolvedGoogleFont | null = $state(null);
23
24
 
24
25
  // Unified paste — accepts a bare URL, `<link>` tag, `@import url(...)`,
25
26
  // or one or more `@font-face { ... }` rules. We sniff which on Detect.
@@ -37,7 +38,7 @@
37
38
  nameInput = '';
38
39
  nameError = '';
39
40
  nameDiscovering = false;
40
- nameParsed = null;
41
+ nameResolved = null;
41
42
  pasteInput = '';
42
43
  pasteError = '';
43
44
  pasteDiscovering = false;
@@ -110,46 +111,34 @@
110
111
  pasteDiscovering = false;
111
112
  }
112
113
 
113
- /** Build a Google Fonts CSS2 URL for a family name, requesting a wide
114
- * weight range with italics. Works for variable fonts and most static
115
- * multi-weight families. Single-weight static fonts (e.g. GFS Didot) will
116
- * reject the range axis with 400 Bad Request — Chrome then CORBs the
117
- * response. Such fonts must be persisted as `?family=Name&display=swap`. */
118
- function googleUrlForName(name: string): string {
119
- const family = name.trim().replace(/\s+/g, '+');
120
- return `https://fonts.googleapis.com/css2?family=${family}:ital,wght@0,100..900;1,100..900&display=swap`;
121
- }
122
-
123
114
  async function discoverByName() {
124
115
  nameError = '';
125
- nameParsed = null;
116
+ nameResolved = null;
126
117
  if (!nameInput.trim()) {
127
118
  nameError = 'Enter a family name';
128
119
  return;
129
120
  }
130
121
  nameDiscovering = true;
131
122
  try {
132
- const found = await discoverFamiliesFromUrl(googleUrlForName(nameInput));
133
- if (found && found.length > 0) {
134
- nameParsed = found;
135
- } else {
136
- nameError = `Couldn't find "${nameInput.trim()}" on Google Fonts`;
137
- }
138
- } catch {
139
- nameError = `Couldn't reach Google Fonts for "${nameInput.trim()}"`;
123
+ nameResolved = await resolveGoogleFont(nameInput, (url) => fetch(url));
124
+ } catch (e) {
125
+ nameError = e instanceof Error ? e.message : `Couldn't reach Google Fonts for "${nameInput.trim()}"`;
140
126
  }
141
127
  nameDiscovering = false;
142
128
  }
143
129
 
144
130
  function addNameSource() {
145
- if (!nameParsed || nameParsed.length === 0) return;
146
- if (nameDuplicate) return;
131
+ if (!nameResolved || nameDuplicate) return;
147
132
  // $state.snapshot() unwraps the reactive proxy. Without it the FontSource
148
133
  // we hand to the store carries proxy arrays (weights, etc.) and the next
149
134
  // `mutate()` call fails with DataCloneError inside structuredClone.
150
- const families = $state.snapshot(nameParsed) as ParsedFamily[];
151
- const source = buildSourceFromUrl(googleUrlForName(nameInput), families);
152
- commitSources([...fontSourcesList, source]);
135
+ const resolved = $state.snapshot(nameResolved) as ResolvedGoogleFont;
136
+ const family: ParsedFamily = {
137
+ name: resolved.name,
138
+ ...(resolved.weights.length > 0 ? { weights: resolved.weights } : {}),
139
+ ...(resolved.italics ? { italics: true } : {}),
140
+ };
141
+ commitSources([...fontSourcesList, buildSourceFromUrl(resolved.url, [family])]);
153
142
  reset();
154
143
  }
155
144
 
@@ -166,10 +155,9 @@
166
155
  return null;
167
156
  }
168
157
 
169
- let nameDuplicate = $derived.by(() => {
170
- if (!nameParsed || nameParsed.length === 0) return null;
171
- return findExistingFamilyByName(nameParsed[0].name);
172
- });
158
+ let nameDuplicate = $derived.by(() =>
159
+ nameResolved ? findExistingFamilyByName(nameResolved.name) : null,
160
+ );
173
161
 
174
162
  function addUrlSource() {
175
163
  const url = extractFontsUrl(pasteInput);
@@ -339,9 +327,9 @@
339
327
  class="ui-form-input pf-name-input"
340
328
  placeholder="e.g. Inter, Fraunces, Space Mono"
341
329
  bind:value={nameInput}
342
- onkeydown={(e) => { if (e.key === 'Enter' && !nameParsed) discoverByName(); }}
330
+ onkeydown={(e) => { if (e.key === 'Enter' && !nameResolved) discoverByName(); }}
343
331
  />
344
- {#if nameParsed}
332
+ {#if nameResolved}
345
333
  <UIPillButton variant="primary" onclick={addNameSource} disabled={!!nameDuplicate}>Add</UIPillButton>
346
334
  {:else}
347
335
  <UIPillButton variant="secondary" onclick={discoverByName} disabled={!nameInput.trim() || nameDiscovering}>
@@ -350,16 +338,16 @@
350
338
  {/if}
351
339
  </div>
352
340
  {#if nameError}<div class="pf-error">{nameError}</div>{/if}
353
- {#if nameParsed}
341
+ {#if nameResolved}
354
342
  {#if nameDuplicate}
355
343
  <div class="pf-notice">
356
344
  <strong>{nameDuplicate}</strong> is already in your project fonts.
357
345
  </div>
358
346
  {:else}
359
347
  <div class="pf-detected">
360
- Found <strong>{nameParsed[0].name}</strong>
361
- {#if nameParsed[0].weights && nameParsed[0].weights.length > 0}
362
- <span class="pf-check-meta">({nameParsed[0].weights.length} weights)</span>
348
+ Found <strong>{nameResolved.name}</strong>
349
+ {#if nameResolved.weights.length > 0}
350
+ <span class="pf-check-meta">({nameResolved.weights.length} weights)</span>
363
351
  {/if}
364
352
  </div>
365
353
  {/if}
@@ -424,9 +424,12 @@
424
424
  border-color: var(--ui-border-high);
425
425
  }
426
426
 
427
+ /* Same weight-only selection language as the palette ramp, at the smaller
428
+ swatch's scale. */
427
429
  .override-slot.active {
428
- border-color: var(--ui-border-higher);
429
- outline: 1px solid var(--ui-border-high);
430
+ border-style: solid;
431
+ border-color: var(--ui-surface-lowest);
432
+ outline: 2px solid var(--ui-text-primary);
430
433
  outline-offset: 1px;
431
434
  }
432
435
 
@@ -14,6 +14,9 @@
14
14
  isEditingBase: boolean;
15
15
  /** Keep the swatch reading as active while the curve editors are visible. */
16
16
  pinnedOpen?: boolean;
17
+ /** The same ring the ramp's anchored step wears, so the two swatches read
18
+ * as the one value they are. */
19
+ selected?: boolean;
17
20
  copiedKey: string | null;
18
21
  onStartEdit: () => void;
19
22
  onCopyBaseHex: (key: string, hex: string, event?: MouseEvent) => void;
@@ -29,6 +32,7 @@
29
32
  baseColor,
30
33
  isEditingBase,
31
34
  pinnedOpen = false,
35
+ selected = false,
32
36
  copiedKey,
33
37
  onStartEdit,
34
38
  onCopyBaseHex,
@@ -49,6 +53,7 @@
49
53
  <div
50
54
  class="header-swatch"
51
55
  class:active={isEditingBase || pinnedOpen}
56
+ class:selected
52
57
  style="background: {baseHex}"
53
58
  onclick={onStartEdit}
54
59
  role="button"
@@ -150,6 +155,14 @@
150
155
  outline-offset: 1px;
151
156
  }
152
157
 
158
+ .header-swatch.selected {
159
+ border-color: var(--ui-surface-lowest);
160
+ outline: none;
161
+ box-shadow:
162
+ inset 0 0 0 3px var(--ui-text-primary),
163
+ inset 0 0 0 4px var(--ui-surface-lowest);
164
+ }
165
+
153
166
  .editor-label {
154
167
  font-size: var(--ui-font-size-xl);
155
168
  font-weight: var(--ui-font-weight-semibold);