@motion-proto/live-tokens 0.61.0 → 0.63.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.
- package/.claude/skills/live-tokens-adjust-geometry/SKILL.md +4 -4
- package/.claude/skills/live-tokens-build-page/SKILL.md +21 -3
- package/.claude/skills/live-tokens-create-component/SKILL.md +16 -48
- package/.claude/skills/live-tokens-create-component/references/fixed-overlays.md +10 -1
- package/.claude/skills/live-tokens-create-component/references/intrinsics.md +7 -5
- package/.claude/skills/live-tokens-create-component/references/sketch-mode.md +1 -1
- package/.claude/skills/live-tokens-create-component/references/token-naming.md +50 -0
- package/.claude/skills/live-tokens-generate-theme/SKILL.md +16 -9
- package/.claude/skills/live-tokens-pair-fonts/SKILL.md +8 -6
- package/.claude/skills/live-tokens-pick-component/SKILL.md +19 -5
- package/CHANGELOG.md +114 -0
- package/bin/migrate.mjs +6 -2
- package/dist-plugin/adjust/index.cjs +1 -1
- package/dist-plugin/adjust/index.d.cts +1 -1
- package/dist-plugin/adjust/index.d.ts +1 -1
- package/dist-plugin/adjust/index.js +1 -1
- package/dist-plugin/{chunk-232GZGQU.js → chunk-NDJJORKJ.js} +342 -5
- package/dist-plugin/{chunk-Y5CNFSSV.js → chunk-RVE3MNKM.js} +1 -1
- package/dist-plugin/{chunk-OIOXU7FR.js → chunk-ZHPX7ZYQ.js} +83 -25
- package/dist-plugin/{dataPaths-CRfD1LdA.d.ts → dataPaths-DZUzVv8H.d.cts} +3 -3
- package/dist-plugin/{dataPaths-CRfD1LdA.d.cts → dataPaths-DZUzVv8H.d.ts} +3 -3
- package/dist-plugin/fontPairing/index.cjs +1 -1
- package/dist-plugin/fontPairing/index.d.cts +1 -1
- package/dist-plugin/fontPairing/index.d.ts +1 -1
- package/dist-plugin/fontPairing/index.js +1 -1
- package/dist-plugin/generateColorsAndType/index.cjs +1 -1
- package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
- package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
- package/dist-plugin/generateColorsAndType/index.js +1 -1
- package/dist-plugin/index.cjs +461 -58
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +31 -23
- package/dist-plugin/migrateData/index.cjs +350 -9
- package/dist-plugin/migrateData/index.d.cts +1 -1
- package/dist-plugin/migrateData/index.d.ts +1 -1
- package/dist-plugin/migrateData/index.js +9 -5
- package/dist-plugin/tokensCssMigrations/index.cjs +83 -25
- package/dist-plugin/tokensCssMigrations/index.d.cts +1 -1
- package/dist-plugin/tokensCssMigrations/index.d.ts +1 -1
- package/dist-plugin/tokensCssMigrations/index.js +2 -2
- package/package.json +4 -2
- package/src/app/site.css +32 -0
- package/src/editor/component-editor/CardEditor.svelte +7 -1
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +25 -2
- package/src/editor/core/preview/lookPreview.ts +46 -5
- package/src/editor/core/productionPulse.ts +6 -2
- package/src/editor/core/sketch/maskField.ts +75 -39
- package/src/editor/core/sketch/sketchLayer.ts +32 -11
- package/src/editor/core/sketch/sketchStore.ts +311 -86
- package/src/editor/core/sketch/{sketchPresetService.ts → sketchStyleService.ts} +14 -14
- package/src/editor/core/sketch/{sketchPresets.ts → sketchStyles.ts} +27 -28
- package/src/editor/core/themes/themeDocumentSync.ts +2 -0
- package/src/editor/core/themes/themeInit.ts +19 -1
- package/src/editor/core/themes/themeService.ts +7 -2
- package/src/editor/core/themes/themeTypes.ts +5 -0
- package/src/editor/docs/content/editing-tokens.md +1 -1
- package/src/editor/docs/content/sketch-mode.md +34 -18
- package/src/editor/docs/content/themes-workflow.md +30 -21
- package/src/editor/docs/content/where-themes-live.md +9 -5
- package/src/editor/docs/content.generated.ts +4 -4
- package/src/editor/overlay/LiveTokensRouter.svelte +8 -0
- package/src/editor/ui/EditorViewSwitcher.svelte +3 -3
- package/src/editor/ui/ThemePanel.svelte +47 -1
- package/src/editor/ui/sections/textStyles.ts +29 -1
- package/src/editor/ui/sketch/SketchPreview.svelte +3 -3
- package/src/editor/ui/sketch/SketchTab.svelte +120 -134
- package/src/live-tokens/data/colors-and-type/midnight-study.json +40 -30
- package/src/live-tokens/data/themes/autumn.json +3 -3
- package/src/live-tokens/data/themes/halloween.json +3 -3
- package/src/live-tokens/data/themes/midnight-study.json +61 -49
- package/src/live-tokens/data/themes/ocean.json +3 -3
- package/src/live-tokens/data/themes/royal-velvet.json +3 -3
- package/src/live-tokens/data/themes/sketchy.json +3 -3
- package/src/live-tokens/data/themes/spring-meadow.json +3 -3
- package/src/live-tokens/data/themes/sunset.json +3 -3
- package/src/system/components/Card.svelte +27 -9
- package/src/system/components/FloatingTokenTags.css +10 -8
- package/src/system/components/ImageLightbox.svelte +5 -2
- package/src/system/components/SectionDivider.svelte +3 -3
- package/src/system/components/SegmentedControl.svelte +11 -9
- package/src/system/styles/tokens.css +28 -5
- package/template/src/pages/Home.svelte +1 -11
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { API_BASE } from '../storage/apiBase';
|
|
2
|
-
import {
|
|
2
|
+
import { hydrateSketchStyle, type SketchStyle } from './sketchStyles';
|
|
3
3
|
|
|
4
|
-
export interface
|
|
4
|
+
export interface SketchStyleFile {
|
|
5
5
|
name: string;
|
|
6
6
|
createdAt?: string;
|
|
7
7
|
updatedAt?: string;
|
|
8
|
-
settings:
|
|
8
|
+
settings: SketchStyle;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export interface
|
|
11
|
+
export interface SketchStyleMeta {
|
|
12
12
|
name: string;
|
|
13
13
|
fileName: string;
|
|
14
14
|
updatedAt: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
const BASE = `${API_BASE}/sketch-
|
|
17
|
+
const BASE = `${API_BASE}/sketch-styles`;
|
|
18
18
|
|
|
19
19
|
/** Slug for the file name. The display name is stored inside the file, so this
|
|
20
20
|
only has to be stable, unique-ish and inside the route's charset. */
|
|
21
|
-
export function
|
|
21
|
+
export function slugifySketchStyle(name: string): string {
|
|
22
22
|
return name
|
|
23
23
|
.toLowerCase()
|
|
24
24
|
.replace(/[^a-z0-9]+/g, '-')
|
|
@@ -26,24 +26,24 @@ export function slugifySketchPreset(name: string): string {
|
|
|
26
26
|
.slice(0, 60);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export async function
|
|
29
|
+
export async function listSketchStyles(): Promise<SketchStyleMeta[]> {
|
|
30
30
|
const res = await fetch(BASE);
|
|
31
|
-
if (!res.ok) throw new Error('Failed to list
|
|
31
|
+
if (!res.ok) throw new Error('Failed to list sketchstyles');
|
|
32
32
|
const body = await res.json();
|
|
33
33
|
return body.files ?? [];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export async function
|
|
36
|
+
export async function loadSketchStyle(fileName: string): Promise<SketchStyleFile> {
|
|
37
37
|
const res = await fetch(`${BASE}/${encodeURIComponent(fileName)}`);
|
|
38
|
-
if (!res.ok) throw new Error(`Failed to load
|
|
38
|
+
if (!res.ok) throw new Error(`Failed to load sketchstyle: ${fileName}`);
|
|
39
39
|
const body = await res.json();
|
|
40
|
-
return { ...body, settings:
|
|
40
|
+
return { ...body, settings: hydrateSketchStyle(body.settings) };
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export async function
|
|
43
|
+
export async function saveSketchStyle(
|
|
44
44
|
fileName: string,
|
|
45
45
|
name: string,
|
|
46
|
-
settings:
|
|
46
|
+
settings: SketchStyle,
|
|
47
47
|
): Promise<void> {
|
|
48
48
|
const res = await fetch(`${BASE}/${encodeURIComponent(fileName)}`, {
|
|
49
49
|
method: 'PUT',
|
|
@@ -56,7 +56,7 @@ export async function saveSketchPreset(
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export async function
|
|
59
|
+
export async function deleteSketchStyle(fileName: string): Promise<void> {
|
|
60
60
|
const res = await fetch(`${BASE}/${encodeURIComponent(fileName)}`, { method: 'DELETE' });
|
|
61
61
|
if (!res.ok) {
|
|
62
62
|
const err = await res.json().catch(() => ({ error: 'Delete failed' }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface SketchStyle {
|
|
2
2
|
label: string;
|
|
3
3
|
blurb: string;
|
|
4
4
|
/** How far the fill's edge travels at its furthest, in px. Every dial that
|
|
@@ -62,11 +62,10 @@ export interface SketchSettings {
|
|
|
62
62
|
/** Wavelength of the coverage noise, in page px. Small gives speckle, large gives broad patches. */
|
|
63
63
|
maskBlob: number;
|
|
64
64
|
/** Output levels on the coverage field, 0 to 1: the palest the fill gets and
|
|
65
|
-
the densest.
|
|
66
|
-
thinner than 40% ink
|
|
67
|
-
together is a flat wash, far apart a
|
|
68
|
-
|
|
69
|
-
at every grain and octave count. */
|
|
65
|
+
the densest. The whole field is squeezed into the gap, never cut at it, so
|
|
66
|
+
0.4 to 1 is a fill that is never thinner than 40% ink and 0 to 0.8 one
|
|
67
|
+
that never quite fills in. Close together is a flat wash, far apart a
|
|
68
|
+
strong blotch. */
|
|
70
69
|
maskOutputMin: number;
|
|
71
70
|
maskOutputMax: number;
|
|
72
71
|
/** Detail layers. 1-2 gives broad blobs, 4+ goes cloudy and stops reading as blotches. */
|
|
@@ -120,7 +119,7 @@ export interface SketchSettings {
|
|
|
120
119
|
iconMaskScale: number;
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
const base:
|
|
122
|
+
const base: SketchStyle = {
|
|
124
123
|
label: '', blurb: '',
|
|
125
124
|
fillTravel: 1.5, strokeTravel: 1.5, wobble: 45, roughness: 3,
|
|
126
125
|
borderWavelength: 1, waveform: 1,
|
|
@@ -134,13 +133,13 @@ const base: SketchSettings = {
|
|
|
134
133
|
iconTravel: 1.25, iconWavelength: 0.625, iconMaskOn: true, iconMaskScale: 1,
|
|
135
134
|
};
|
|
136
135
|
|
|
137
|
-
export const
|
|
136
|
+
export const SKETCH_STYLES: Record<string, SketchStyle> = {
|
|
138
137
|
pencil: {
|
|
139
138
|
...base, label: 'Pencil',
|
|
140
139
|
blurb: 'Two graphite passes on their own seeds, so the outline disagrees with itself the way a hand coming back round does. Tight grain, little else.',
|
|
141
140
|
fillTravel: 0.75, strokeTravel: 1.25, wobble: 30, roughness: 3, waveform: 1,
|
|
142
141
|
strokeWidth: 1.25, doubleStroke: true, retracePass: 'reseeded', retraceOffset: 1.5, strokeInk: 0.85,
|
|
143
|
-
maskBlob: 40, maskOutputMin: 0.62, maskOutputMax: 1, maskOctaves: 3, maskPosterize: 1, maskSoftness: 0
|
|
142
|
+
maskBlob: 40, maskOutputMin: 0.62, maskOutputMax: 1, maskOctaves: 3, maskPosterize: 1, maskSoftness: 0,
|
|
144
143
|
jitterX: 1.5, jitterY: 1.5, jitterRot: 0.35, jitterScale: 0.022,
|
|
145
144
|
cornerSpread: 6, cornerTravel: 4.5,
|
|
146
145
|
pressure: 0.15, pressureMod: 0.3, pooling: 0, iconTravel: 0.75,
|
|
@@ -151,7 +150,7 @@ export const SKETCH_PRESETS: Record<string, SketchSettings> = {
|
|
|
151
150
|
blurb: 'Broad translucent nib gone round twice on the same line, so the overlap darkens and the ink pools where it slows.',
|
|
152
151
|
fillTravel: 2, strokeTravel: 1.5, wobble: 56, waveform: 1.4, borderWavelength: 1.3,
|
|
153
152
|
strokeWidth: 4, doubleStroke: true, retracePass: 'copy', strokeInk: 0.52, retraceOffset: 2.2,
|
|
154
|
-
maskBlob: 115, maskOutputMin: 0.
|
|
153
|
+
maskBlob: 115, maskOutputMin: 0.42, maskOutputMax: 1, maskOctaves: 3, maskPosterize: 4, maskSoftness: 4,
|
|
155
154
|
jitterX: 3, jitterY: 3, jitterRot: 0.8, jitterScale: 0.045,
|
|
156
155
|
cornerSpread: 10, cornerTravel: 8,
|
|
157
156
|
pressure: 0.2, pressureMod: 0.25, pooling: 2, iconTravel: 1.25, iconMaskScale: 4,
|
|
@@ -162,8 +161,8 @@ export const SKETCH_PRESETS: Record<string, SketchSettings> = {
|
|
|
162
161
|
blurb: 'The fattest nib on glass. One long smooth undulation, and a veined mask that streaks the fill like a half-wiped board.',
|
|
163
162
|
fillTravel: 2.5, strokeTravel: 2.5, wobble: 90, roughness: 1, waveform: 1, borderWavelength: 1.5,
|
|
164
163
|
strokeWidth: 5.5, doubleStroke: true, retracePass: 'copy', strokeInk: 0.66, retraceOffset: 3,
|
|
165
|
-
maskGrain: 'turbulence', maskBlob: 180, maskOutputMin: 0.42, maskOutputMax:
|
|
166
|
-
maskOctaves: 1, maskPosterize: 3, maskSoftness:
|
|
164
|
+
maskGrain: 'turbulence', maskBlob: 180, maskOutputMin: 0.42, maskOutputMax: 1,
|
|
165
|
+
maskOctaves: 1, maskPosterize: 3, maskSoftness: 8,
|
|
167
166
|
jitterX: 4.5, jitterY: 4.5, jitterRot: 1.2, jitterScale: 0.07,
|
|
168
167
|
cornerSpread: 14, cornerTravel: 11,
|
|
169
168
|
pressure: 0.15, pressureMod: 0.15, pooling: 3.5, iconTravel: 1.75, iconMaskScale: 1.5,
|
|
@@ -196,7 +195,7 @@ export const SKETCH_PRESETS: Record<string, SketchSettings> = {
|
|
|
196
195
|
blurb: 'Ballpoint in a hurry. Everything loose at once: a square wave sends every edge to full travel, and the second pass lands wherever it lands.',
|
|
197
196
|
fillTravel: 3, strokeTravel: 2.25, wobble: 50, roughness: 3, waveform: 2.5,
|
|
198
197
|
strokeWidth: 2.25, doubleStroke: true, retracePass: 'reseeded', retraceOffset: 4, strokeInk: 1,
|
|
199
|
-
maskBlob: 150, maskOutputMin: 0.43, maskOutputMax: 0.95, maskOctaves: 2, maskPosterize: 2, maskSoftness:
|
|
198
|
+
maskBlob: 150, maskOutputMin: 0.43, maskOutputMax: 0.95, maskOctaves: 2, maskPosterize: 2, maskSoftness: 10,
|
|
200
199
|
jitterX: 6, jitterY: 6, jitterRot: 1.8, jitterScale: 0.1,
|
|
201
200
|
cornerSpread: 20, cornerTravel: 17,
|
|
202
201
|
pressure: 0.45, pressureMod: 0.6, pooling: 2.5, iconTravel: 2.25, iconMaskScale: 3.6,
|
|
@@ -207,7 +206,7 @@ export const SKETCH_PRESETS: Record<string, SketchSettings> = {
|
|
|
207
206
|
blurb: 'Ink that ran out. One scratchy pass that breaks up along its length, over a fill the mask has worn nearly through in patches.',
|
|
208
207
|
fillTravel: 2.25, strokeTravel: 1.75, wobble: 50, waveform: 2, borderWavelength: 0.5,
|
|
209
208
|
strokeWidth: 3.5, doubleStroke: false, strokeInk: 0.4,
|
|
210
|
-
maskBlob: 150, maskOutputMin: 0.
|
|
209
|
+
maskBlob: 150, maskOutputMin: 0.24, maskOutputMax: 0.91,
|
|
211
210
|
maskOctaves: 2, maskPosterize: 4, maskSoftness: 1.75,
|
|
212
211
|
jitterX: 5, jitterY: 5, jitterRot: 1.4, jitterScale: 0.08,
|
|
213
212
|
cornerSpread: 16, cornerTravel: 13,
|
|
@@ -215,17 +214,17 @@ export const SKETCH_PRESETS: Record<string, SketchSettings> = {
|
|
|
215
214
|
},
|
|
216
215
|
};
|
|
217
216
|
|
|
218
|
-
export const
|
|
217
|
+
export const DEFAULT_SKETCH_STYLE = 'marker';
|
|
219
218
|
|
|
220
|
-
/** Reconciled against a full
|
|
219
|
+
/** Reconciled against a full sketchstyle in both directions: a value stored before a
|
|
221
220
|
control existed picks up the default, and a value stored for a control since
|
|
222
221
|
retired is dropped. Without the drop, a stale key survives every spread and
|
|
223
222
|
makes the settings compare unequal to any baseline forever. */
|
|
224
|
-
export function
|
|
225
|
-
const base =
|
|
226
|
-
const stored = (raw ?? {}) as Partial<
|
|
223
|
+
export function hydrateSketchStyle(raw: unknown): SketchStyle {
|
|
224
|
+
const base = SKETCH_STYLES[DEFAULT_SKETCH_STYLE];
|
|
225
|
+
const stored = (raw ?? {}) as Partial<SketchStyle>;
|
|
227
226
|
const out = { ...base };
|
|
228
|
-
for (const key of Object.keys(base) as (keyof
|
|
227
|
+
for (const key of Object.keys(base) as (keyof SketchStyle)[]) {
|
|
229
228
|
if (stored[key] !== undefined) (out[key] as unknown) = stored[key];
|
|
230
229
|
}
|
|
231
230
|
// A retired option: the fill's presence belongs to the theme, not the effect.
|
|
@@ -242,8 +241,8 @@ export function hydrateSketchSettings(raw: unknown): SketchSettings {
|
|
|
242
241
|
|
|
243
242
|
/** The second pass used to sit at a distance derived from the stroke width,
|
|
244
243
|
with no dial of its own. A look stored before the dial comes back at that
|
|
245
|
-
distance rather than at whatever the fallback
|
|
246
|
-
function restoreDerivedRetrace(stored: Record<string, unknown>, out:
|
|
244
|
+
distance rather than at whatever the fallback sketchstyle happens to carry. */
|
|
245
|
+
function restoreDerivedRetrace(stored: Record<string, unknown>, out: SketchStyle): void {
|
|
247
246
|
if (stored.retraceOffset === undefined) {
|
|
248
247
|
out.retraceOffset = Number(Math.max(1.2, out.strokeWidth * 0.55).toFixed(2));
|
|
249
248
|
}
|
|
@@ -263,7 +262,7 @@ const SWING_DIALS = {
|
|
|
263
262
|
/** The pen wobble used to be stated as `frequency`, in cycles per px, which is
|
|
264
263
|
the number the filter wants and not one anybody can picture. It is a
|
|
265
264
|
wavelength in px now, the way the mask states its blobs. */
|
|
266
|
-
function convertCyclesToWavelength(stored: Record<string, unknown>, out:
|
|
265
|
+
function convertCyclesToWavelength(stored: Record<string, unknown>, out: SketchStyle): void {
|
|
267
266
|
const cycles = stored.frequency;
|
|
268
267
|
if (typeof cycles === 'number' && cycles > 0) out.wobble = Math.round(1 / cycles);
|
|
269
268
|
// The layer count was `octaves`, and its dial ran to 5. The top two moved
|
|
@@ -286,13 +285,13 @@ function convertCyclesToWavelength(stored: Record<string, unknown>, out: SketchS
|
|
|
286
285
|
of the field, so it came out either untouched or gone. It is a share of the
|
|
287
286
|
glyph now, and the old default reads as one period across the glyph, which
|
|
288
287
|
is what that size was aiming at. */
|
|
289
|
-
function convertIconTileToScale(stored: Record<string, unknown>, out:
|
|
288
|
+
function convertIconTileToScale(stored: Record<string, unknown>, out: SketchStyle): void {
|
|
290
289
|
const tile = stored.iconMaskTile;
|
|
291
290
|
if (typeof tile !== 'number') return;
|
|
292
291
|
out.iconMaskScale = Math.min(5, Math.max(0.5, Number((tile / 90).toFixed(2))));
|
|
293
292
|
}
|
|
294
293
|
|
|
295
|
-
function halveSwingDials(stored: Record<string, unknown>, out:
|
|
294
|
+
function halveSwingDials(stored: Record<string, unknown>, out: SketchStyle): void {
|
|
296
295
|
for (const [legacy, key] of Object.entries(SWING_DIALS)) {
|
|
297
296
|
const value = stored[legacy];
|
|
298
297
|
if (typeof value === 'number') out[key as 'fillTravel'] = value / 2;
|
|
@@ -302,7 +301,7 @@ function halveSwingDials(stored: Record<string, unknown>, out: SketchSettings):
|
|
|
302
301
|
/** The mask used to be a 600-unit tile painted at `maskScale` px, with the
|
|
303
302
|
coverage point buried in the hardness slope. Recover page-px blobs and
|
|
304
303
|
softness, and the levels the old slope and floor put the edge at. */
|
|
305
|
-
function convertTiledMask(stored: Record<string, unknown>, out:
|
|
304
|
+
function convertTiledMask(stored: Record<string, unknown>, out: SketchStyle): void {
|
|
306
305
|
const scale = stored.maskScale;
|
|
307
306
|
if (typeof scale !== 'number') return;
|
|
308
307
|
const freq = stored.maskFrequency;
|
|
@@ -317,7 +316,7 @@ function convertTiledMask(stored: Record<string, unknown>, out: SketchSettings):
|
|
|
317
316
|
* levels the pair put the edge between, and rescale them onto the field as it
|
|
318
317
|
* is now: stretched onto its full range before the levels see it.
|
|
319
318
|
*/
|
|
320
|
-
function convertCutToLevels(stored: Record<string, unknown>, out:
|
|
319
|
+
function convertCutToLevels(stored: Record<string, unknown>, out: SketchStyle): void {
|
|
321
320
|
const contrast = stored.maskContrast;
|
|
322
321
|
const coverage = stored.maskCoverage;
|
|
323
322
|
if (typeof contrast !== 'number' || typeof coverage !== 'number') return;
|
|
@@ -333,7 +332,7 @@ function convertCutToLevels(stored: Record<string, unknown>, out: SketchSettings
|
|
|
333
332
|
now, the palest and densest the fill gets. The same two numbers carry over:
|
|
334
333
|
a look cut between 20% and 50% comes back as a wash between 20% and 50% ink,
|
|
335
334
|
which keeps its spread and loses only the hole. */
|
|
336
|
-
function carryLevelsToOutput(stored: Record<string, unknown>, out:
|
|
335
|
+
function carryLevelsToOutput(stored: Record<string, unknown>, out: SketchStyle): void {
|
|
337
336
|
if (typeof stored.maskLevelMin === 'number') out.maskOutputMin = stored.maskLevelMin;
|
|
338
337
|
if (typeof stored.maskLevelMax === 'number') out.maskOutputMax = stored.maskLevelMax;
|
|
339
338
|
}
|
|
@@ -2,6 +2,7 @@ import { liveMovedSinceBake } from '../productionPulse';
|
|
|
2
2
|
import { openThemeSlug } from '../store/editorConfigStore';
|
|
3
3
|
import { loadThemeFromApi } from '../store/editorStore';
|
|
4
4
|
import { migrateColorsAndTypeFonts } from '../fonts/fontMigration';
|
|
5
|
+
import { openThemeSketchStyle } from '../sketch/sketchStore';
|
|
5
6
|
import type { ApplyThemeResult } from './themeService';
|
|
6
7
|
|
|
7
8
|
const CHANNEL_NAME = 'live-tokens:active-theme:v1';
|
|
@@ -42,6 +43,7 @@ export function hydrateAppliedTheme(fileName: string, result: ApplyThemeResult):
|
|
|
42
43
|
migrateColorsAndTypeFonts(colorsAndType);
|
|
43
44
|
loadThemeFromApi(colorsAndType, structuredClone(result.componentConfigs));
|
|
44
45
|
openThemeSlug.set(fileName);
|
|
46
|
+
openThemeSketchStyle(result.theme.sketchStyle);
|
|
45
47
|
liveMovedSinceBake.set(false);
|
|
46
48
|
if (typeof document !== 'undefined') {
|
|
47
49
|
document.dispatchEvent(new CustomEvent<AppliedThemeDetail>(THEME_APPLIED_EVENT, {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { AliasDiskValue, ColorsAndType } from './themeTypes';
|
|
1
|
+
import type { AliasDiskValue, ColorsAndType, Theme } from './themeTypes';
|
|
2
2
|
import { openThemeSlug } from '../store/editorConfigStore';
|
|
3
3
|
import { migrateColorsAndTypeFonts } from '../fonts/fontMigration';
|
|
4
4
|
import { loadFromFile, seedComponentsFromApi } from '../store/editorStore';
|
|
5
5
|
import { getActiveComponentConfig, type ComponentSummary } from '../components/componentConfigService';
|
|
6
6
|
import { safeFetch } from '../storage/storage';
|
|
7
7
|
import { API_BASE } from '../storage/apiBase';
|
|
8
|
+
import { hasPersistedSketchState, openThemeSketchStyle, themeSketchStyle } from '../sketch/sketchStore';
|
|
8
9
|
|
|
9
10
|
interface ListComponentsDto {
|
|
10
11
|
components: ComponentSummary[];
|
|
@@ -61,4 +62,21 @@ export async function initializeTheme(): Promise<void> {
|
|
|
61
62
|
// and CSS defaults because one request happened to fail during boot.
|
|
62
63
|
if (!componentReadFailed) seedComponentsFromApi(configs);
|
|
63
64
|
}
|
|
65
|
+
|
|
66
|
+
const active = await safeFetch<Theme>(`${API_BASE}/themes/active`);
|
|
67
|
+
// A failed fetch is not "the theme carries no sketchstyle": treating null as
|
|
68
|
+
// absent would tell the panel the look is off the theme, or hand a fresh
|
|
69
|
+
// browser a blank buffer, over a fetch that will likely succeed next time.
|
|
70
|
+
if (active) {
|
|
71
|
+
if (hasPersistedSketchState()) {
|
|
72
|
+
// The buffer already painted on the first frame; boot only learns what
|
|
73
|
+
// the theme holds so unsaved dial work reads as unsaved rather than
|
|
74
|
+
// getting silently overwritten (that overwrite is what Apply is for).
|
|
75
|
+
themeSketchStyle.set(active.sketchStyle);
|
|
76
|
+
} else {
|
|
77
|
+
// Nothing was ever recorded in this browser: the theme's value becomes
|
|
78
|
+
// the live value, the same reconciliation opening a theme performs.
|
|
79
|
+
openThemeSketchStyle(active.sketchStyle);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
64
82
|
}
|
|
@@ -7,6 +7,7 @@ import { getActiveColorsAndType } from './colorsAndTypeService';
|
|
|
7
7
|
import { broadcastAppliedTheme, hydrateAppliedTheme } from './themeDocumentSync';
|
|
8
8
|
import { CURRENT_COMPONENT_SCHEMA_VERSION } from './migrations';
|
|
9
9
|
import { THEME_SCHEMA_VERSION } from './themeTypes';
|
|
10
|
+
import { liveSketchStyle, themeSketchStyle } from '../sketch/sketchStore';
|
|
10
11
|
|
|
11
12
|
export type { ThemeFillReport };
|
|
12
13
|
|
|
@@ -144,7 +145,7 @@ function withoutLiveMarkers<T extends { _fileName?: string; _source?: unknown }>
|
|
|
144
145
|
* way out of `GET /colors-and-type/active`, which matters: the server trusts an
|
|
145
146
|
* already-embedded copy and runs no migrations over it on write.
|
|
146
147
|
*/
|
|
147
|
-
async function captureLook(): Promise<Pick<Theme, 'colorsAndType' | 'componentConfigs'>> {
|
|
148
|
+
async function captureLook(): Promise<Pick<Theme, 'colorsAndType' | 'componentConfigs' | 'sketchStyle'>> {
|
|
148
149
|
const liveColorsAndType = await getActiveColorsAndType();
|
|
149
150
|
if (!liveColorsAndType) {
|
|
150
151
|
throw new Error('No live colors and type to capture');
|
|
@@ -156,7 +157,9 @@ async function captureLook(): Promise<Pick<Theme, 'colorsAndType' | 'componentCo
|
|
|
156
157
|
const config = configs[i];
|
|
157
158
|
if (config) componentConfigs[c.name] = withoutLiveMarkers(config);
|
|
158
159
|
});
|
|
159
|
-
|
|
160
|
+
// Sketchstyle has no server door of its own (RJC 8): the live buffer, not a
|
|
161
|
+
// fetch, is the source of truth for what the dials currently say.
|
|
162
|
+
return { colorsAndType: withoutLiveMarkers(liveColorsAndType), componentConfigs, sketchStyle: liveSketchStyle() };
|
|
160
163
|
}
|
|
161
164
|
|
|
162
165
|
/**
|
|
@@ -178,6 +181,7 @@ export async function saveAsTheme(
|
|
|
178
181
|
componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION,
|
|
179
182
|
...look,
|
|
180
183
|
});
|
|
184
|
+
themeSketchStyle.set(look.sketchStyle);
|
|
181
185
|
liveMovedSinceBake.set(true);
|
|
182
186
|
await setActiveTheme(fileName);
|
|
183
187
|
}
|
|
@@ -201,6 +205,7 @@ export async function saveActiveTheme(displayName?: string): Promise<void> {
|
|
|
201
205
|
componentSchemaVersion: CURRENT_COMPONENT_SCHEMA_VERSION,
|
|
202
206
|
...look,
|
|
203
207
|
});
|
|
208
|
+
themeSketchStyle.set(look.sketchStyle);
|
|
204
209
|
liveMovedSinceBake.set(true);
|
|
205
210
|
}
|
|
206
211
|
|
|
@@ -2,6 +2,7 @@ import type { CurveAnchor } from '../../ui/curveEngine';
|
|
|
2
2
|
import type { GradientValue } from './parsers/gradient';
|
|
3
3
|
import type { Oklch } from '../palettes/oklch';
|
|
4
4
|
import type { HarmonyAxis } from '../palettes/colorHarmony';
|
|
5
|
+
import type { SketchStyle } from '../sketch/sketchStyles';
|
|
5
6
|
/** Single source of truth for the theme schema version
|
|
6
7
|
* (docs/plans/theme-completeness.md, Wave 2 step 5). It lives here, on the
|
|
7
8
|
* shipped side: `vite-plugin/` is build tooling and is not in the tarball, so
|
|
@@ -279,6 +280,10 @@ export interface Theme {
|
|
|
279
280
|
* buy no isolation. `normalizeTheme` sets this on every read; an embedded
|
|
280
281
|
* config's own `schemaVersion` (if hand-authored) is ignored and stripped. */
|
|
281
282
|
componentSchemaVersion: number;
|
|
283
|
+
/** The sketchstyle this look paints, by value. Absent means the look is
|
|
284
|
+
* crisp: presence is the on state, so there is no separate flag that can
|
|
285
|
+
* disagree with the dials beside it (RJC 1). */
|
|
286
|
+
sketchStyle?: SketchStyle;
|
|
282
287
|
/** Server-attached file-name marker. Same role as `ColorsAndType._fileName`. */
|
|
283
288
|
_fileName?: string;
|
|
284
289
|
}
|
|
@@ -11,7 +11,7 @@ The editor has four views:
|
|
|
11
11
|
tell across a page.
|
|
12
12
|
- **Components**: per-component editors. Re-Assign what tokens a component uses
|
|
13
13
|
without changing the underlying system.
|
|
14
|
-
- **
|
|
14
|
+
- **Sketchstyle**: an effect layer that redraws the page by hand. See
|
|
15
15
|
[Sketch mode](sketch-mode.md).
|
|
16
16
|
|
|
17
17
|
This page covers **Tokens**. For components, see
|
|
@@ -4,12 +4,11 @@ Sketch mode redraws your whole page as if it had been drawn by hand. Every
|
|
|
4
4
|
component keeps its own colours, spacing and corners; what changes is the line
|
|
5
5
|
they are drawn with.
|
|
6
6
|
|
|
7
|
-
It is an effect layer, not a set of token values. It
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
it goes.
|
|
7
|
+
It is an effect layer, not a set of token values. It never touches a token
|
|
8
|
+
itself, so turning it off returns every component to exactly what its tokens
|
|
9
|
+
already say. A production build never carries the drawing.
|
|
11
10
|
|
|
12
|
-
Open the **
|
|
11
|
+
Open the **Sketchstyle** view in the editor and switch **Sketch mode** on. The effect
|
|
13
12
|
applies to the page behind the editor as well as to the preview, so what you see
|
|
14
13
|
in context is what it does.
|
|
15
14
|
|
|
@@ -21,10 +20,10 @@ are pushed around one shared field of noise. Because every component samples the
|
|
|
21
20
|
same field, the whole page reads as one drawing rather than as a set of
|
|
22
21
|
separately wobbled boxes.
|
|
23
22
|
|
|
24
|
-
## The
|
|
23
|
+
## The sketchstyles
|
|
25
24
|
|
|
26
25
|
Seven looks ship with the package, and each is a complete set of dials rather
|
|
27
|
-
than a
|
|
26
|
+
than just a name:
|
|
28
27
|
|
|
29
28
|
- **Pencil.** Two graphite passes on their own seeds, so the outline disagrees
|
|
30
29
|
with itself the way a hand coming back round does.
|
|
@@ -39,9 +38,10 @@ than a style name:
|
|
|
39
38
|
- **Napkin.** Ballpoint in a hurry. Everything loose at once.
|
|
40
39
|
- **Dry marker.** Ink that ran out. One scratchy pass over a mostly eaten fill.
|
|
41
40
|
|
|
42
|
-
Pick one, then move whatever you like. **Save
|
|
43
|
-
your own, alongside the shipped seven, as a file under
|
|
44
|
-
`src/live-tokens/data/sketch-
|
|
41
|
+
Pick one, then move whatever you like. **Save as sketchstyle…** keeps your
|
|
42
|
+
dials under a name of your own, alongside the shipped seven, as a file under
|
|
43
|
+
`src/live-tokens/data/sketch-styles/`. That is a different gesture from
|
|
44
|
+
saving a theme; see "Where the settings live" below.
|
|
45
45
|
|
|
46
46
|
## The dials
|
|
47
47
|
|
|
@@ -50,8 +50,13 @@ your own, alongside the shipped seven, as a file under
|
|
|
50
50
|
few pixels off or runs it through the pen again on its own seed.
|
|
51
51
|
- **Fill.** Solid or hatched, how far the fill's edge travels, and how far each
|
|
52
52
|
instance is offset, rotated and scaled from its neighbours. **Ink coverage**
|
|
53
|
-
thins the fill with a field of blotches: set their size
|
|
54
|
-
detail,
|
|
53
|
+
thins the fill with a field of blotches: set their size and how many levels of
|
|
54
|
+
detail, then work the field as a levels control. The field always runs black
|
|
55
|
+
to white whatever the noise underneath. Steps flattens it into tones, and
|
|
56
|
+
Output squeezes the whole of it into the range the ink covers, from how pale
|
|
57
|
+
it gets at its thinnest to how dense at its fullest. Menus and tooltips are
|
|
58
|
+
drawn solid whatever the coverage dials say: they float over the page, and a
|
|
59
|
+
fill worn through in patches lets the page show through them.
|
|
55
60
|
- **Shape.** **Corner spread** rounds each corner by its own share of the dial,
|
|
56
61
|
so no two match. **Corner travel** leans the drawn box into a quadrilateral
|
|
57
62
|
with no two sides parallel. This is the dial that stops a component reading as
|
|
@@ -65,14 +70,25 @@ your own, alongside the shipped seven, as a file under
|
|
|
65
70
|
|
|
66
71
|
## Where the settings live
|
|
67
72
|
|
|
68
|
-
The
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
The sketch layer is part of the theme, the same way colors and type are.
|
|
74
|
+
**Save** in the Theme panel folds your dials into the open theme; **Load**
|
|
75
|
+
applies whatever a theme carries, and turns the effect off for a theme that
|
|
76
|
+
carries none.
|
|
77
|
+
|
|
78
|
+
Until you save, the dials sit in your browser only. The Theme panel calls
|
|
79
|
+
that state off the theme, the same word it uses for an unsaved component
|
|
80
|
+
change. The built-in **Motion Proto** theme is read-only, so Save
|
|
81
|
+
is disabled there; use **Save As** to fold the dials into a theme of your
|
|
82
|
+
own.
|
|
83
|
+
|
|
84
|
+
**Save as sketchstyle…** in the **Sketchstyle** view is a different gesture. It
|
|
85
|
+
writes a named sketchstyle to `src/live-tokens/data/sketch-styles/`, a look you
|
|
86
|
+
can pick from any theme. It never touches the open theme, and it never marks
|
|
87
|
+
the look off the theme.
|
|
72
88
|
|
|
73
89
|
Sketch mode is a tool for looking at the page, not a layer the page can ship.
|
|
74
|
-
|
|
75
|
-
production build has no sketch layer in it at all.
|
|
90
|
+
The theme records the dials, but nothing bakes them: `tokens.generated.css`
|
|
91
|
+
never sees them, and a production build has no sketch layer in it at all.
|
|
76
92
|
|
|
77
93
|
## Drawing your own elements
|
|
78
94
|
|
|
@@ -5,14 +5,19 @@ Save your work, switch between looks, and ship one to production.
|
|
|
5
5
|
## The Theme panel
|
|
6
6
|
|
|
7
7
|
The **Theme** panel at the foot of the editor sidebar holds the whole look:
|
|
8
|
-
colors, type,
|
|
9
|
-
name the look ships under, whether production is running
|
|
10
|
-
|
|
8
|
+
colors, type, a setting for every component, and the sketch layer, in one
|
|
9
|
+
file. It carries the name the look ships under, whether production is running
|
|
10
|
+
it, and **Adopt**. Three parts sit under it, each a read-out rather than a
|
|
11
|
+
file to manage.
|
|
11
12
|
|
|
12
13
|
- **Colors & Type** holds the design tokens. Components read those tokens to
|
|
13
14
|
define their appearance. It names the two faces the page is showing.
|
|
14
15
|
- **Components** counts how many components have an unsaved edit that has not
|
|
15
16
|
been saved into the theme, and opens the component editors.
|
|
17
|
+
- **Sketchstyle** names the look the theme's sketch layer carries: its label, or
|
|
18
|
+
off the theme when what's on screen no longer matches what was saved, or
|
|
19
|
+
none when the theme carries no sketch layer. It travels with the theme
|
|
20
|
+
like colors and type do, but never reaches a production build.
|
|
16
21
|
|
|
17
22
|
A theme holds its own copy of every part, so one theme can never break another.
|
|
18
23
|
|
|
@@ -21,13 +26,15 @@ A theme holds its own copy of every part, so one theme can never break another.
|
|
|
21
26
|
A theme is a document, and the editor works the way any editor does.
|
|
22
27
|
|
|
23
28
|
- **A theme** is a named JSON file in `src/live-tokens/data/themes/`. It carries
|
|
24
|
-
the whole look: the colors and type
|
|
29
|
+
the whole look: the colors and type, a setting for every component, and the
|
|
30
|
+
sketch layer.
|
|
25
31
|
- **The open theme** is the one the editor is working on, named in
|
|
26
32
|
`themes/_active.json`. One at a time.
|
|
27
33
|
- **Your unsaved edits** are what the page shows right now. The editor keeps
|
|
28
|
-
them in your browser as you work
|
|
29
|
-
one slot
|
|
30
|
-
|
|
34
|
+
them in your browser as you work, writing most parts to a buffer,
|
|
35
|
+
`_working.json`, one slot each; the sketch layer has no buffer and stays
|
|
36
|
+
live in the browser until you save. **Save** captures all of it into the
|
|
37
|
+
open theme.
|
|
31
38
|
- **The production theme** is the one your site ships, named in
|
|
32
39
|
`themes/_production.json`. **Adopt** changes it; saving a preset in the Theme
|
|
33
40
|
Picker performs that Adopt for you.
|
|
@@ -96,29 +103,31 @@ to it, and the editor never overwrites it, so start your own with **Save As**.
|
|
|
96
103
|
## Switching
|
|
97
104
|
|
|
98
105
|
**Load**—or clicking the active theme's name—opens the Theme Picker. Picking a
|
|
99
|
-
theme shows it on the page as a preview with nothing written to disk,
|
|
100
|
-
try each look and compare. **Save** in that window
|
|
101
|
-
theme in one step: the active pointer changes,
|
|
102
|
-
works on it, and production ships it. **Cancel**
|
|
103
|
-
|
|
106
|
+
theme shows it on the page as a preview with nothing written to disk, sketch
|
|
107
|
+
layer included, so you can try each look and compare. **Save** in that window
|
|
108
|
+
opens and adopts the previewed theme in one step: the active pointer changes,
|
|
109
|
+
the buffers clear, the editor works on it, and production ships it. **Cancel**
|
|
110
|
+
returns you to where you were, unsaved sketch dials included. Previewing alone
|
|
111
|
+
never changes what your site ships.
|
|
104
112
|
|
|
105
113
|
**Colors and type only. Keep my shapes.** narrows the load to the palette and
|
|
106
|
-
the fonts: your component settings stay as they are and
|
|
107
|
-
stays open. Saved colors and type files are listed
|
|
108
|
-
type*, and picking one is always that narrower
|
|
114
|
+
the fonts: your component settings and your sketch layer stay as they are, and
|
|
115
|
+
the theme you have open stays open. Saved colors and type files are listed
|
|
116
|
+
there too, marked *colors & type*, and picking one is always that narrower
|
|
117
|
+
load.
|
|
109
118
|
|
|
110
119
|
## Shipping
|
|
111
120
|
|
|
112
|
-
**Adopt**, in the Theme panel, is the "ship it" step
|
|
113
|
-
|
|
121
|
+
**Adopt**, in the Theme panel, is the "ship it" step. It saves the open theme,
|
|
122
|
+
then bakes the colors and type plus every component the theme carries into
|
|
114
123
|
`src/live-tokens/data/tokens.generated.css`, which your build bundles alongside
|
|
115
|
-
`tokens.css
|
|
116
|
-
regenerate to match. The line under the theme name
|
|
117
|
-
running this theme.
|
|
124
|
+
`tokens.css`. The sketch layer is the one part of the theme Adopt never bakes:
|
|
125
|
+
it stays a preview. Fonts regenerate to match. The line under the theme name
|
|
126
|
+
says whether production is running this theme.
|
|
118
127
|
|
|
119
128
|
Production is one saved theme, so nothing else publishes. Trying a look, moving
|
|
120
129
|
a token, saving a theme: all of it leaves the generated CSS alone until you
|
|
121
|
-
Adopt. A component editor's Adopt runs the same
|
|
130
|
+
Adopt. A component editor's Adopt runs the same save-then-bake step, because a
|
|
122
131
|
component never ships alone. Adopting while Motion Proto is open saves your look
|
|
123
132
|
as a theme of your own first, since the built-in one is read-only.
|
|
124
133
|
|
|
@@ -20,15 +20,17 @@ src/live-tokens/data/
|
|
|
20
20
|
default.json Button's shipped settings, derived at boot
|
|
21
21
|
_working.json unsaved Button edits
|
|
22
22
|
my-button.json a preset you saved from the Button editor
|
|
23
|
+
sketch-styles/
|
|
24
|
+
my-look.json a sketchstyle saved from the Sketchstyle view
|
|
23
25
|
tokens.generated.css the baked CSS your production build ships
|
|
24
26
|
src/system/styles/
|
|
25
27
|
tokens.css your token vocabulary, hand-authored, never written
|
|
26
28
|
fonts.css font imports, rewritten when you Adopt
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
A saved theme carries the whole look by value: the colors and type
|
|
30
|
-
setting for every component. It depends on no other
|
|
31
|
-
anything else never breaks it.
|
|
31
|
+
A saved theme carries the whole look by value: the colors and type, a
|
|
32
|
+
setting for every component, and the sketch layer. It depends on no other
|
|
33
|
+
file, so deleting anything else never breaks it.
|
|
32
34
|
|
|
33
35
|
## What writes when
|
|
34
36
|
|
|
@@ -36,8 +38,10 @@ anything else never breaks it.
|
|
|
36
38
|
edits in the browser as you work and writes them to the `_working.json`
|
|
37
39
|
buffers when you save a component. When the Theme panel finds several dirty
|
|
38
40
|
components, **Save all** writes those buffers together.
|
|
39
|
-
- **Save** captures the buffers into the open theme's file
|
|
40
|
-
|
|
41
|
+
- **Save** captures the buffers into the open theme's file, along with the
|
|
42
|
+
sketch layer, which has no buffer of its own and lives only in the browser
|
|
43
|
+
until Save writes it. That file is the durable copy of your look; matching
|
|
44
|
+
buffers are then removed.
|
|
41
45
|
- **Load** clears the buffers and points `themes/_active.json` at the theme you
|
|
42
46
|
picked. Live reads fall through to that file. Nothing else changes, so trying
|
|
43
47
|
looks is free and ordinary switching changes only the pointer.
|