@motion-proto/live-tokens 0.67.1 → 0.69.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 (138) hide show
  1. package/.claude/skills/live-tokens-build-page/SKILL.md +16 -5
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +43 -8
  3. package/.claude/skills/live-tokens-create-component/references/token-naming.md +30 -1
  4. package/.claude/skills/live-tokens-fix-findings/SKILL.md +131 -0
  5. package/.claude/skills/live-tokens-pick-component/SKILL.md +2 -1
  6. package/CHANGELOG.md +239 -0
  7. package/README.md +11 -4
  8. package/bin/check-component.mjs +367 -63
  9. package/bin/check-page.mjs +409 -0
  10. package/bin/cli.mjs +57 -8
  11. package/bin/generate-theme.mjs +5 -4
  12. package/bin/lib/cssValues.mjs +50 -0
  13. package/bin/lib/findings.mjs +106 -0
  14. package/bin/lib/tokenVocabulary.mjs +213 -0
  15. package/dist-plugin/adjust/index.cjs +174 -23
  16. package/dist-plugin/adjust/index.d.cts +1 -1
  17. package/dist-plugin/adjust/index.d.ts +1 -1
  18. package/dist-plugin/adjust/index.js +68 -23
  19. package/dist-plugin/{chunk-J2JT4UEA.js → chunk-2YNERPXY.js} +26 -26
  20. package/dist-plugin/{chunk-NE6N66EE.js → chunk-GPIBU44G.js} +107 -1
  21. package/dist-plugin/{chunk-ZHPX7ZYQ.js → chunk-RFVYPNRO.js} +39 -1
  22. package/dist-plugin/{dataPaths-DZUzVv8H.d.ts → dataPaths-bJTCEO4H.d.cts} +1 -1
  23. package/dist-plugin/{dataPaths-DZUzVv8H.d.cts → dataPaths-bJTCEO4H.d.ts} +1 -1
  24. package/dist-plugin/fontPairing/index.d.cts +1 -1
  25. package/dist-plugin/fontPairing/index.d.ts +1 -1
  26. package/dist-plugin/generateColorsAndType/index.cjs +107 -1
  27. package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
  28. package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
  29. package/dist-plugin/generateColorsAndType/index.js +1 -1
  30. package/dist-plugin/index.cjs +173 -29
  31. package/dist-plugin/index.d.cts +1 -1
  32. package/dist-plugin/index.d.ts +1 -1
  33. package/dist-plugin/index.js +7 -7
  34. package/dist-plugin/migrateData/index.cjs +132 -26
  35. package/dist-plugin/migrateData/index.d.cts +1 -1
  36. package/dist-plugin/migrateData/index.d.ts +1 -1
  37. package/dist-plugin/migrateData/index.js +2 -2
  38. package/dist-plugin/tokensCssMigrations/index.cjs +39 -1
  39. package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
  40. package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
  41. package/dist-plugin/tokensCssMigrations/index.js +1 -1
  42. package/package.json +6 -5
  43. package/src/app/site.css +4 -4
  44. package/src/editor/bootstrap.ts +6 -6
  45. package/src/editor/component-editor/ButtonEditor.svelte +71 -4
  46. package/src/editor/component-editor/CardEditor.svelte +6 -6
  47. package/src/editor/component-editor/DialogEditor.svelte +3 -3
  48. package/src/editor/component-editor/IconButtonEditor.svelte +68 -3
  49. package/src/editor/component-editor/ImageEditor.svelte +8 -8
  50. package/src/editor/component-editor/ImageLightboxEditor.svelte +13 -2
  51. package/src/editor/component-editor/MenuSelectEditor.svelte +63 -3
  52. package/src/editor/component-editor/SegmentedControlEditor.svelte +63 -3
  53. package/src/editor/component-editor/SideNavigationEditor.svelte +67 -3
  54. package/src/editor/component-editor/SliderEditor.svelte +186 -0
  55. package/src/editor/component-editor/TabBarEditor.svelte +63 -3
  56. package/src/editor/component-editor/registry.ts +10 -0
  57. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +4 -4
  58. package/src/editor/core/components/aliasKinds.ts +51 -28
  59. package/src/editor/core/fonts/fontLoader.ts +2 -2
  60. package/src/editor/core/preview/{lookPreview.ts → themePreview.ts} +27 -27
  61. package/src/editor/core/productionPulse.ts +3 -3
  62. package/src/editor/core/sketch/index.ts +52 -44
  63. package/src/editor/core/sketch/maskField.ts +9 -9
  64. package/src/editor/core/sketch/sketchLayer.ts +25 -9
  65. package/src/editor/core/sketch/sketchRegistry.ts +34 -34
  66. package/src/editor/core/sketch/sketchStore.ts +98 -83
  67. package/src/editor/core/sketch/sketchStyleService.ts +4 -4
  68. package/src/editor/core/sketch/sketchStyles.ts +28 -28
  69. package/src/editor/core/store/editorPersistence.ts +44 -1
  70. package/src/editor/core/store/editorRenderer.ts +2 -2
  71. package/src/editor/core/store/editorStore.ts +18 -18
  72. package/src/editor/core/store/editorTypes.ts +7 -6
  73. package/src/editor/core/themes/colorsAndTypeService.ts +1 -1
  74. package/src/editor/core/themes/loadRows.ts +8 -8
  75. package/src/editor/core/themes/migrations/2026-09-01-gate-suffix-enabled.ts +31 -0
  76. package/src/editor/core/themes/migrations/2026-09-01-scrim-rename.ts +52 -0
  77. package/src/editor/core/themes/migrations/2026-09-01-tabbar-active-tint.ts +27 -0
  78. package/src/editor/core/themes/migrations/2026-09-01-tint-rename.ts +42 -0
  79. package/src/editor/core/themes/migrations/index.ts +16 -0
  80. package/src/editor/core/themes/slices/domainVars.ts +2 -2
  81. package/src/editor/core/themes/slices/washes.ts +107 -0
  82. package/src/editor/core/themes/themeDocumentSync.ts +2 -2
  83. package/src/editor/core/themes/themeInit.ts +2 -2
  84. package/src/editor/core/themes/themeService.ts +17 -17
  85. package/src/editor/core/themes/{lookSummary.ts → themeSummary.ts} +9 -9
  86. package/src/editor/core/themes/themeTypes.ts +10 -6
  87. package/src/editor/docs/content/editing-tokens.md +5 -3
  88. package/src/editor/docs/content/getting-started.md +1 -1
  89. package/src/editor/docs/content/sketch-mode.md +22 -17
  90. package/src/editor/docs/content/themes-workflow.md +11 -11
  91. package/src/editor/docs/content/where-themes-live.md +6 -6
  92. package/src/editor/docs/content.generated.ts +5 -5
  93. package/src/editor/index.ts +2 -2
  94. package/src/editor/pages/EditorShell.svelte +1 -1
  95. package/src/editor/ui/SurfacesTab.svelte +3 -3
  96. package/src/editor/ui/ThemePanel.svelte +52 -52
  97. package/src/editor/ui/UITokenSelector.svelte +1 -0
  98. package/src/editor/ui/VariablesTab.svelte +2 -2
  99. package/src/editor/ui/sections/{OverlaysSection.svelte → WashesSection.svelte} +44 -43
  100. package/src/editor/ui/sketch/SketchPreview.svelte +2 -2
  101. package/src/editor/ui/sketch/SketchTab.svelte +64 -38
  102. package/src/live-tokens/data/colors-and-type/autumn.json +6 -6
  103. package/src/live-tokens/data/colors-and-type/default.json +6 -6
  104. package/src/live-tokens/data/colors-and-type/halloween.json +6 -6
  105. package/src/live-tokens/data/colors-and-type/midnight-study.json +6 -6
  106. package/src/live-tokens/data/colors-and-type/ocean.json +6 -6
  107. package/src/live-tokens/data/colors-and-type/royal-velvet.json +6 -6
  108. package/src/live-tokens/data/colors-and-type/sketchy.json +6 -6
  109. package/src/live-tokens/data/colors-and-type/spring-meadow.json +6 -6
  110. package/src/live-tokens/data/colors-and-type/sunset.json +6 -6
  111. package/src/live-tokens/data/sketch-styles/dry.json +5 -5
  112. package/src/live-tokens/data/sketch-styles/hatched.json +9 -9
  113. package/src/live-tokens/data/themes/autumn.json +82 -14
  114. package/src/live-tokens/data/themes/halloween.json +82 -14
  115. package/src/live-tokens/data/themes/midnight-study.json +82 -14
  116. package/src/live-tokens/data/themes/ocean.json +82 -14
  117. package/src/live-tokens/data/themes/royal-velvet.json +82 -14
  118. package/src/live-tokens/data/themes/sketchy.json +82 -14
  119. package/src/live-tokens/data/themes/spring-meadow.json +82 -14
  120. package/src/live-tokens/data/themes/sunset.json +82 -14
  121. package/src/live-tokens/data/tokens.generated.css +6 -6
  122. package/src/system/backdrop/backdrop.ts +1 -1
  123. package/src/system/components/Button.svelte +28 -10
  124. package/src/system/components/Card.svelte +6 -6
  125. package/src/system/components/Dialog.svelte +3 -3
  126. package/src/system/components/IconButton.svelte +23 -7
  127. package/src/system/components/Image.svelte +6 -6
  128. package/src/system/components/MenuSelect.svelte +17 -1
  129. package/src/system/components/SectionDivider.svelte +1 -1
  130. package/src/system/components/SegmentedControl.svelte +20 -2
  131. package/src/system/components/SideNavigation.svelte +22 -2
  132. package/src/system/components/Slider.svelte +348 -0
  133. package/src/system/components/TabBar.svelte +20 -2
  134. package/src/system/styles/CONVENTIONS.md +2 -2
  135. package/src/system/styles/tokens.css +12 -4
  136. package/template/package.json +3 -2
  137. package/template/src/pages/Home.svelte +1 -1
  138. package/src/editor/core/themes/slices/overlays.ts +0 -101
@@ -1,18 +1,19 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * Overlay + hover stops. Each stop binds an existing color token through
4
- * `UIPaletteSelector`; the picker handles family/step selection and
5
- * opacity, and writes route through `onwrite` into the overlays slice
6
- * (see editorStore.makeDefaultOverlaysState). The slice fans the
7
- * resulting color-mix expressions out to :root.
3
+ * Scrim + tint stops. A scrim dims what sits behind it; a tint shades the
4
+ * surface it sits on. Each stop binds an existing color token through
5
+ * `UIPaletteSelector`; the picker handles family/step selection and opacity,
6
+ * and writes route through `onwrite` into the washes slice
7
+ * (see editorStore.makeDefaultWashesState). The slice fans the resulting
8
+ * color-mix expressions out to :root.
8
9
  */
9
10
  import { editorState, mutate } from '../../core/store/editorStore';
10
- import type { OverlayToken } from '../../core/store/editorTypes';
11
+ import type { WashToken } from '../../core/store/editorTypes';
11
12
  import {
12
- makeDefaultOverlayTokens,
13
- makeDefaultHoverTokens,
14
- parseOverlayCss,
15
- } from '../../core/themes/slices/overlays';
13
+ makeDefaultScrimTokens,
14
+ makeDefaultTintTokens,
15
+ parseWashCss,
16
+ } from '../../core/themes/slices/washes';
16
17
  import UIPaletteSelector from '../UIPaletteSelector.svelte';
17
18
 
18
19
  interface Props {
@@ -22,7 +23,7 @@
22
23
 
23
24
  let { copiedVar = null, oncopy }: Props = $props();
24
25
 
25
- type Channel = 'overlay' | 'hover';
26
+ type Channel = 'scrim' | 'tint';
26
27
 
27
28
  function copy(v: string) { oncopy?.(v); }
28
29
 
@@ -31,41 +32,41 @@
31
32
  * - bare `--name` → 100% opacity on that alias.
32
33
  * - `color-mix(in srgb, var(--name) N%, transparent)` → alias + N%.
33
34
  * - other (incl. `transparent`) → ignored; the picker's "None" option
34
- * doesn't apply to overlays. */
35
+ * doesn't apply to a wash. */
35
36
  function handleWrite(channel: Channel, idx: number, value: string | null) {
36
37
  mutate(`${channel} ${idx} edit`, (s) => {
37
- const arr = channel === 'overlay' ? s.overlays.tokens : s.overlays.hoverTokens;
38
+ const arr = channel === 'scrim' ? s.washes.scrims : s.washes.tints;
38
39
  const t = arr[idx];
39
40
  if (!t) return;
40
41
  if (value === null) {
41
- const defaults = channel === 'overlay' ? makeDefaultOverlayTokens() : makeDefaultHoverTokens();
42
+ const defaults = channel === 'scrim' ? makeDefaultScrimTokens() : makeDefaultTintTokens();
42
43
  const d = defaults[idx];
43
44
  if (d) { t.alias = d.alias; t.opacity = d.opacity; }
44
45
  return;
45
46
  }
46
47
  if (value.startsWith('--')) { t.alias = value; t.opacity = 1; return; }
47
- const parsed = parseOverlayCss(value);
48
+ const parsed = parseWashCss(value);
48
49
  if (parsed) { t.alias = parsed.alias; t.opacity = parsed.opacity; }
49
50
  });
50
51
  }
51
52
 
52
- function copyCss(t: OverlayToken): string {
53
+ function copyCss(t: WashToken): string {
53
54
  const pct = Math.round(t.opacity * 100);
54
55
  return pct >= 100 ? `var(${t.alias})` : `color-mix(in srgb, var(${t.alias}) ${pct}%, transparent)`;
55
56
  }
56
57
  </script>
57
58
 
58
- <section class="section" id="overlays">
59
- <h2 class="section-title">Overlays</h2>
59
+ <section class="section" id="washes">
60
+ <h2 class="section-title">Washes</h2>
60
61
 
61
- <h3 class="group-title">Dark Overlays</h3>
62
- <div class="overlays-grid">
63
- {#each $editorState.overlays.tokens as token, i (token.variable)}
64
- <div class="overlay-row">
65
- <div class="overlay-swatch-wrap">
66
- <div class="overlay-swatch" style="background: var({token.variable});"></div>
62
+ <h3 class="group-title">Scrims</h3>
63
+ <div class="washes-grid">
64
+ {#each $editorState.washes.scrims as token, i (token.variable)}
65
+ <div class="wash-row">
66
+ <div class="wash-swatch-wrap">
67
+ <div class="wash-swatch" style="background: var({token.variable});"></div>
67
68
  </div>
68
- <div class="overlay-meta">
69
+ <div class="wash-meta">
69
70
  <button
70
71
  class="token-variable copyable"
71
72
  class:copied={copiedVar === token.variable}
@@ -73,11 +74,11 @@
73
74
  >{copiedVar === token.variable ? 'copied!' : token.variable}</button>
74
75
  <span class="token-value">{token.label} — {Math.round(token.opacity * 100)}%</span>
75
76
  </div>
76
- <div class="overlay-picker">
77
+ <div class="wash-picker">
77
78
  <UIPaletteSelector
78
79
  variable={token.variable}
79
80
  showNone={false}
80
- onwrite={(v) => handleWrite('overlay', i, v)}
81
+ onwrite={(v) => handleWrite('scrim', i, v)}
81
82
  />
82
83
  </div>
83
84
  <button
@@ -91,14 +92,14 @@
91
92
  {/each}
92
93
  </div>
93
94
 
94
- <h3 class="group-title">Hover Overlays</h3>
95
- <div class="overlays-grid">
96
- {#each $editorState.overlays.hoverTokens as token, i (token.variable)}
97
- <div class="overlay-row">
98
- <div class="overlay-swatch-wrap overlay-swatch-wrap--dark">
99
- <div class="overlay-swatch" style="background: var({token.variable});"></div>
95
+ <h3 class="group-title">Tints</h3>
96
+ <div class="washes-grid">
97
+ {#each $editorState.washes.tints as token, i (token.variable)}
98
+ <div class="wash-row">
99
+ <div class="wash-swatch-wrap wash-swatch-wrap--dark">
100
+ <div class="wash-swatch" style="background: var({token.variable});"></div>
100
101
  </div>
101
- <div class="overlay-meta">
102
+ <div class="wash-meta">
102
103
  <button
103
104
  class="token-variable copyable"
104
105
  class:copied={copiedVar === token.variable}
@@ -106,11 +107,11 @@
106
107
  >{copiedVar === token.variable ? 'copied!' : token.variable}</button>
107
108
  <span class="token-value">{token.label} — {Math.round(token.opacity * 100)}%</span>
108
109
  </div>
109
- <div class="overlay-picker">
110
+ <div class="wash-picker">
110
111
  <UIPaletteSelector
111
112
  variable={token.variable}
112
113
  showNone={false}
113
- onwrite={(v) => handleWrite('hover', i, v)}
114
+ onwrite={(v) => handleWrite('tint', i, v)}
114
115
  />
115
116
  </div>
116
117
  <button
@@ -148,13 +149,13 @@
148
149
  margin: 0;
149
150
  }
150
151
 
151
- .overlays-grid {
152
+ .washes-grid {
152
153
  display: flex;
153
154
  flex-direction: column;
154
155
  gap: var(--ui-space-8);
155
156
  }
156
157
 
157
- .overlay-row {
158
+ .wash-row {
158
159
  display: grid;
159
160
  grid-template-columns: 3.5rem minmax(10rem, 1fr) minmax(14rem, 1.5fr) auto;
160
161
  gap: var(--ui-space-12);
@@ -165,7 +166,7 @@
165
166
  border-radius: var(--ui-radius-md);
166
167
  }
167
168
 
168
- .overlay-swatch-wrap {
169
+ .wash-swatch-wrap {
169
170
  width: 3.5rem;
170
171
  height: 3.5rem;
171
172
  border-radius: var(--ui-radius-sm);
@@ -182,7 +183,7 @@
182
183
  background-color: #fff;
183
184
  }
184
185
 
185
- .overlay-swatch-wrap--dark {
186
+ .wash-swatch-wrap--dark {
186
187
  background-color: #222;
187
188
  background-image:
188
189
  linear-gradient(45deg, #333 25%, transparent 25%),
@@ -191,12 +192,12 @@
191
192
  linear-gradient(-45deg, transparent 75%, #333 75%);
192
193
  }
193
194
 
194
- .overlay-swatch {
195
+ .wash-swatch {
195
196
  position: absolute;
196
197
  inset: 0;
197
198
  }
198
199
 
199
- .overlay-meta {
200
+ .wash-meta {
200
201
  display: flex;
201
202
  flex-direction: column;
202
203
  gap: var(--ui-space-2);
@@ -220,7 +221,7 @@
220
221
  color: var(--ui-text-muted);
221
222
  }
222
223
 
223
- .overlay-picker { min-width: 0; }
224
+ .wash-picker { min-width: 0; }
224
225
 
225
226
  .copy-css-btn {
226
227
  all: unset;
@@ -6,10 +6,10 @@
6
6
  import Callout from '../../../system/components/Callout.svelte';
7
7
  import Toggle from '../../../system/components/Toggle.svelte';
8
8
  import { setSketchScope } from '../../core/sketch/sketchLayer';
9
- import type { SketchStyle } from '../../core/sketch/sketchStyles';
9
+ import type { SketchStyleSettings } from '../../core/sketch/sketchStyles';
10
10
 
11
11
  interface Props {
12
- settings: SketchStyle;
12
+ settings: SketchStyleSettings;
13
13
  enabled: boolean;
14
14
  }
15
15
 
@@ -14,18 +14,19 @@
14
14
  import {
15
15
  sketchEnabled,
16
16
  setSketchEnabled,
17
- sketchOffLook,
18
- sketchStyleName,
17
+ sketchOffTheme,
18
+ selectedSketchStyleId,
19
19
  sketchDirty,
20
20
  sketchSettings,
21
- selectSketchStyle,
22
21
  updateSketchSettings,
23
22
  refreshSavedSketchStyles,
24
23
  saveCurrentSketchStyle,
25
24
  saveSelectedSketchStyle,
26
25
  deleteSavedSketchStyle,
27
26
  } from '../../core/sketch/sketchStore';
28
- import { sketchLooks } from '../../core/sketch/sketchRegistry';
27
+ import { sketchStyles } from '../../core/sketch/sketchRegistry';
28
+ import { setSketch, unsavedSketchStyle } from '../../core/sketch';
29
+ import { THEME_SKETCH_ID } from '../../core/sketch/sketchStyles';
29
30
 
30
31
  interface Props {
31
32
  /** The rail's last jump. A fresh object per click, not a bare id, so
@@ -38,8 +39,13 @@
38
39
 
39
40
  let s = $derived($sketchSettings);
40
41
 
42
+ /** The theme's own style sits in the grid like any other row, but it is the
43
+ one row with no file behind it, so Save has nothing to write over. Save As
44
+ is what gives it one, after which it is an ordinary sketchstyle. */
45
+ let onUnsavedSketchStyle = $derived($selectedSketchStyleId === THEME_SKETCH_ID);
46
+
41
47
  // Save is disabled for the protected Default theme (ThemePanel.svelte);
42
- // Save As is the gesture actually on offer there, so the off-look copy has
48
+ // Save As is the gesture actually on offer there, so the off-style copy has
43
49
  // to name it instead.
44
50
  let activeIsProtected = $derived($openThemeSlug === 'default');
45
51
 
@@ -50,7 +56,7 @@
50
56
 
51
57
  /** All closed to start: twenty-odd dials in one scroll is a wall, and the
52
58
  summary on each trigger row answers most questions without opening
53
- anything. Each section then stands alone, because shaping a look means
59
+ anything. Each section then stands alone, because shaping a style means
54
60
  reaching between them — the border's detail against the noise it
55
61
  multiplies, the fill's travel against the border's.
56
62
 
@@ -124,43 +130,49 @@
124
130
  });
125
131
 
126
132
  /** Shipped and saved sketchstyles are one choice, so they share one radio
127
- group: picking a saved one clears the shipped selection natively. */
133
+ group: picking a saved one clears the shipped selection natively.
134
+
135
+ Picks go through `setSketch`, the same door a page picker uses, so one is
136
+ the whole gesture: the style is applied, the effect comes on, and the
137
+ page's own picker follows. Selecting without applying would leave the two
138
+ pickers disagreeing about what the page is drawn with. */
128
139
  const STYLE_RADIO_GROUP = 'sketchstyle';
129
140
 
130
141
  /** What the readout under the grid says. The selection survives a dial move,
131
142
  so the base keeps its name here and the blurb reports the drift instead. */
132
143
  let active = $derived(
133
144
  $sketchDirty
134
- ? { name: s.label, blurb: `Modified from ${s.label}. Save it to keep it.` }
145
+ ? {
146
+ name: s.label,
147
+ blurb: onUnsavedSketchStyle
148
+ ? 'Modified from settings this theme carries. Save As keeps them as a sketchstyle.'
149
+ : `Modified from ${s.label}. Save it to keep it.`,
150
+ }
135
151
  : { name: s.label, blurb: s.blurb || 'Your saved sketchstyle.' },
136
152
  );
137
153
 
138
- let selectedLook = $derived($sketchLooks.find((l) => l.id === $sketchStyleName));
154
+ let selectedSketchStyle = $derived($sketchStyles.find((l) => l.id === $selectedSketchStyleId));
139
155
  /** Offered whenever the dials have moved off the sketchstyle they name. A
140
- look backed by a project file is written over. A shipped one's file lives
156
+ style backed by a project file is written over. A shipped one's file lives
141
157
  in the package, so saving it writes this project's own under the same
142
158
  name, which is how a project comes to own its Pencil: the id is the same,
143
159
  so the new file takes the shipped one's place in the grid rather than
144
160
  sitting beside it. Nothing is lost either way, since deleting that file
145
161
  uncovers the packaged one again. */
146
- let canSave = $derived(!!selectedLook && $sketchDirty);
162
+ let canSave = $derived(!!selectedSketchStyle && $sketchDirty);
147
163
  let saveTitle = $derived(
148
- !selectedLook
149
- ? 'Pick a sketchstyle to save over'
150
- : !$sketchDirty
151
- ? 'No changes to save'
152
- : selectedLook.source === 'file'
153
- ? `Save over ${selectedLook.label}`
154
- : `Keep these dials as this project's ${selectedLook.label}`,
164
+ onUnsavedSketchStyle
165
+ ? 'These settings belong to no sketchstyle file. Save As keeps them as one.'
166
+ : !selectedSketchStyle
167
+ ? 'Pick a sketchstyle to save over'
168
+ : !$sketchDirty
169
+ ? 'No changes to save'
170
+ : `Save over ${selectedSketchStyle.label}`,
155
171
  );
156
172
 
157
173
  function saveInPlace() {
158
- if (!selectedLook) return;
159
- runStyleAction(
160
- selectedLook.source === 'file'
161
- ? saveSelectedSketchStyle()
162
- : saveCurrentSketchStyle(selectedLook.label),
163
- );
174
+ if (!selectedSketchStyle) return;
175
+ runStyleAction(saveSelectedSketchStyle());
164
176
  }
165
177
 
166
178
  let naming = $state(false);
@@ -324,9 +336,9 @@
324
336
  <div class="readout" aria-live="polite">
325
337
  <span class="readout-name">{active.name}</span>
326
338
  <p class="readout-blurb">{active.blurb}</p>
327
- {#if $sketchOffLook}
339
+ {#if $sketchOffTheme}
328
340
  {#if $sketchEnabled}
329
- <p class="readout-off-look">
341
+ <p class="readout-off-style">
330
342
  {#if activeIsProtected}
331
343
  Motion Proto is read-only, and these dials are ahead of it. Use Save As in the
332
344
  Theme panel to keep them in a new theme.
@@ -336,7 +348,7 @@
336
348
  {/if}
337
349
  </p>
338
350
  {:else}
339
- <p class="readout-off-look">
351
+ <p class="readout-off-style">
340
352
  The theme carries a sketchstyle this page is not painting. Saving now would drop
341
353
  it.
342
354
  </p>
@@ -345,25 +357,39 @@
345
357
  </div>
346
358
 
347
359
  <div class="presets">
348
- {#each $sketchLooks as look (look.id)}
360
+ {#if $unsavedSketchStyle}
361
+ <div class="saved-item">
362
+ <label class="preset">
363
+ <input
364
+ type="radio"
365
+ name={STYLE_RADIO_GROUP}
366
+ value={$unsavedSketchStyle.id}
367
+ checked={onUnsavedSketchStyle}
368
+ onchange={() => setSketch(THEME_SKETCH_ID)}
369
+ />
370
+ <span class="preset-name">{$unsavedSketchStyle.label}</span>
371
+ </label>
372
+ </div>
373
+ {/if}
374
+ {#each $sketchStyles as style (style.id)}
349
375
  <div class="saved-item">
350
376
  <label class="preset">
351
377
  <input
352
378
  type="radio"
353
379
  name={STYLE_RADIO_GROUP}
354
- value={look.id}
355
- checked={$sketchStyleName === look.id}
356
- onchange={() => selectSketchStyle(look.id)}
380
+ value={style.id}
381
+ checked={$selectedSketchStyleId === style.id}
382
+ onchange={() => setSketch(style.id)}
357
383
  />
358
- <span class="preset-name">{look.label}</span>
384
+ <span class="preset-name">{style.label}</span>
359
385
  </label>
360
- {#if look.source === 'file'}
386
+ {#if style.source === 'file'}
361
387
  <button
362
388
  type="button"
363
389
  class="saved-delete"
364
- title="Delete {look.label}"
365
- aria-label="Delete {look.label}"
366
- onclick={() => runStyleAction(deleteSavedSketchStyle(look.id))}
390
+ title="Delete {style.label}"
391
+ aria-label="Delete {style.label}"
392
+ onclick={() => runStyleAction(deleteSavedSketchStyle(style.id))}
367
393
  ><i class="fas fa-xmark"></i></button>
368
394
  {/if}
369
395
  </div>
@@ -665,7 +691,7 @@
665
691
  />
666
692
  </div>
667
693
  </div>
668
- {#if !s.maskBlobLinked}
694
+ {#if s.maskBlobX !== s.maskBlobY}
669
695
  <SketchDial
670
696
  label="Rotation" value={s.maskAngle} min={0} max={180} step={1}
671
697
  readout={`${blobAngle}°`}
@@ -1097,7 +1123,7 @@
1097
1123
  color: var(--ui-text-secondary);
1098
1124
  }
1099
1125
 
1100
- .readout-off-look {
1126
+ .readout-off-style {
1101
1127
  margin: var(--ui-space-2) 0 0;
1102
1128
  font-size: var(--ui-font-size-sm);
1103
1129
  font-weight: var(--ui-font-weight-medium);
@@ -2259,12 +2259,12 @@
2259
2259
  "--columns-max-width": "1440px",
2260
2260
  "--columns-gutter": "32px",
2261
2261
  "--columns-margin": "0px",
2262
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2263
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2264
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2265
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2266
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2267
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2262
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2263
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2264
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2265
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2266
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2267
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2268
2268
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.25)",
2269
2269
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.25)",
2270
2270
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.25)",
@@ -2290,12 +2290,12 @@
2290
2290
  "--columns-max-width": "1440px",
2291
2291
  "--columns-gutter": "32px",
2292
2292
  "--columns-margin": "0px",
2293
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2294
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2295
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2296
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2297
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2298
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2293
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2294
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2295
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2296
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2297
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2298
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2299
2299
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.9)",
2300
2300
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.9)",
2301
2301
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.9)",
@@ -2306,12 +2306,12 @@
2306
2306
  "--columns-max-width": "1440px",
2307
2307
  "--columns-gutter": "32px",
2308
2308
  "--columns-margin": "0px",
2309
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2310
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2311
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2312
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2313
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2314
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2309
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2310
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2311
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2312
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2313
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2314
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2315
2315
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.9)",
2316
2316
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.9)",
2317
2317
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.9)",
@@ -2302,12 +2302,12 @@
2302
2302
  "--columns-max-width": "1440px",
2303
2303
  "--columns-gutter": "32px",
2304
2304
  "--columns-margin": "0px",
2305
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2306
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2307
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2308
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2309
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2310
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2305
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2306
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2307
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2308
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2309
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2310
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2311
2311
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.9)",
2312
2312
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.9)",
2313
2313
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.9)",
@@ -2259,12 +2259,12 @@
2259
2259
  "--columns-max-width": "1440px",
2260
2260
  "--columns-gutter": "32px",
2261
2261
  "--columns-margin": "0px",
2262
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2263
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2264
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2265
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2266
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2267
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2262
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2263
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2264
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2265
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2266
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2267
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2268
2268
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.2)",
2269
2269
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.2)",
2270
2270
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.2)",
@@ -2291,12 +2291,12 @@
2291
2291
  "--columns-max-width": "1440px",
2292
2292
  "--columns-gutter": "32px",
2293
2293
  "--columns-margin": "0px",
2294
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2295
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2296
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2297
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2298
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2299
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2294
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2295
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2296
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2297
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2298
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2299
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2300
2300
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.9)",
2301
2301
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.9)",
2302
2302
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.9)",
@@ -2245,12 +2245,12 @@
2245
2245
  "--columns-max-width": "1440px",
2246
2246
  "--columns-gutter": "32px",
2247
2247
  "--columns-margin": "0px",
2248
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2249
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2250
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2251
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2252
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2253
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2248
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2249
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2250
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2251
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2252
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2253
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2254
2254
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.2)",
2255
2255
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.2)",
2256
2256
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.2)",
@@ -2245,12 +2245,12 @@
2245
2245
  "--columns-max-width": "1440px",
2246
2246
  "--columns-gutter": "32px",
2247
2247
  "--columns-margin": "0px",
2248
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2249
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2250
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2251
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2252
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2253
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2248
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2249
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2250
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2251
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2252
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2253
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2254
2254
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.2)",
2255
2255
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.2)",
2256
2256
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.2)",
@@ -2306,12 +2306,12 @@
2306
2306
  "--columns-max-width": "1440px",
2307
2307
  "--columns-gutter": "32px",
2308
2308
  "--columns-margin": "0px",
2309
- "--overlay-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2310
- "--overlay": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2311
- "--overlay-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2312
- "--hover-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2313
- "--hover": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2314
- "--hover-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2309
+ "--scrim-low": "color-mix(in srgb, var(--surface-neutral-lowest) 38%, transparent)",
2310
+ "--scrim": "color-mix(in srgb, var(--surface-neutral-lowest) 51%, transparent)",
2311
+ "--scrim-high": "color-mix(in srgb, var(--surface-neutral-lowest) 64%, transparent)",
2312
+ "--tint-low": "color-mix(in srgb, var(--text-primary) 5%, transparent)",
2313
+ "--tint": "color-mix(in srgb, var(--text-primary) 10%, transparent)",
2314
+ "--tint-high": "color-mix(in srgb, var(--text-primary) 15%, transparent)",
2315
2315
  "--shadow-sm": "1px 1px 2px hsla(237, 18%, 3%, 0.9)",
2316
2316
  "--shadow-md": "3px 3px 6px hsla(237, 18%, 3%, 0.9)",
2317
2317
  "--shadow-lg": "5px 5px 9px hsla(237, 18%, 3%, 0.9)",
@@ -21,11 +21,11 @@
21
21
  "pooling": 1.5,
22
22
  "strokeInk": 0.4,
23
23
  "maskOn": true,
24
- "maskBlobX": 150,
25
- "maskBlobY": 150,
24
+ "maskBlobX": 170,
25
+ "maskBlobY": 170,
26
26
  "maskBlobLinked": true,
27
27
  "maskAngle": 0,
28
- "maskOutputMin": 0.24,
28
+ "maskOutputMin": 0.57,
29
29
  "maskOutputMax": 0.91,
30
30
  "maskOctaves": 2,
31
31
  "maskGrain": "fractal",
@@ -42,6 +42,6 @@
42
42
  "iconMaskOn": true,
43
43
  "iconMaskScale": 3.8
44
44
  },
45
- "updatedAt": "2026-08-28T15:47:36.934Z",
45
+ "updatedAt": "2026-08-28T21:19:51.383Z",
46
46
  "createdAt": "2026-08-28T15:47:36.934Z"
47
- }
47
+ }