@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
@@ -131,10 +131,10 @@ export function resolveFontStackValues(
131
131
  * in an iframe) via the same pipeline used for color variables.
132
132
  *
133
133
  * Returns the variables it set, so a caller tracking what it has applied — to
134
- * tear those vars down when switching looks — can record them without keeping
134
+ * tear those vars down when switching themes — can record them without keeping
135
135
  * its own copy of the stack list. A hand-maintained copy silently falls behind
136
136
  * whenever a stack is added here, leaving the missed variable stuck at the
137
- * outgoing look's value; `--font-editorial` did exactly that.
137
+ * outgoing theme's value; `--font-editorial` did exactly that.
138
138
  */
139
139
  export function applyFontStacks(
140
140
  stacks: FontStack[],
@@ -9,11 +9,11 @@ import { loadTheme } from '../themes/themeService';
9
9
  import { previewSketchStyle, revertSketchStylePreview } from '../sketch/sketchStore';
10
10
 
11
11
  /**
12
- * Client-side rendering of a saved file, for browsing looks without committing
13
- * one. A theme previews as a whole look; a colors-and-type file previews as
12
+ * Client-side rendering of a saved file, for browsing themes without committing
13
+ * one. A theme previews as a whole next; a colors-and-type file previews as
14
14
  * colors and type over the components the user has right now. Nothing here writes to the server,
15
15
  * mutates the editor store, or marks anything dirty: a preview is paint only.
16
- * Save-As stays honest because `captureLook` reads the server's active files,
16
+ * Save-As stays honest because `captureThemeContent` reads the server's active files,
17
17
  * which a preview never touches.
18
18
  *
19
19
  * Both painting and reverting run the same derivation the renderer runs on
@@ -27,23 +27,23 @@ import { previewSketchStyle, revertSketchStylePreview } from '../sketch/sketchSt
27
27
  * CSS vars do.
28
28
  *
29
29
  * One preview is live at a time, whichever kind it is: painting reverts first,
30
- * so every look is a diff against the user's real state.
30
+ * so every next is a diff against the user's real state.
31
31
  */
32
32
 
33
- /** Everything a look puts on the page: the full var set (colors and type,
33
+ /** Everything a next puts on the page: the full var set (colors and type,
34
34
  * components and the composed `--font-*` stacks) plus the sources its faces
35
35
  * come from. */
36
- export interface RenderedLook {
36
+ export interface RenderedTheme {
37
37
  vars: Record<string, string>;
38
38
  fontSources: FontSource[];
39
39
  }
40
40
 
41
41
  /**
42
42
  * The full var set a theme paints. Components the theme carries no config
43
- * for render `defaults`' config, matching Apply's complete-look semantics;
43
+ * for render `defaults`' config, matching Apply's complete-next semantics;
44
44
  * configs for components this install lacks are skipped, as Apply skips them.
45
45
  */
46
- export function themeLook(theme: Theme, defaults: Theme): RenderedLook {
46
+ export function renderTheme(theme: Theme, defaults: Theme): RenderedTheme {
47
47
  const colorsAndType = structuredClone(theme.colorsAndType);
48
48
  migrateColorsAndTypeFonts(colorsAndType);
49
49
  const state = colorsAndTypeToState(colorsAndType);
@@ -60,27 +60,27 @@ export function themeLook(theme: Theme, defaults: Theme): RenderedLook {
60
60
  }
61
61
 
62
62
  /**
63
- * The full var set colors and type paint on their own. They are not a whole look,
63
+ * The full var set colors and type paint on their own. They are not a whole next,
64
64
  * so the components stay as the user has them: `colorsAndTypeToState` carries the live
65
65
  * component slice forward (`loadComponentsFromVars`) and strips component-owned
66
66
  * vars out of their own bag, which is exactly the composition a preview
67
67
  * wants. No defaults fetch, no component reset.
68
68
  */
69
- export function colorsAndTypeLook(colorsAndType: ColorsAndType): RenderedLook {
69
+ export function renderColorsAndType(colorsAndType: ColorsAndType): RenderedTheme {
70
70
  const next = structuredClone(colorsAndType);
71
71
  migrateColorsAndTypeFonts(next);
72
72
  const vars = deriveCssVars(colorsAndTypeToState(next));
73
73
  return { vars, fontSources: next.fontSources ?? [] };
74
74
  }
75
75
 
76
- /** The look the editor store currently describes — the state a revert returns to. */
77
- export function liveLook(): RenderedLook {
76
+ /** The next the editor store currently describes — the state a revert returns to. */
77
+ export function liveTheme(): RenderedTheme {
78
78
  const state = get(editorState);
79
79
  const vars = deriveCssVars(state);
80
80
  return { vars, fontSources: state.fonts.sources };
81
81
  }
82
82
 
83
- let livePreview: RenderedLook | null = null;
83
+ let livePreview: RenderedTheme | null = null;
84
84
  let defaultsPromise: Promise<Theme> | null = null;
85
85
  // Whether the current preview session has a sketchstyle painted over the live
86
86
  // buffer, so a colors-only preview (which never previews sketch) and revert
@@ -101,7 +101,7 @@ function loadDefaults(): Promise<Theme> {
101
101
  return defaultsPromise;
102
102
  }
103
103
 
104
- function paint(next: RenderedLook, from: RenderedLook): void {
104
+ function paint(next: RenderedTheme, from: RenderedTheme): void {
105
105
  batchCssVarChanges(() => {
106
106
  for (const [name, value] of Object.entries(next.vars)) {
107
107
  if (from.vars[name] !== value) setCssVar(name, value);
@@ -114,33 +114,33 @@ function paint(next: RenderedLook, from: RenderedLook): void {
114
114
  }
115
115
 
116
116
  /**
117
- * Show `look` on the page. Re-entrant previews diff directly from the look
117
+ * Show `next` on the page. Re-entrant previews diff directly from the next
118
118
  * already painted; only Cancel restores the live store projection. This avoids
119
119
  * repainting the live theme as an invisible intermediate on every picker row.
120
120
  */
121
- function applyPreview(look: RenderedLook): void {
122
- paint(look, livePreview ?? liveLook());
123
- livePreview = look;
121
+ function applyPreview(next: RenderedTheme): void {
122
+ paint(next, livePreview ?? liveTheme());
123
+ livePreview = next;
124
124
  }
125
125
 
126
- /** Paint a whole look: the theme's colors and type, every component config
126
+ /** Paint a whole next: the theme's colors and type, every component config
127
127
  * it carries, and its sketchstyle — present or not, since a theme with none
128
128
  * paints crisp regardless of what is live (invariant 3). */
129
129
  export async function previewTheme(theme: Theme): Promise<void> {
130
130
  const gen = generation;
131
131
  const defaults = await loadDefaults();
132
132
  if (gen !== generation) return;
133
- applyPreview(themeLook(theme, defaults));
134
- previewSketchStyle(theme.sketchStyle);
133
+ applyPreview(renderTheme(theme, defaults));
134
+ previewSketchStyle(theme.sketchSettings);
135
135
  sketchPreviewActive = true;
136
136
  }
137
137
 
138
138
  /** Paint colors and type over the components as they stand. Not a whole
139
- * look, so the sketchstyle stays live too — reverting a sketch preview a
139
+ * next, so the sketchstyle stays live too — reverting a sketch preview a
140
140
  * prior row left painted, if one is running. */
141
141
  export function previewColorsAndType(colorsAndType: ColorsAndType): void {
142
142
  generation++;
143
- applyPreview(colorsAndTypeLook(colorsAndType));
143
+ applyPreview(renderColorsAndType(colorsAndType));
144
144
  if (sketchPreviewActive) {
145
145
  revertSketchStylePreview();
146
146
  sketchPreviewActive = false;
@@ -151,7 +151,7 @@ export function previewColorsAndType(colorsAndType: ColorsAndType): void {
151
151
  export function revertPreview(): void {
152
152
  generation++;
153
153
  if (!livePreview) return;
154
- paint(liveLook(), livePreview);
154
+ paint(liveTheme(), livePreview);
155
155
  livePreview = null;
156
156
  if (sketchPreviewActive) {
157
157
  revertSketchStylePreview();
@@ -160,11 +160,11 @@ export function revertPreview(): void {
160
160
  }
161
161
 
162
162
  /** Release the preview without repainting. The caller must immediately load
163
- * the exact look being previewed into the store. This is the Save handoff: the
164
- * selected theme is already on screen, so restoring the old live look before
163
+ * the exact next being previewed into the store. This is the Save handoff: the
164
+ * selected theme is already on screen, so restoring the old live next before
165
165
  * applying it would add work and create a visible flash across the request.
166
166
  * The sketchstyle stays painted for the same reason, though the handoff is not
167
- * quite free: when the live state was crisp, `openThemeSketchStyle` writes the
167
+ * quite free: when the live state was crisp, `openThemeSketchSettings` writes the
168
168
  * settings before the flag, so the sheet and the filter bank come down and go
169
169
  * straight back up in between. That order is still the right one, since writing
170
170
  * the flag first would paint the OLD dials for a frame. */
@@ -28,17 +28,17 @@ export function bumpComponentActiveRevision(): void {
28
28
  }
29
29
 
30
30
  /**
31
- * The live look has moved past the last bake. Only Adopt bakes the CSS, so a
31
+ * The live theme has moved past the last bake. Only Adopt bakes the CSS, so a
32
32
  * buffer write or a theme save after one leaves production a version behind,
33
33
  * and no pointer file records when the bake happened. Every surface reads the
34
34
  * one signal: a component save has to reach the theme panel's Adopt, and a
35
35
  * component editor's Adopt has to clear it for the panel.
36
36
  *
37
- * Set by the client writes that move the live look (`writeWorkingColorsAndType`,
37
+ * Set by the client writes that move the live theme (`writeWorkingColorsAndType`,
38
38
  * `writeWorkingComponentConfig`, `saveActiveTheme`, `saveAsTheme`, and every
39
39
  * sketch gesture that changes what the effect paints: `setSketchEnabled`,
40
40
  * `updateSketchSettings`, `selectSketchStyle`);
41
- * cleared by `adoptLook` and by `hydrateAppliedTheme`, on this document and on
41
+ * cleared by `adoptTheme` and by `hydrateAppliedTheme`, on this document and on
42
42
  * every peer an Apply broadcasts to. Module-level, so it survives the
43
43
  * remounts a view switch causes.
44
44
  */
@@ -1,70 +1,78 @@
1
1
  import { derived, get, type Readable } from 'svelte/store';
2
2
  import { THEME_SKETCH_ID } from './sketchStyles';
3
- import { lookById, sketchLooks, type SketchLook } from './sketchRegistry';
3
+ import { sketchStyleById, sketchStyles, type SketchStyle } from './sketchRegistry';
4
4
  import {
5
- sameLook,
5
+ sameSketchStyle,
6
6
  selectSketchStyle,
7
- selectThemeSketchStyle,
7
+ selectUnsavedSketchStyle,
8
8
  setSketchEnabled,
9
9
  sketchEnabled,
10
- sketchStyleName,
11
- themeSketchStyle,
10
+ selectedSketchStyleId,
11
+ themeSketchSettings,
12
12
  } from './sketchStore';
13
13
 
14
- export { sketchLooks, registerSketchLook } from './sketchRegistry';
15
- export type { SketchLook, SketchLookSource, RegisterSketchLookInput } from './sketchRegistry';
14
+ export { sketchStyles, registerSketchStyle } from './sketchRegistry';
15
+ export type { SketchStyle, SketchStyleSource, RegisterSketchStyleInput } from './sketchRegistry';
16
16
 
17
- /** A look as one picker row. The theme's own look is offered this way too, and
17
+ /** A style as one picker row. The theme's own style is offered this way too, and
18
18
  it belongs to no pool entry, so the fields only a pool entry has are off. */
19
- export type SketchLookRow = Omit<SketchLook, 'settings' | 'source'>;
19
+ export type SketchStyleRow = Omit<SketchStyle, 'settings' | 'source'>;
20
20
 
21
21
  /**
22
- * The look the open theme carries, as one more row for a picker: same shape as
23
- * a shipped look, and `setSketch` takes its id like any other. Null when the
24
- * theme carries no sketchstyle, and null when what it carries IS one of the
25
- * shipped looks, since that look's own row already names it.
22
+ * Sketch settings the open theme carries that no sketchstyle file holds, as one
23
+ * more row for a picker: same shape as a shipped sketchstyle, and `setSketch`
24
+ * takes its id like any other. Null when the theme carries no settings, and
25
+ * null when what it carries matches a sketchstyle, since that file's own row
26
+ * already names it.
26
27
  *
27
- * Without this row the theme's look is a one-way door: a visitor lands on it,
28
- * picks Pencil, and nothing can take them back. It is also the only thing that
29
- * can name that look, which no shipped label can do honestly. A theme tuned off
30
- * `marker` still carries the label "Marker", so a row built from the style's
31
- * own label would sit beside the shipped Marker claiming to be it.
28
+ * A theme embeds its sketch settings by value, so it can hold a set that
29
+ * belongs to no file: Save As is what turns one into a sketchstyle. Until then
30
+ * this is the only row that can offer it. Without it the settings are a one-way
31
+ * door, a visitor picks Pencil and nothing can take them back.
32
+ *
33
+ * Labelled "Unsaved" rather than by the settings' own label, which lies: a theme
34
+ * tuned off `marker` still carries the label "Marker", so a row built from it
35
+ * would sit beside the shipped Marker claiming to be it.
32
36
  */
33
- export const themeSketchLook: Readable<SketchLookRow | null> = derived(
34
- [themeSketchStyle, sketchLooks],
35
- ([style, looks]) => {
36
- if (!style) return null;
37
- if (looks.some((look) => sameLook(look.settings, style))) return null;
38
- return { id: THEME_SKETCH_ID, label: 'Theme', blurb: 'The look this theme carries.' };
37
+ export const unsavedSketchStyle: Readable<SketchStyleRow | null> = derived(
38
+ [themeSketchSettings, sketchStyles],
39
+ ([settings, styles]) => {
40
+ if (!settings) return null;
41
+ if (styles.some((style) => sameSketchStyle(style.settings, settings))) return null;
42
+ return {
43
+ id: THEME_SKETCH_ID,
44
+ label: 'Unsaved',
45
+ blurb: 'Sketch settings this theme carries that no sketchstyle holds.',
46
+ };
39
47
  },
40
48
  );
41
49
 
42
50
  /**
43
51
  * What the page is drawing with. Three states, not two: the effect can be on
44
- * under a look no shipped sketchstyle names — one saved to a file, or one a
52
+ * under a style no shipped sketchstyle names — one saved to a file, or one a
45
53
  * theme carried — and a picker that collapses that into `off` tells the
46
54
  * visitor the page is crisp while it is visibly drawn.
47
55
  *
48
- * A dial moved off a shipped look keeps naming it, which is `selectSketchStyle`'s
49
- * own rule: the pick says where the look came from, and `sketchDirty` says it
56
+ * A dial moved off a shipped style keeps naming it, which is `selectSketchStyle`'s
57
+ * own rule: the pick says where the style came from, and `sketchDirty` says it
50
58
  * has since drifted.
51
59
  */
52
60
  export type SketchPick =
53
61
  | { state: 'off' }
54
- | { state: 'look'; look: SketchLookRow }
62
+ | { state: 'style'; style: SketchStyleRow }
55
63
  | { state: 'adjusted' };
56
64
 
57
65
  export const sketchPick: Readable<SketchPick> = derived(
58
- [sketchEnabled, sketchStyleName, sketchLooks, themeSketchLook],
59
- ([on, name, looks, themeLook]): SketchPick => {
66
+ [sketchEnabled, selectedSketchStyleId, sketchStyles, unsavedSketchStyle],
67
+ ([on, name, styles, unsaved]): SketchPick => {
60
68
  if (!on) return { state: 'off' };
61
- const look = looks.find((l) => l.id === name) ?? (themeLook?.id === name ? themeLook : undefined);
62
- return look ? { state: 'look', look: { id: look.id, label: look.label, blurb: look.blurb } } : { state: 'adjusted' };
69
+ const style = styles.find((s) => s.id === name) ?? (unsaved?.id === name ? unsaved : undefined);
70
+ return style ? { state: 'style', style: { id: style.id, label: style.label, blurb: style.blurb } } : { state: 'adjusted' };
63
71
  },
64
72
  );
65
73
 
66
74
  /**
67
- * Draw the page with one of the looks in the pool, or `null` for none.
75
+ * Draw the page with one of the styles in the pool, or `null` for none.
68
76
  *
69
77
  * The only supported way for a consumer to drive the effect. Reaching for
70
78
  * `applySketchLayer` instead paints a stylesheet the store does not know it
@@ -77,15 +85,15 @@ export function setSketch(id: string | null): void {
77
85
  return;
78
86
  }
79
87
  if (id === THEME_SKETCH_ID) {
80
- if (!get(themeSketchStyle)) {
81
- throw new Error('No theme sketchstyle to draw with. `themeSketchLook` is null unless a theme carries one.');
88
+ if (!get(themeSketchSettings)) {
89
+ throw new Error('No theme sketchstyle to draw with. `unsavedSketchStyle` is null unless a theme carries one.');
82
90
  }
83
- selectThemeSketchStyle();
91
+ selectUnsavedSketchStyle();
84
92
  setSketchEnabled(true);
85
93
  return;
86
94
  }
87
- if (!lookById(id)) {
88
- throw new Error(`Unknown sketchstyle "${id}". Ids come from sketchLooks and themeSketchLook.`);
95
+ if (!sketchStyleById(id)) {
96
+ throw new Error(`Unknown sketchstyle "${id}". Ids come from sketchStyles and unsavedSketchStyle.`);
89
97
  }
90
98
  selectSketchStyle(id);
91
99
  setSketchEnabled(true);
@@ -106,22 +114,22 @@ export { hasPersistedSketchState } from './sketchStore';
106
114
  * already decided for itself.
107
115
  *
108
116
  * The route from a saved theme to a built page. Hand it the theme's
109
- * `sketchStyle` field, raw: a built site has no theme API, so it reads its own
117
+ * `sketchSettings` field, raw: a built site has no theme API, so it reads its own
110
118
  * theme JSON and this hydrates what it finds. Absent, `null`, or anything that
111
119
  * is not an object all mean the same thing, which is no sketch.
112
120
  *
113
121
  * Call it before mounting, the way dev boot does (`bootstrap.ts` awaits
114
- * `initializeTheme` first), so the look is up on the first frame rather than
122
+ * `initializeTheme` first), so the style is up on the first frame rather than
115
123
  * arriving over a crisp page.
116
124
  *
117
125
  * A visitor who has recorded a pick of their own keeps it, None included: this
118
126
  * seeds an undecided browser and never overwrites a decided one, so it is safe
119
- * to call on every boot. `themeSketchLook` is populated either way, so a picker
120
- * can offer the theme's look as a row whether or not this painted it.
127
+ * to call on every boot. `unsavedSketchStyle` is populated either way, so a picker
128
+ * can offer the theme's style as a row whether or not this painted it.
121
129
  */
122
130
  export { seedSketchFromTheme } from './sketchStore';
123
131
 
124
- /** The dial set a theme's `sketchStyle` field holds, for a consumer typing the
132
+ /** The dial set a theme's `sketchSettings` field holds, for a consumer typing the
125
133
  value it pulled out of its own theme JSON. `seedSketchFromTheme` takes it
126
134
  raw, so nothing has to be cast to hand it over. */
127
- export type { SketchStyle } from './sketchStyles';
135
+ export type { SketchStyleSettings } from './sketchStyles';
@@ -19,7 +19,7 @@
19
19
  * Everything is integer maths on a Float32Array with no DOM, so what a test
20
20
  * asserts is what the browser paints.
21
21
  */
22
- import type { SketchStyle } from './sketchStyles';
22
+ import type { SketchStyleSettings } from './sketchStyles';
23
23
 
24
24
  /** Samples across the tile, on both axes. Fixed, whatever the blobs measure:
25
25
  the field is blobs and gradients, so the browser stretching the tile back to
@@ -69,7 +69,7 @@ export interface MaskLattice {
69
69
  angle: number;
70
70
  }
71
71
 
72
- export function maskLattice(s: SketchStyle): MaskLattice {
72
+ export function maskLattice(s: SketchStyleSettings): MaskLattice {
73
73
  const ask = s.maskBlobX === s.maskBlobY ? 0 : s.maskAngle;
74
74
  const rad = ask * (Math.PI / 180);
75
75
  const cos = Math.cos(rad), sin = Math.sin(rad);
@@ -78,7 +78,7 @@ export function maskLattice(s: SketchStyle): MaskLattice {
78
78
  const vx = fit(dx, ask, (v) => turnOf(-v[1] * s.maskBlobY, v[0] * s.maskBlobX));
79
79
  let vy = fit(dy, ask, (v) => turnOf(v[0] * s.maskBlobX, v[1] * s.maskBlobY));
80
80
  // Two vectors along one line describe no tile at all. The turn of the first
81
- // is the whole look, so the second is the one that gives way.
81
+ // fixes the tile, so the second is the one that gives way.
82
82
  if (vx[0] * vy[1] - vx[1] * vy[0] === 0) vy = [-vx[1], vx[0]];
83
83
  return {
84
84
  vx,
@@ -131,7 +131,7 @@ function fit(d: Vec, ask: number, turn: (v: Vec) => number): Vec {
131
131
  * fitted to it, which meant the dial could only reach the sizes that divide
132
132
  * 600: it said 250px and painted 300px, and there was nothing above that.
133
133
  */
134
- export function maskTile(s: SketchStyle): { w: number; h: number } {
134
+ export function maskTile(s: SketchStyleSettings): { w: number; h: number } {
135
135
  const { w, h } = maskLattice(s);
136
136
  return { w, h };
137
137
  }
@@ -228,11 +228,11 @@ function makePerlin(seed: number): (x: number, y: number, w: Wrap) => number {
228
228
  * the one before, stretched onto its own measured range.
229
229
  *
230
230
  * `veined` folds each octave at zero before summing. The fold puts a crease
231
- * wherever the octave crossed zero, which is the marbled look; plain sum is
231
+ * wherever the octave crossed zero, which is the marbled effect; plain sum is
232
232
  * cloud.
233
233
  */
234
234
  function rawField(
235
- s: SketchStyle, seed: number, raster: number, lattice: MaskLattice,
235
+ s: SketchStyleSettings, seed: number, raster: number, lattice: MaskLattice,
236
236
  ): Float32Array {
237
237
  const noise = makePerlin(seed);
238
238
  const veined = s.maskGrain === 'turbulence';
@@ -388,7 +388,7 @@ function boxPass(f: Float32Array, n: number, radius: number, stride: number): Fl
388
388
  all four; the levels, the posterising and the blur are one pass each. */
389
389
  let rawCache: { key: string; field: Float32Array } | null = null;
390
390
 
391
- function cachedRaw(s: SketchStyle, seed: number): Float32Array {
391
+ function cachedRaw(s: SketchStyleSettings, seed: number): Float32Array {
392
392
  const lattice = maskLattice(s);
393
393
  // Keyed on the tile vectors rather than the blob sizes: most of the Scale
394
394
  // dial's travel paints the same lattice at another size, and the field is
@@ -407,7 +407,7 @@ function cachedRaw(s: SketchStyle, seed: number): Float32Array {
407
407
  * pass did.
408
408
  */
409
409
  export function buildMaskField(
410
- s: SketchStyle, seed = 9, through?: MaskStage,
410
+ s: SketchStyleSettings, seed = 9, through?: MaskStage,
411
411
  ): { field: Float32Array; raster: number } {
412
412
  const raw = cachedRaw(s, seed);
413
413
  if (through === 'noise') return { field: raw, raster: RASTER };
@@ -544,7 +544,7 @@ const CACHE_MAX = 6;
544
544
  const cache = new Map<string, string>();
545
545
 
546
546
  /** The field as a `url(...)` for `mask-image`. */
547
- export function buildMaskUri(s: SketchStyle, seed = 9, through?: MaskStage): string {
547
+ export function buildMaskUri(s: SketchStyleSettings, seed = 9, through?: MaskStage): string {
548
548
  const key = [...KEYS.map((k) => s[k]), seed, through ?? 'all'].join('|');
549
549
  const hit = cache.get(key);
550
550
  if (hit) return hit;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Sketch effect layer.
3
3
  *
4
- * Builds an SVG filter bank and a stylesheet from one SketchStyle and
4
+ * Builds an SVG filter bank and a stylesheet from one SketchStyleSettings and
5
5
  * injects both into every document cssVarSync tracks, so the host page behind
6
6
  * the overlay iframe gets the same effect the editor's preview shows.
7
7
  *
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import { getSyncedDocuments } from '../cssVarSync';
18
18
  import { buildMaskUri, maskTile } from './maskField';
19
- import type { SketchStyle } from './sketchStyles';
19
+ import type { SketchStyleSettings } from './sketchStyles';
20
20
 
21
21
  const DEFS_ATTR = 'data-sketch-defs';
22
22
  const STYLE_ATTR = 'data-sketch-style';
@@ -227,6 +227,22 @@ const PART_SPECS: readonly PartSpec[] = [
227
227
  // Holds the picture inside the frame; without it a zoom spills out square.
228
228
  clips: true,
229
229
  },
230
+ ...['single', 'range'].map((v) => ({
231
+ sel: `.slider.${v} .slider-track`,
232
+ fill: `var(--slider-${v}-track-surface)`,
233
+ stroke: `var(--slider-${v}-track-border)`,
234
+ radius: `var(--slider-${v}-track-radius, 0px)`,
235
+ })),
236
+ // The fill rides inside the track, so it is stroked by nothing and hatched
237
+ // in the track's ink.
238
+ ...['single', 'range'].map((v) => ({
239
+ sel: `.slider.${v} .slider-fill`,
240
+ fill: `var(--slider-${v}-fill)`,
241
+ stroke: 'transparent',
242
+ hatch: `var(--slider-${v}-track-border)`,
243
+ radius: `var(--slider-${v}-track-radius, 0px)`,
244
+ positioned: true,
245
+ })),
230
246
  { sel: '.toggle .track', stem: 'toggle-track' },
231
247
  { sel: '.toggle.on .track', stem: 'toggle-on-track', radius: 'var(--toggle-track-radius, 0px)' },
232
248
 
@@ -325,7 +341,7 @@ const UNCLIPPED = PART_SPECS.filter((p) => !p.clips).map((p) => p.sel).join(', '
325
341
  const CLIPPED = PART_SPECS.filter((p) => p.clips).map((p) => p.sel).join(', ');
326
342
  const UNMASKED = PART_SPECS.filter((p) => p.unmasked).map((p) => p.sel).join(', ');
327
343
 
328
- export function buildDefsMarkup(s: SketchStyle): string {
344
+ export function buildDefsMarkup(s: SketchStyleSettings): string {
329
345
  /**
330
346
  * `warp` is the shape stage: one wave of noise whose wavelength spans a whole
331
347
  * component, so the four corners sample different parts of the field and the
@@ -559,7 +575,7 @@ const MASK_POS = ['0 0', '-137px -211px', '-311px -97px', '-73px -389px', '-419p
559
575
  * A hand holds a rough size and misses it by a bit each time, so half to full
560
576
  * is the shape of the error. The four land in a different order row to row.
561
577
  *
562
- * Added, never subtracted, because a hand-drawn look tends to sit on a theme
578
+ * Added, never subtracted, because a hand-drawn line tends to sit on a theme
563
579
  * whose corners are already tight: a coefficient set that swung both ways
564
580
  * spent half its range clamped at zero.
565
581
  */
@@ -584,7 +600,7 @@ const swing = (travel: number) => String(Number((travel * 2).toFixed(4)));
584
600
  /** Squares the displacement wave off around 0.5, its zero, so full amplitude is
585
601
  spent along the whole edge rather than only where the wave peaks. Both
586
602
  channels take it: the map reads x from R and y from G. */
587
- function squareOff(s: SketchStyle, from: string, to: string): string {
603
+ function squareOff(s: SketchStyleSettings, from: string, to: string): string {
588
604
  if (s.waveform <= 1) return '';
589
605
  const slope = s.waveform.toFixed(2);
590
606
  const intercept = ((1 - s.waveform) / 2).toFixed(3);
@@ -594,7 +610,7 @@ function squareOff(s: SketchStyle, from: string, to: string): string {
594
610
  `</feComponentTransfer>`;
595
611
  }
596
612
 
597
- const squaredResult = (s: SketchStyle, from: string, to: string) =>
613
+ const squaredResult = (s: SketchStyleSettings, from: string, to: string) =>
598
614
  (s.waveform > 1 ? to : from);
599
615
 
600
616
  /** Along-stroke pressure wavelength. Low, with a high floor in the transfer
@@ -650,7 +666,7 @@ const RETRACE_SEED = 53;
650
666
  const WARP_FREQUENCY = 0.08;
651
667
 
652
668
 
653
- export function buildStylesheet(s: SketchStyle): string {
669
+ export function buildStylesheet(s: SketchStyleSettings): string {
654
670
  const on = '[data-sketch]';
655
671
  const parts = `:is(${PARTS})`;
656
672
  const el = `${on} ${parts}`;
@@ -1089,7 +1105,7 @@ function defsNode(doc: Document): SVGSVGElement {
1089
1105
  * This only makes the effect *available*. An element opts in by carrying
1090
1106
  * data-sketch, which is what `setSketchScope` writes.
1091
1107
  */
1092
- export function applySketchLayer(settings: SketchStyle): void {
1108
+ export function applySketchLayer(settings: SketchStyleSettings): void {
1093
1109
  const defs = buildDefsMarkup(settings);
1094
1110
  const css = buildStylesheet(settings);
1095
1111
  for (const doc of getSyncedDocuments()) {
@@ -1132,7 +1148,7 @@ export function removeSketchLayer(): void {
1132
1148
  * The host page's root and the editor's own preview container are both scopes,
1133
1149
  * which is why this takes an element rather than assuming documentElement.
1134
1150
  */
1135
- export function setSketchScope(el: HTMLElement | null, settings: SketchStyle | null): void {
1151
+ export function setSketchScope(el: HTMLElement | null, settings: SketchStyleSettings | null): void {
1136
1152
  if (!el) return;
1137
1153
  if (!settings) {
1138
1154
  el.removeAttribute('data-sketch');