@motion-proto/live-tokens 0.58.0 → 0.60.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 (42) hide show
  1. package/.claude/skills/live-tokens-generate-theme/SKILL.md +15 -14
  2. package/.claude/skills/live-tokens-generate-theme/references/mood-vocabulary.md +43 -0
  3. package/.claude/skills/live-tokens-generate-theme/references/style-vocabulary.md +35 -0
  4. package/CHANGELOG.md +89 -0
  5. package/dist-plugin/generateColorsAndType/index.cjs +4 -3
  6. package/dist-plugin/generateColorsAndType/index.js +4 -3
  7. package/package.json +1 -1
  8. package/src/editor/component-editor/ImageLightboxEditor.svelte +11 -2
  9. package/src/editor/component-editor/MenuSelectEditor.svelte +2 -1
  10. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +5 -3
  11. package/src/editor/component-editor/scaffolding/types.ts +6 -0
  12. package/src/editor/core/sketch/sketchLayer.ts +16 -7
  13. package/src/editor/core/sketch/sketchPresets.ts +8 -8
  14. package/src/editor/core/themes/parsers/shadow.ts +12 -4
  15. package/src/editor/docs/content/sketch-mode.md +2 -1
  16. package/src/editor/docs/content.generated.ts +1 -1
  17. package/src/editor/overlay/ColumnsOverlay.svelte +3 -0
  18. package/src/editor/ui/ColorEditPanel.svelte +13 -9
  19. package/src/editor/ui/PaletteEditor.svelte +11 -3
  20. package/src/editor/ui/UIInlineEditActions.svelte +97 -0
  21. package/src/editor/ui/UIPaletteSelector.svelte +14 -6
  22. package/src/live-tokens/data/colors-and-type/autumn.json +5 -5
  23. package/src/live-tokens/data/colors-and-type/default.json +5 -5
  24. package/src/live-tokens/data/colors-and-type/halloween.json +5 -5
  25. package/src/live-tokens/data/colors-and-type/midnight-study.json +5 -5
  26. package/src/live-tokens/data/colors-and-type/ocean.json +5 -5
  27. package/src/live-tokens/data/colors-and-type/royal-velvet.json +5 -5
  28. package/src/live-tokens/data/colors-and-type/sketchy.json +5 -5
  29. package/src/live-tokens/data/colors-and-type/spring-meadow.json +5 -5
  30. package/src/live-tokens/data/colors-and-type/sunset.json +5 -5
  31. package/src/live-tokens/data/themes/autumn.json +6 -6
  32. package/src/live-tokens/data/themes/halloween.json +6 -6
  33. package/src/live-tokens/data/themes/midnight-study.json +6 -6
  34. package/src/live-tokens/data/themes/ocean.json +6 -6
  35. package/src/live-tokens/data/themes/royal-velvet.json +6 -6
  36. package/src/live-tokens/data/themes/sketchy.json +6 -6
  37. package/src/live-tokens/data/themes/spring-meadow.json +6 -6
  38. package/src/live-tokens/data/themes/sunset.json +6 -6
  39. package/src/live-tokens/data/tokens.generated.css +5 -5
  40. package/src/system/components/ImageLightbox.svelte +26 -2
  41. package/src/system/components/MenuSelect.svelte +1 -1
  42. package/src/system/styles/tokens.css +5 -5
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  name: live-tokens-generate-theme
3
- description: Generate a complete live-tokens theme (color, type, and geometry) from a natural-language mood brief. Chooses 10 OKLCH seeds and runs the packaged generator, which enforces AA contrast, then carries the same brief into a font pairing and a geometry through the sibling skills. Use whenever the user asks for a theme, a look, a vibe, a brand feel, a color scheme, or a palette, by mood, season, holiday, or hue, even if they only mention color: make me a bright and cheerful theme, a dark moody night theme, a St. Patrick's Day theme in green and gold, a Christmas look, something red-based, warmer, more contrast, calmer. Not for a single token (use the editor), for type alone (live-tokens-pair-fonts), or for geometry alone (live-tokens-adjust-geometry).
3
+ description: Generate a complete live-tokens theme (color, type, and geometry) from a natural-language brief. Chooses 10 OKLCH seeds and runs the packaged generator, which enforces AA contrast, then carries the same brief into a font pairing and a geometry through the sibling skills. Use whenever the user asks for a theme, a look, a vibe, a brand feel, a color scheme, or a palette, by mood, style, era, season, holiday, or hue, even if they only mention color: make me a bright and cheerful theme, a dark moody night theme, a brutalist look, mid-century modern, something Swiss and minimal, make it feel like a terminal, cyberpunk neon, an editorial magazine feel, a St. Patrick's Day theme in green and gold, something red-based, warmer, more contrast, calmer. Not for a single token (use the editor), for type alone (live-tokens-pair-fonts), or for geometry alone (live-tokens-adjust-geometry).
4
4
  ---
5
5
 
6
- # Generating a theme from a mood brief
6
+ # Generating a theme from a brief
7
7
 
8
8
  A theme is three decisions made from one brief: color, type, and geometry. This skill owns the color decision directly and delegates the other two, so the whole look comes from the same reading of the brief. Never hand-author theme JSON and never edit the data tree directly.
9
9
 
10
10
  ## Workflow
11
11
 
12
- 1. Read the brief once and name its voice in a sentence: the mood, the hue family, the scheme, and the type and geometry that mood implies. Everything below keys off that sentence.
12
+ 1. Read the brief once and name its voice in a sentence: the mood, the hue family, the scheme, and the type and geometry that mood implies. Everything below keys off that sentence. Then read the anchor reference that matches the brief (feeling, idiom, or occasion; see below) before seeding: each entry fixes all three decisions together and overrides the generic defaults here.
13
13
  2. Translate the brief into a seed file using the framework below. Write it to `scratch/theme-brief.json`.
14
14
  3. Run `npx live-tokens generate-theme scratch/theme-brief.json`. It writes `themes/<slug>.json`, opens that theme, and prints a contrast report. Auto-corrections are fine. Unmet floors (exit 1) mean the seeds themselves are unworkable; each failure line names the seed to change, usually by raising its lightness or cutting its chroma. Fix the brief and re-run; the same name overwrites. Regeneration replaces that theme's whole color state, including palette edits made in the editor since the last run, so say so once when iterating.
15
15
  4. Invoke **live-tokens-pair-fonts** with the same voice. Skip only when the user asked for colors specifically and said to leave the type alone.
@@ -86,16 +86,9 @@ Also:
86
86
 
87
87
  ## Mood dials
88
88
 
89
- Pleasantness rises with lightness (strongly) and saturation (weakly); energy rises with saturation; drama rises with dark plus saturated.
89
+ Pleasantness rises with lightness (strongly) and saturation (weakly); energy rises with saturation; drama rises with dark plus saturated. Dominance, the third axis, is carried by surface contrast, type weight, and tightness rather than by color at all.
90
90
 
91
- | Brief says | Dials |
92
- |---|---|
93
- | cheerful, bright, playful | light; Brand and Accent L 0.7 to 0.9, C 0.15 to 0.22; warm hues 40 to 140 (yellow is the strongest joy hue) |
94
- | calm, serene, soft | light; C 0.03 to 0.08 on everything chromatic; cool hues 140 to 260 |
95
- | energetic, bold | C 0.18 or more at L 0.55 to 0.65; red, orange, magenta |
96
- | dark, moody, dramatic, luxurious | dark; Canvas L 0.15 to 0.25; purple, deep blue, crimson; working accents stay light per the dark transform, with dark saturated color saved for one or two moments |
97
- | professional, trustworthy | blue 230 to 265, C 0.08 to 0.15; everything else muted |
98
- | warm / cool | hues 20 to 110 plus pink 290 to 360 / hues 140 to 290 |
91
+ That is the whole mechanism, and one dial moves without a reference: warm is hues 20 to 110 plus pink 290 to 360, cool is 140 to 290. For a brief that names a feeling, read `references/mood-vocabulary.md` instead of guessing the dial settings; each entry places the emotion on the three axes and gives the color, type, and geometry together.
99
92
 
100
93
  Avoid mid-lightness yellow-green (H 100 to 120 at L 0.5 to 0.7, C about 0.1) unless the brief asks for olive or toxic.
101
94
 
@@ -122,9 +115,15 @@ Hue offsets from Brand: complementary +180; split-complementary +150/+210; triad
122
115
 
123
116
  Shadow opacity derives from Canvas lightness and re-derives on every run, so there is nothing to choose. When shadows read heavy or muddy, raise the Canvas seed's L.
124
117
 
125
- ## Named themes
118
+ ## Anchor references
119
+
120
+ Read the matching reference before seeding, and apply the bands above on top of it.
126
121
 
127
- A holiday or season brief is a statement brief: commitment level 2 or 3, with the named color on the ground rather than only on the buttons. Read `references/named-themes.md` for the OKLCH anchors of Christmas, Halloween, St. Patrick's, Ocean, Sunset, Autumn, and Spring before seeding one.
122
+ - `references/mood-vocabulary.md` covers feelings: joyful, playful, optimistic, confident, serene, tender, cozy, earthy, clinical, wistful, contemplative, urgent, tense, defiant, melancholy, somber, ominous, austere. It opens with the valence, energy, and dominance axes, so a feeling it does not list still places on them.
123
+ - `references/style-vocabulary.md` covers named idioms, eras, and genres: Swiss, Bauhaus, mid-century, art deco, terminal, cyberpunk, vaporwave, Y2K, blueprint, Scandinavian, Japandi, cottagecore, editorial, newsprint, riso, corporate, brutalist, Memphis, industrial. Each entry fixes color, type, and geometry as one set, so hand its Type and Geometry columns to the sibling skills verbatim.
124
+ - `references/named-themes.md` covers holidays, seasons, and natural scenes: Christmas, Halloween, St. Patrick's, Ocean, Sunset, Autumn, Spring. A holiday or season brief is a statement brief: commitment level 2 or 3, with the named color on the ground rather than only on the buttons.
125
+
126
+ Most briefs hit the first file. A brief that names no feeling, idiom, or occasion at all takes the bands above and the geometry table below.
128
127
 
129
128
  ## Geometry from the voice
130
129
 
@@ -137,6 +136,8 @@ The geometry lives in radius, padding, gap, and border width, and `live-tokens-a
137
136
  | technical, dense, systematic | tighter spacing, small radius, square corners on containers |
138
137
  | calm, minimal | leave geometry alone unless the brief says otherwise |
139
138
 
139
+ This table is the fallback. When the brief matched an entry in the mood or style reference, take the geometry from that entry instead: it is tuned to the same reading the color came from, and a style's geometry is often targeted rather than global.
140
+
140
141
  ## What each step writes
141
142
 
142
143
  Color writes `themes/<slug>.json` and opens it. Type and geometry write the unsaved buffers, which the page already runs. One Save in the editor keeps all three; Adopt ships them. Component aliases and gradients carry forward from the live look into a generated theme; user-tuned gradients survive, stock ones rebuild from the new families.
@@ -0,0 +1,43 @@
1
+ # Mood vocabulary: feelings
2
+
3
+ Read this when the brief names a feeling. Entries are starting points, not seeds
4
+ to copy: apply the chroma budget, per-role bands, and canvas commitment rules
5
+ from SKILL.md on top of them.
6
+
7
+ Three axes place any feeling, including one the table omits:
8
+
9
+ | Axis | Reads as | Carried by |
10
+ |---|---|---|
11
+ | Valence | pleasant against unpleasant | lightness, above everything else |
12
+ | Energy | aroused against calm | chroma, and hue distance on screen |
13
+ | Dominance | assertive against yielding | surface contrast, type weight, border weight, tightness |
14
+
15
+ Valence and energy set the quadrant, and the table runs in quadrant order:
16
+ pleasant-aroused, pleasant-calm, unpleasant-aroused, unpleasant-calm. Dominance
17
+ separates confident from gentle inside one quadrant and lives almost entirely
18
+ outside color.
19
+
20
+ Energy spent on the ground tier fights the contrast gate, so keep chroma on the
21
+ garnish. And a low-valence, low-energy brief taken literally reads as broken
22
+ rather than sad, which is why every dark entry below holds one moment of color.
23
+
24
+ | Brief | Anchors (L, C, H) | Type | Geometry |
25
+ |---|---|---|---|
26
+ | Joyful, exuberant, energetic | butter canvas (0.95, 0.05, 90), Brand (0.80, 0.17, 75), coral Accent (0.72, 0.18, 20), green Special (0.75, 0.16, 145); analogous warm. Yellow stays yellow only above L 0.80 | dynamic sans in both slots | radius +2, padding +1, pill buttons |
27
+ | Playful, whimsical | tinted canvas (0.93, 0.05, 330), Special at full chroma; tetradic, the rare brief that wants four hue families | expressive display over a plain workhorse | pill buttons, rounder cards, wide gaps |
28
+ | Optimistic, hopeful | sky canvas (0.94, 0.03, 220), Brand (0.62, 0.14, 200), warm yellow Accent (0.85, 0.14, 90); complementary across the warm/cool line, which reads as sunrise rather than sky | humanist sans, low contrast | radius +1 |
29
+ | Confident, bold | canvas (0.90, 0.07, 250), Brand C 0.18 at L 0.55, wide L range between surfaces. High dominance is the point | heavy rational display, plain body | radius -1, borders +1, tight gaps |
30
+ | Serene, tranquil | cool canvas (0.95, 0.03, 200), nothing above C 0.08, analogous 160 to 240 | dynamic sans, one family | soft radius, padding +1, no borders |
31
+ | Tender, gentle, romantic | blush canvas (0.95, 0.03, 20), rose Brand (0.70, 0.08, 10), sage Accent (0.68, 0.06, 150); narrow L range, light weights | soft serif display over humanist sans | rounder, hairline borders |
32
+ | Cozy, comforting | amber-cream canvas (0.92, 0.05, 75), rust Brand (0.55, 0.12, 40), neutrals at H 60; nothing cool on screen | dynamic serif over dynamic sans | radius +1, padding +1 |
33
+ | Wistful, nostalgic, vintage, faded | faded canvas (0.92, 0.03, 70), every chromatic seed capped at C 0.10, hues warm and close. The feeling is chroma withheld, not darkness | rational serif display, quiet body | unchanged, hairline rules |
34
+ | Earthy, grounded, natural | canvas (0.90, 0.05, 90) at commitment level 2, hues 30 to 140 at C 0.06 to 0.14, neutrals H 60 to 80; no magenta, no cyan, nothing over C 0.16 | dynamic serif over humanist sans | radius +1, padding +1 |
35
+ | Clinical, sterile, precise | near-white canvas at C 0.01, the one brief an untinted ground suits; one cool Brand 200 to 260 at C 0.10; statuses carry the only other color | one neo-grotesque, tightly set | radius sm, tight gaps, hairline borders |
36
+ | Contemplative, focused | canvas (0.94, 0.015, 250) or its dark twin (0.20, 0.02, 250), one cool Brand at C 0.10, almost no other hue | one quiet superfamily | padding +1, minimal borders |
37
+ | Urgent, alarming | ground held near-neutral so the alarm lands, red Brand (0.58, 0.22, 27) given real area, Warning and Danger on one shared L | condensed grotesque, heavy | radius -2, borders +2, tight padding |
38
+ | Tense, anxious | an uncomfortable ground, (0.88, 0.04, 105) light or (0.22, 0.03, 280) dark, plus a near-complementary pair that vibrates with one side toned down | neo-grotesque, tightly set | tight gaps, radius sm |
39
+ | Defiant, rebellious, loud | near-black canvas (0.15, 0.01, 0), one acid hue (0.85, 0.20, 120), nothing else chromatic | heavy display; a mono body works | radius none, borders +3 |
40
+ | Melancholy, moody, sad | dark; canvas (0.22, 0.03, 250), chromatic seeds C 0.06 to 0.10 at L 0.72 to 0.80, blue through violet, with Accent held at C 0.14 as the moment of color | rational serif display over quiet sans | padding +1, hairline borders |
41
+ | Somber, grave, mournful | near-neutral dark canvas (0.18, 0.01, 260), one desaturated Brand, gradient off | rational serif in both slots | sharp, tight gaps, hairline rules |
42
+ | Ominous, dramatic, haunted | dark canvas (0.15, 0.04, 300), one hot accent (0.75, 0.16, 30) used sparingly, canvasGradient on | heavy high-contrast display | sharp, borders +2 |
43
+ | Austere, severe, cold | monochrome; canvas at either L extreme at C 0.01 or below, one low-chroma Brand, muted statuses. Highest dominance, lowest energy | one rational family | radius none, tight padding, hairline borders |
@@ -0,0 +1,35 @@
1
+ # Style vocabulary: idioms, eras, and genres
2
+
3
+ Read this when the brief names a design idiom, a decade, or a genre. A mood
4
+ moves dials; an idiom sets constraints, and it overrides the generic defaults in
5
+ SKILL.md: the polished-UI Accent at 45% of Brand's chroma is right for a vague
6
+ brief and wrong for Bauhaus.
7
+
8
+ Entries are starting points, not seeds to copy: apply the chroma budget,
9
+ per-role bands, and canvas commitment rules on top of them. Riso, Memphis, and
10
+ brutalist break the chroma budget on purpose. Break it in the one layer the
11
+ style is about and hold the rest of the ground tier down.
12
+
13
+ The table runs modernist, digital, quiet, print, expressive.
14
+
15
+ | Brief | Anchors (L, C, H) | Type | Geometry |
16
+ |---|---|---|---|
17
+ | Swiss, International | near-white canvas (0.97, 0.01, 0), or true black for the poster reading; one red Brand (0.55, 0.22, 27) as the only hue on screen; Neutral at C 0.005, untinted on purpose; monochromatic | one rational neo-grotesque across both slots | radius none, tight gaps, hairline borders |
18
+ | Bauhaus | paper canvas (0.95, 0.02, 85) under primaries at full commitment: red (0.58, 0.21, 27), blue (0.48, 0.20, 264), yellow (0.86, 0.17, 90); triadic | geometric sans, heavy display | radius none on containers, radius full on buttons alone, so the circle reads as a decision |
19
+ | Mid-century modern | canvas (0.90, 0.05, 75), mustard (0.75, 0.13, 85), teal (0.55, 0.10, 195), burnt orange (0.60, 0.15, 45), walnut neutrals H 60; nothing over C 0.16; compound | dynamic sans, or a geometric display over a dynamic body | radius +1 to +2, padding +1, no borders |
20
+ | Art deco, opulent, luxurious | near-black canvas (0.20, 0.02, 280), gold (0.78, 0.13, 88), jade (0.60, 0.10, 165); dark, one metallic accent, everything else grey | rational high-contrast serif display, quiet body | sharp, padding +1, thin borders |
21
+ | Terminal, phosphor | canvas (0.16, 0.01, 150), phosphor green Brand (0.80, 0.16, 145), amber Accent (0.80, 0.13, 80); monochromatic, dark, gradient off | mono in both slots | radius none, tight padding, a 1px border on everything |
22
+ | Cyberpunk, neon noir, futuristic | canvas (0.18, 0.04, 300), magenta Brand (0.78, 0.18, 330), cyan Accent (0.82, 0.12, 200); complementary, dark, canvasGradient on for the glow | wide geometric display, neutral body | sharp, tight gaps |
23
+ | Vaporwave | sunset canvas (0.88, 0.06, 330), pink (0.72, 0.16, 350), cyan (0.80, 0.11, 205), lilac Special; light, gradient on | wide retro display, serif welcome | rounder, airier |
24
+ | Y2K, bubble | chrome canvas (0.96, 0.015, 240), electric blue Brand (0.62, 0.18, 255), lime Accent (0.85, 0.17, 130) | geometric sans, heavy display | radius full on buttons with the padding a pill needs, generous spacing |
25
+ | Blueprint | canvas (0.35, 0.07, 245), pale rules (0.90, 0.02, 240), one warm accent (0.75, 0.14, 60); dark | mono, or a technical grotesque; a mono body works here and almost nowhere else | radius none, 1px borders, tight grid |
26
+ | Scandinavian, hygge | chalk canvas (0.96, 0.012, 70), sage Brand (0.60, 0.06, 150), clay Accent (0.70, 0.08, 40); nothing above C 0.10; analogous | dynamic sans on both sides | soft radius, padding +1, hairline borders |
27
+ | Japandi, wabi-sabi | unbleached paper canvas (0.93, 0.025, 80), ink Brand (0.35, 0.02, 250), one earth Accent (0.62, 0.09, 45); near-monochrome | rational serif display over a quiet humanist body | radius sm, padding +2, no borders |
28
+ | Cottagecore, botanical | cream canvas (0.94, 0.04, 85), moss (0.55, 0.10, 135), dusty rose (0.70, 0.09, 15), butter (0.85, 0.11, 95); warm analogous | dynamic serif display over humanist sans | rounder, airier |
29
+ | Editorial, magazine | paper canvas (0.97, 0.015, 85), ink neutrals, one strong Brand (0.50, 0.18, 20) carried by rules and pull quotes | high-contrast serif display over a text serif or humanist sans | sharp, padding +1, hairline rules |
30
+ | Newsprint, broadsheet | grey-warm canvas (0.91, 0.02, 80), near-black ink, nothing chromatic above C 0.10 | rational serif in both slots | radius none, tight gaps, hairline rules |
31
+ | Risograph, zine | paper canvas (0.94, 0.03, 80) with two flat spot inks, fluoro pink (0.68, 0.22, 5) and blue (0.52, 0.18, 260); complementary, no midtones between them | expressive display over a plain workhorse | radius none, borders +2 |
32
+ | Corporate, professional, trustworthy | canvas (0.97, 0.012, 250), navy Brand (0.48, 0.12, 255), teal Accent (0.60, 0.09, 195), conventional statuses | rational sans body with a rational serif or same-family display | leave it alone |
33
+ | Brutalist | pure canvas, (0.98, 0, 0) or (0.15, 0, 0), with Neutral at C 0, untinted because that is the point; one alarming Brand (0.58, 0.24, 27) | neo-grotesque or mono at heavy weight | radius none, borders +2 to +3, tight padding |
34
+ | Memphis, postmodern | pastel canvas (0.95, 0.03, 60) carrying full-chroma primaries and a hot pink Special (0.70, 0.20, 350); tetradic or square, four hue families on purpose | geometric display, plain body | targeted rather than global: pill buttons against radius-none cards |
35
+ | Industrial, workshop, gritty | concrete canvas (0.88, 0.008, 250), or (0.22, 0.01, 250) dark, safety orange Brand (0.68, 0.18, 50), steel neutrals | condensed grotesque display over a plain body | radius sm, thick borders, tight padding |
package/CHANGELOG.md CHANGED
@@ -1,5 +1,94 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.60.0 — A brief names a feeling or an idiom
4
+
5
+ ### Added
6
+
7
+ - **The theme skill reads two new anchor references: feelings and idioms.**
8
+ `live-tokens-generate-theme` turned a brief into seeds through a six-row dial
9
+ table. It covered "cheerful" and "calm" and left everything else to guesswork,
10
+ so "Bauhaus", "terminal" and "wistful" all landed on the same polished-UI
11
+ defaults. `references/mood-vocabulary.md` places eighteen feelings on valence,
12
+ energy and dominance axes, and says which of the three color carries at all.
13
+ `references/style-vocabulary.md` fixes color, type and geometry together for
14
+ nineteen idioms, eras and genres, and names the one layer each is allowed to
15
+ break the chroma budget in. An entry overrides the generic bands, and its Type
16
+ and Geometry columns go verbatim to `live-tokens-pair-fonts` and
17
+ `live-tokens-adjust-geometry`, so all three decisions come from one reading of
18
+ the brief. `named-themes.md` keeps the holidays and seasons, and the three
19
+ files are now read as one set of anchors.
20
+
21
+ ### Changed
22
+
23
+ - **The menu panel is mostly opaque, and cannot be dialled below 90%.** At 85%
24
+ the page behind a dropdown ghosted through it: a headline or a rule crossing
25
+ the panel read as content inside the list, and the panel stopped looking like
26
+ a solid thing you could touch. `--menuselect-menu-surface` now defaults to 95%
27
+ of `--surface-neutral-lower`, enough translucency to sit above the page
28
+ without competing with it. The floor is declared on the token itself, so the
29
+ opacity slider stops at 90 and the `None` chip is gone from that one picker.
30
+ Any component, shipped or consumer-authored, can declare `minOpacity` on a
31
+ colour token; the registry contract test holds its shipped default above the
32
+ line.
33
+
34
+ ### Fixed
35
+
36
+ - **Sketch mode leaves the editor's own chrome alone.** The layer draws glyphs
37
+ and inline SVG with a filter and an ink mask, and chrome inside the scope
38
+ turns both off by declaring `--sketch-icon-off`. Only the filter read it: the
39
+ mask went on regardless, so the overlay bar's icons came out blotched by an
40
+ effect meant for the page behind it. The mask now answers to the same one
41
+ declaration, and the column guides, which never opted out at all, now do.
42
+
43
+ ## 0.59.0 — The shadow traces the art
44
+
45
+ ### Added
46
+
47
+ - **`shadow` on `ImageLightbox`.** The tile cast its shadow from its own
48
+ rectangle and offered no way out, so a PNG or WebP with transparency sat on a
49
+ phantom slab: the shadow drew the box, and the box was not the picture. Art
50
+ cut out against the page — a device mockup, a logo, a chart on no background —
51
+ showed a hard rounded rectangle behind it. `shadow` now picks where that
52
+ shadow falls. `box` is the old behaviour and stays the default. `content`
53
+ casts from the image's own alpha, so the shadow follows the silhouette.
54
+ `none` drops it. All three read the one `--imagelightbox-tile-shadow` the
55
+ theme already sets — the mode moves the shadow, it does not restyle it. The
56
+ open modal keeps its shadow only under `box`: it casts against a near-opaque
57
+ scrim where nothing reads, and filtering the pan-and-zoom surface would
58
+ repaint it every frame.
59
+
60
+ ### Changed
61
+
62
+ - **A shadow token writes its spread slot only when the spread is set.**
63
+ `--shadow-md` read `3px 3px 6px 0px hsla(…)`, and that fourth length was the
64
+ reason a shadow token could not also be a filter: `drop-shadow()` has no
65
+ spread slot, so the declaration was invalid and dropped. Every shipped theme
66
+ carries a zero spread, so the slot said nothing and cost the scale half its
67
+ reach. The zero-spread form is now three lengths, which `box-shadow` reads
68
+ identically and `drop-shadow()` accepts — one token, both properties, no
69
+ second scale to keep in step. The spread control is untouched: dial one and
70
+ the token grows its fourth length back, still a shadow but no longer a
71
+ filter, so a theme with a real spread has no shadow under `content`.
72
+ Four-length values still parse, and normalise on the next save.
73
+
74
+ ### Fixed
75
+
76
+ - **The colour edit panel wears editor chrome again.** Its confirm, cancel and
77
+ "Remove override" buttons were the shipped `Button` and `InlineEditActions`
78
+ components, which read the theme's own tokens — so the controls you edit a
79
+ theme *with* restyled themselves as you edited, and a saturated success or
80
+ danger palette turned three small chrome buttons into slabs. They are now
81
+ `UIInlineEditActions` and `UIPillButton`, drawn from the theme-immune `--ui-*`
82
+ scale like the rest of the editor. `UIInlineEditActions` is the editor's
83
+ check/cross pair, sized to the 1.5rem chrome buttons it sits beside.
84
+ - **The base colour panel offers a confirm and a cancel.** Opening a palette's
85
+ editor showed the base panel with no way to accept or discard — every slider
86
+ wrote straight through, and only the docked step panel carried the pair. The
87
+ panel now shows both, greyed until an edit is pending; the first touch of a
88
+ slider, a hex field or the eyedropper opens the same session the step panel
89
+ uses, so cancel restores the colour the panel opened with and confirm keeps
90
+ the drag as one undo step.
91
+
3
92
  ## 0.58.0 — The sketch layer states its contract
4
93
 
5
94
  ### Added
@@ -719,15 +719,16 @@ function computeAngleDistance(x, y) {
719
719
  return { angle: Math.round(angle), distance };
720
720
  }
721
721
  function shadowTokenCss(t) {
722
- return `${t.x}px ${t.y}px ${t.blur}px ${t.spread}px hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
722
+ const spread = t.spread ? `${t.spread}px ` : "";
723
+ return `${t.x}px ${t.y}px ${t.blur}px ${spread}hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
723
724
  }
724
725
  function parseShadowCss(variable, raw) {
725
- const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(-?\d+)px\s+hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
726
+ const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(?:(-?\d+)px\s+)?hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
726
727
  if (!m) return null;
727
728
  const x = parseInt(m[1], 10);
728
729
  const y = parseInt(m[2], 10);
729
730
  const blur = parseInt(m[3], 10);
730
- const spread = parseInt(m[4], 10);
731
+ const spread = m[4] === void 0 ? 0 : parseInt(m[4], 10);
731
732
  const hue = Math.round(parseFloat(m[5]));
732
733
  const saturation = Math.round(parseFloat(m[6]));
733
734
  const lightness = Math.round(parseFloat(m[7]));
@@ -105,15 +105,16 @@ function computeAngleDistance(x, y) {
105
105
  return { angle: Math.round(angle), distance };
106
106
  }
107
107
  function shadowTokenCss(t) {
108
- return `${t.x}px ${t.y}px ${t.blur}px ${t.spread}px hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
108
+ const spread = t.spread ? `${t.spread}px ` : "";
109
+ return `${t.x}px ${t.y}px ${t.blur}px ${spread}hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
109
110
  }
110
111
  function parseShadowCss(variable, raw) {
111
- const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(-?\d+)px\s+hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
112
+ const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(?:(-?\d+)px\s+)?hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
112
113
  if (!m) return null;
113
114
  const x = parseInt(m[1], 10);
114
115
  const y = parseInt(m[2], 10);
115
116
  const blur = parseInt(m[3], 10);
116
- const spread = parseInt(m[4], 10);
117
+ const spread = m[4] === void 0 ? 0 : parseInt(m[4], 10);
117
118
  const hue = Math.round(parseFloat(m[5]));
118
119
  const saturation = Math.round(parseFloat(m[6]));
119
120
  const lightness = Math.round(parseFloat(m[7]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.58.0",
3
+ "version": "0.60.0",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
6
6
  "keywords": [
@@ -44,12 +44,13 @@
44
44
  ];
45
45
 
46
46
  let multiple = $state(true);
47
+ let shadow = $state<'box' | 'content' | 'none'>('box');
47
48
  </script>
48
49
 
49
50
  <ComponentEditorBase
50
51
  {component}
51
52
  title="Image Lightbox"
52
- description="Click an inline image to expand it into a centered modal with a backdrop. Pass multiple images for a gallery (chevrons + counter). Extended mode adds zoom controls and drag panning."
53
+ description="Click an inline image to expand it into a centered modal with a backdrop. Pass multiple images for a gallery (chevrons + counter). Extended mode adds zoom controls and drag panning. `shadow` picks where the one tile-shadow token falls: `box` casts it from the tile rectangle, `content` from the image's own alpha, or `none`."
53
54
  tokens={allTokens}
54
55
  >
55
56
  <VariantGroup name="imagelightbox" title="Image Lightbox" {states} {component}>
@@ -58,9 +59,17 @@
58
59
  <input type="checkbox" checked={multiple} onchange={(e) => (multiple = e.currentTarget.checked)} />
59
60
  <span>Multiple images</span>
60
61
  </label>
62
+ <label class="preview-toggle">
63
+ <span>Shadow</span>
64
+ <select value={shadow} onchange={(e) => (shadow = e.currentTarget.value as typeof shadow)}>
65
+ <option value="box">Box</option>
66
+ <option value="content">Content</option>
67
+ <option value="none">None</option>
68
+ </select>
69
+ </label>
61
70
  {/snippet}
62
71
  <div class="preview-frame">
63
- <ImageLightbox images={multiple ? demoImages : [demoImages[0]]} extended />
72
+ <ImageLightbox images={multiple ? demoImages : [demoImages[0]]} {shadow} extended />
64
73
  </div>
65
74
  </VariantGroup>
66
75
  </ComponentEditorBase>
@@ -7,7 +7,8 @@
7
7
  // Non-text tokens per state; text/font lives in typeGroups. Item-shape tokens sit under `menu` so they read as one decision across states.
8
8
  const states: Record<string, Token[]> = {
9
9
  menu: [
10
- { label: 'surface color', element: 'frame', groupKey: 'surface', variable: '--menuselect-menu-surface' },
10
+ // The panel floats over arbitrary page content; below ~90% the content behind it ghosts through and it stops reading as a solid, tappable target.
11
+ { label: 'surface color', element: 'frame', groupKey: 'surface', variable: '--menuselect-menu-surface', minOpacity: 90 },
11
12
  { label: 'border color', element: 'frame', groupKey: 'border', variable: '--menuselect-menu-border' },
12
13
  { label: 'border width', element: 'frame', groupKey: 'width', variable: '--menuselect-menu-border-width' },
13
14
  { label: 'corner radius', element: 'frame', groupKey: 'menu-radius', variable: '--menuselect-menu-radius' },
@@ -124,6 +124,8 @@
124
124
  standalone?: boolean;
125
125
  };
126
126
 
127
+ const paletteExtra = (t: Token) => ({ minOpacity: t.minOpacity ?? 0 });
128
+
127
129
  const SELECTOR_REGISTRY: Record<Kind, SelectorEntry> = {
128
130
  'font-family': { component: UIFontFamilySelector },
129
131
  'font-weight': { component: UIFontWeightSelector },
@@ -153,9 +155,9 @@
153
155
  'blur': { component: UIVariantSelector, extra: () => ({ ...BLUR }) },
154
156
  'scale': { component: UIVariantSelector, extra: () => ({ ...SCALE }) },
155
157
  'shadow': { component: UIVariantSelector, extra: () => ({ ...SHADOW }) },
156
- 'surface': { component: UIPaletteSelector },
157
- 'border': { component: UIPaletteSelector },
158
- 'text-color': { component: UIPaletteSelector },
158
+ 'surface': { component: UIPaletteSelector, extra: paletteExtra },
159
+ 'border': { component: UIPaletteSelector, extra: paletteExtra },
160
+ 'text-color': { component: UIPaletteSelector, extra: paletteExtra },
159
161
  };
160
162
 
161
163
  /** Multi-col rank: same as `orderRank` but with `text-color` hoisted between
@@ -34,6 +34,12 @@ export type Token = {
34
34
  `accent`). Set on gradient-kind tokens so Copy-from's family-swap
35
35
  can compute the src→dst family substitution. */
36
36
  family?: string;
37
+ /** Opacity floor (0-100) for a colour token. A surface that floats over
38
+ arbitrary page content has to stay legible and hit-obvious there, so the
39
+ picker refuses to take it below this percentage and drops the `None`
40
+ chip. Shipped defaults are held above the floor by the registry
41
+ contract test. */
42
+ minOpacity?: number;
37
43
  };
38
44
 
39
45
  /** An intrinsic: a structural/display property (alignment, hairline position,
@@ -693,8 +693,8 @@ export function buildStylesheet(s: SketchSettings): string {
693
693
  * Chrome does not honour it — see `bleed` below, which is what actually keeps
694
694
  * the drawn edge off the border box.
695
695
  */
696
- const coverage = (size: string, pos: string) =>
697
- `mask-image:var(--sketch-mask, none);` +
696
+ const coverage = (size: string, pos: string, off = '') =>
697
+ `mask-image:${off}var(--sketch-mask, none);` +
698
698
  `mask-size:${size};` +
699
699
  `mask-mode:luminance;mask-repeat:repeat;mask-clip:no-clip;` +
700
700
  `mask-position:var(${pos}, 0 0);`;
@@ -706,10 +706,19 @@ export function buildStylesheet(s: SketchSettings): string {
706
706
  // `--sketch-icon-off` names what to draw a subtree's glyphs with instead:
707
707
  // `none` keeps them crisp, `var(--sketch-icon-soft)` draws them at a fraction
708
708
  // of the travel. It inherits, so any chrome that lives in the host document
709
- // (the overlay bar) sets it once on its own root and every icon under it
710
- // follows regardless of specificity. `svg` covers inline artwork the same
711
- // way. The injected filter bank is itself an svg in the body, so it has to be
712
- // excluded or it filters itself.
709
+ // (the overlay bar, the column guides) sets it once on its own root and every
710
+ // icon under it follows regardless of specificity. `svg` covers inline artwork
711
+ // the same way. The injected filter bank is itself an svg in the body, so it
712
+ // has to be excluded or it filters itself.
713
+ //
714
+ // The knob answers for the ink mask as well, or chrome that asked for crisp
715
+ // glyphs came out blotched anyway. It holds a filter, which is not a mask
716
+ // image, so it cannot be substituted into `mask-image` as a value: prefixed
717
+ // to it, any value the knob carries makes the declaration invalid, and an
718
+ // invalid declaration is dropped, which is the no-mask this wants. Unset, it
719
+ // substitutes to nothing and the field lands as before. Soft loses the mask
720
+ // too, which is the point of asking for less: the mask is what eats a glyph
721
+ // small enough to need the soft bank.
713
722
  const iconSel = `[class*="fa-"], svg:not([${DEFS_ATTR}])`;
714
723
  const iconsOn = s.iconTravel > 0 || s.iconMaskOn;
715
724
  const icons = iconsOn
@@ -728,7 +737,7 @@ export function buildStylesheet(s: SketchSettings): string {
728
737
  // covers and the dial reads the same on a 16px icon as on a page-wide
729
738
  // drawing. `auto` on the other axis keeps the tile square.
730
739
  (s.iconMaskOn
731
- ? coverage('auto var(--sketch-icon-mask-tile)', '--sketch-icon-mask-pos')
740
+ ? coverage('auto var(--sketch-icon-mask-tile)', '--sketch-icon-mask-pos', 'var(--sketch-icon-off,) ')
732
741
  : '') +
733
742
  `}` +
734
743
  SEEDS.map((seed, i) =>
@@ -151,10 +151,10 @@ export const SKETCH_PRESETS: Record<string, SketchSettings> = {
151
151
  blurb: 'Broad translucent nib gone round twice on the same line, so the overlap darkens and the ink pools where it slows.',
152
152
  fillTravel: 2, strokeTravel: 1.5, wobble: 56, waveform: 1.4, borderWavelength: 1.3,
153
153
  strokeWidth: 4, doubleStroke: true, retracePass: 'copy', strokeInk: 0.52, retraceOffset: 2.2,
154
- maskBlob: 95, maskOutputMin: 0.5, maskOutputMax: 0.92, maskOctaves: 2, maskPosterize: 4, maskSoftness: 2.8,
154
+ maskBlob: 115, maskOutputMin: 0.21, maskOutputMax: 1, maskOctaves: 3, maskPosterize: 4, maskSoftness: 2.5,
155
155
  jitterX: 3, jitterY: 3, jitterRot: 0.8, jitterScale: 0.045,
156
156
  cornerSpread: 10, cornerTravel: 8,
157
- pressure: 0.2, pressureMod: 0.25, pooling: 2, iconTravel: 1.25,
157
+ pressure: 0.2, pressureMod: 0.25, pooling: 2, iconTravel: 1.25, iconMaskScale: 4,
158
158
  },
159
159
 
160
160
  whiteboard: {
@@ -196,22 +196,22 @@ export const SKETCH_PRESETS: Record<string, SketchSettings> = {
196
196
  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
197
  fillTravel: 3, strokeTravel: 2.25, wobble: 50, roughness: 3, waveform: 2.5,
198
198
  strokeWidth: 2.25, doubleStroke: true, retracePass: 'reseeded', retraceOffset: 4, strokeInk: 1,
199
- maskBlob: 150, maskOutputMin: 0.45, maskOutputMax: 1, maskOctaves: 2, maskPosterize: 2, maskSoftness: 6.7,
199
+ maskBlob: 150, maskOutputMin: 0.43, maskOutputMax: 0.95, maskOctaves: 2, maskPosterize: 2, maskSoftness: 6.7,
200
200
  jitterX: 6, jitterY: 6, jitterRot: 1.8, jitterScale: 0.1,
201
201
  cornerSpread: 20, cornerTravel: 17,
202
- pressure: 0.45, pressureMod: 0.6, pooling: 2.5, iconTravel: 2.25,
202
+ pressure: 0.45, pressureMod: 0.6, pooling: 2.5, iconTravel: 2.25, iconMaskScale: 3.6,
203
203
  },
204
204
 
205
205
  dry: {
206
206
  ...base, label: 'Dry marker',
207
- blurb: 'Ink that ran out. One scratchy pass that breaks up along its length, over a fill the mask has torn holes clean through.',
207
+ 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
208
  fillTravel: 2.25, strokeTravel: 1.75, wobble: 50, waveform: 2, borderWavelength: 0.5,
209
209
  strokeWidth: 3.5, doubleStroke: false, strokeInk: 0.4,
210
- maskBlob: 150, maskOutputMin: 0, maskOutputMax: 0.85,
211
- maskOctaves: 2, maskPosterize: 4, maskSoftness: 2,
210
+ maskBlob: 150, maskOutputMin: 0.15, maskOutputMax: 0.91,
211
+ maskOctaves: 2, maskPosterize: 4, maskSoftness: 1.75,
212
212
  jitterX: 5, jitterY: 5, jitterRot: 1.4, jitterScale: 0.08,
213
213
  cornerSpread: 16, cornerTravel: 13,
214
- pressure: 0.4, pressureMod: 0.8, pooling: 1.5, iconTravel: 1.75, iconMaskScale: 0.65,
214
+ pressure: 0.4, pressureMod: 0.8, pooling: 1.5, iconTravel: 1.75, iconMaskScale: 3.8,
215
215
  },
216
216
  };
217
217
 
@@ -1,7 +1,14 @@
1
1
  /**
2
2
  * The single source of truth for the CSS form of a shadow scale token:
3
3
  *
4
- * <x>px <y>px <blur>px <spread>px hsla(<h>, <s>%, <l>%, <a>)
4
+ * <x>px <y>px <blur>px [<spread>px] hsla(<h>, <s>%, <l>%, <a>)
5
+ *
6
+ * The spread slot is written only when it is non-zero. A three-length shadow is
7
+ * legal in `box-shadow` (spread defaults to 0) and in `filter: drop-shadow()`,
8
+ * which has no spread slot at all — so one token dresses both, and a component
9
+ * casting from an image's alpha reads the same variable as one casting from a
10
+ * box. Dial a spread and the token grows its fourth length: still a shadow,
11
+ * no longer a filter.
5
12
  *
6
13
  * Everything that reads or writes that form goes through `parseShadowCss` /
7
14
  * `shadowTokenCss`, so the theme generator (Node, no store) and the editor
@@ -33,16 +40,17 @@ export function computeAngleDistance(x: number, y: number): { angle: number; dis
33
40
  }
34
41
 
35
42
  export function shadowTokenCss(t: ShadowValue): string {
36
- return `${t.x}px ${t.y}px ${t.blur}px ${t.spread}px hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
43
+ const spread = t.spread ? `${t.spread}px ` : '';
44
+ return `${t.x}px ${t.y}px ${t.blur}px ${spread}hsla(${t.hue}, ${t.saturation}%, ${t.lightness}%, ${t.opacity})`;
37
45
  }
38
46
 
39
47
  export function parseShadowCss(variable: string, raw: string): ShadowToken | null {
40
- const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(-?\d+)px\s+hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
48
+ const m = raw.trim().match(/^(-?\d+)px\s+(-?\d+)px\s+(\d+)px\s+(?:(-?\d+)px\s+)?hsla\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)$/);
41
49
  if (!m) return null;
42
50
  const x = parseInt(m[1], 10);
43
51
  const y = parseInt(m[2], 10);
44
52
  const blur = parseInt(m[3], 10);
45
- const spread = parseInt(m[4], 10);
53
+ const spread = m[4] === undefined ? 0 : parseInt(m[4], 10);
46
54
  const hue = Math.round(parseFloat(m[5]));
47
55
  const saturation = Math.round(parseFloat(m[6]));
48
56
  const lightness = Math.round(parseFloat(m[7]));
@@ -163,7 +163,8 @@ redraw. Body type is left alone: an icon is a shape and survives a wobble, a
163
163
  paragraph is not.
164
164
 
165
165
  `--sketch-icon-off` names what a subtree's glyphs are drawn with instead. It
166
- inherits, so one declaration covers everything under it:
166
+ inherits, so one declaration covers everything under it, and it takes the ink
167
+ mask off as well as the wobble:
167
168
 
168
169
  ```css
169
170
  /* Crisp. Chrome, a logo, anything that has to stay exact. */
@@ -6,7 +6,7 @@ export const docContent: Record<string, string> = {
6
6
  "creating-components": "# Creating components\n\nThe package ships about 25 editable components. When you need one it doesn't\nhave, you can make your own Svelte component editable, so anyone using the\neditor can re-point its colours, type, and spacing without touching code.\n\nThe simplest way is to ask Claude. The package bundles a Claude Code skill that\nknows the conventions, writes the files, and checks the result for you.\n\n## Install the skills\n\n```bash\nnpx @motion-proto/live-tokens setup-claude\n```\n\nThis copies the bundled skills into your project's `.claude/skills/`. Once\nthey're there, Claude Code picks them up automatically.\n\n## Ask for a component\n\nDescribe what you want in plain English. Phrases like these trigger the skill:\n\n- \"Add a Toggle component to live-tokens\"\n- \"Make this Svelte component editable in the live-tokens editor\"\n- \"Create a Stat component with a value and a label\"\n\nClaude asks any clarifying questions it needs (which variants, which states,\nwhich parts), then writes the component, registers it with the editor, and runs\nits verification checklist. When it finishes, open `/live-tokens/components` to see your new\ncomponent in the editor and confirm everything works.\n\n## What you get\n\n- A runtime component whose editable properties default to your theme tokens.\n- An editor entry that appears under **Custom** in the `/live-tokens/components` view.\n- The naming and wiring handled for you, so the component fits the system.\n\nAdvanced authors who want to write a component by hand can read the naming and\nstate-model conventions shipped in the package\n(`src/system/styles/CONVENTIONS.md` and the skill's own `SKILL.md`).\n",
7
7
  "editing-tokens": "# Editing tokens\n\nA tour of the editor. The page behind it repaints on every change; saving\nwrites a theme file you can reload later.\n\nThe editor has four views:\n\n- **Tokens**: the design-system primitives (colour, type, spacing, and so on).\n They apply everywhere your site uses them.\n- **Color Wheel**: the harmony wheel, the palette curves, and the story your colours\n tell across a page.\n- **Components**: per-component editors. Re-Assign what tokens a component uses\n without changing the underlying system.\n- **Sketch Style**: an effect layer that redraws the page by hand. See\n [Sketch mode](sketch-mode.md).\n\nThis page covers **Tokens**. For components, see\n[Creating components](creating-components.md).\n\n## Palettes\n\nMost colour work happens here. Each palette (Brand, Accent, Neutral, Canvas,\nSuccess, Warning, Info, Danger, and a few more) has:\n\n- **Base colour.** Pick a hex; the palette derives an 11-step ramp (100 to 950)\n from it.\n- **Curves.** Three curves shape the ramp, in stack order: Hue, Saturation,\n Lightness. Drag the handles to bias it warmer or cooler, more or less\n saturated, darker or lighter. Hue drifts the ramp's temperature without\n moving contrast, because OKLCH hue rotation is close to lightness-preserving.\n It holds ±45 degrees; a bigger shift belongs on the base colour or the\n harmony axis.\n- **Overrides.** Lock a single step to a hand-picked hex when the curve doesn't\n land where you want.\n\nEditing a palette base ripples through every colour that depends on it, in real\ntime. Colours use OKLCH, so the ramp stays perceptually even across hues\nwithout muddy mid-tones.\n\n## Type\n\n- **Fonts.** Add sources from Google Fonts, Adobe (Typekit), a CSS URL, or an\n inline `@font-face`. The font loads in the page as soon as you add it.\n- **Stacks.** Named font cascades you reference by token, such as a display\n stack and a body stack.\n- **Sizes and weights.** A t-shirt scale (xs, sm, md, lg, xl, 2xl…) for size and\n a numeric scale (100 to 900) for weight.\n\n## Spacing, radius, shadow\n\nNumeric scales with a slider per step.\n\n- **Spacing**: the padding, gap, and margin scale.\n- **Radius**: none through full.\n- **Shadow**: colour, offset, blur, spread, and opacity per step, with stacked\n shadows supported.\n\nChange a step and every element using it repaints.\n\n## Overlays and gradients\n\n- **Overlays** are translucent tints layered over surfaces, like the subtle\n tint a card gets on hover. Set a colour and opacity per state.\n- **Gradients** are reusable gradient tokens with a stop list and direction, for\n hero panels and accent backgrounds.\n\n## Columns\n\nThe page-grid overlay. Set column count, gutter, and outer margin, and toggle\nthe visual guide with `Cmd/Ctrl+G`. Pages built on the column system reflow\nlive.\n\n## Saving\n\nThe editor saves to your browser continuously, so work survives a reload\nmid-edit. Writing a file is a separate step: the **Theme** panel at the foot of\nthe sidebar has **Save**, **Save As**, and **Load**, and each theme is one JSON\nfile under `src/live-tokens/data/themes/`.\n\nThe header gives you undo/redo (`Cmd/Ctrl+Z`, `Cmd/Ctrl+Shift+Z`). You can keep\nmany themes side by side; one is open at a time, and only **Adopt** publishes\none. See [Themes](themes-workflow.md) for the full lifecycle.\n",
8
8
  "getting-started": "# Getting started\n\nScaffold a live token site in a moments. You need Node 20 or later, a\npackage manager (npm, pnpm, or yarn), and a browser. Open claude code in your repo and start building.\n\n## Scaffold a new app\n\n```bash\nnpm create @motion-proto/live-tokens@latest my-app\ncd my-app\nnpm install\nnpm run dev\n```\n\nOpen the URL Vite prints (usually `http://localhost:5173`). You get a\none-page Svelte + Vite app that depends on the published package, with the\neditor wired up and the full component set ready to import.\n\n`npx @motion-proto/live-tokens create my-app` runs the same scaffold without\nthe initialiser package.\n\n### What the scaffold gives you\n\nEvery editable file lives under `src/` and is committed, so `npm install` and\nversion upgrades never touch your styles. The package code stays in\n`node_modules`.\n\n| Path | What it is |\n|------|------------|\n| `src/pages/Home.svelte` | The starter page. Replace it with your own content. |\n| `src/App.svelte` | Your routes. `<LiveTokensRouter>` adds dev-only routes under a reserved `/live-tokens/*` namespace: `/live-tokens/editor`, `/live-tokens/components`, and `/live-tokens/docs`. |\n| `src/system/styles/tokens.css` | Your base token vocabulary, hand-authored. |\n| `src/styles/site.css` | Themed page typography, yours to edit. |\n\n## Your first edit\n\n1. Run `npm run dev` and open the home page.\n2. Click **Open Token Editor**, or visit `/live-tokens/editor`. The editor opens beside\n the page.\n3. Open **Palettes**, pick **Brand**, and change the base hex. The page\n repaints as you type.\n4. In the **Theme** panel at the foot of the sidebar, choose **Save As**. Your\n theme appears as JSON under `src/live-tokens/data/themes/`.\n5. Reload. The editor reopens on your theme, so the page returns as you left\n it.\n\n## What you just changed\n\nEvery edit sets a CSS custom property on `:root`. Your components read those\nproperties through `var(--...)`. There is no token build step and no\npreprocessor rewriting your code: the page renders against plain CSS variables\nthe editor swaps live.\n\nTo ship, click **Adopt** in the Theme panel. That saves the open theme and bakes\nit into `src/live-tokens/data/tokens.generated.css`, which your build bundles\nalongside `tokens.css`. Adopt is the only action that changes what your site\nships, so try any look you like first. The editor itself never reaches\nproduction.\n\nAlready have a Svelte 5 + Vite app? The\n[README](https://github.com/motionproto/live-tokens#readme) covers installing\ninto an existing project.\n\n## Where to go next\n\n- **[Editing tokens](editing-tokens.md)**: a tour of the editor.\n- **[Themes](themes-workflow.md)**: save, switch, and ship.\n- **[Creating components](creating-components.md)**: make your own component\n editable.\n",
9
- "sketch-mode": "# Sketch mode\n\nSketch mode redraws your whole page as if it had been drawn by hand. Every\ncomponent keeps its own colours, spacing and corners; what changes is the line\nthey are drawn with.\n\nIt is an effect layer, not a set of token values. It reads nothing from your\ntheme and writes nothing back, so it never touches a token, never lands in a\ntheme file, and never reaches the CSS you ship. Turn it off and every trace of\nit goes.\n\nOpen the **Sketch Style** view in the editor and switch **Sketch mode** on. The effect\napplies to the page behind the editor as well as to the preview, so what you see\nin context is what it does.\n\n## What it draws\n\nEach component's fill and outline are repainted from the tokens that component\nalready owns. The real background and border are hidden behind them, then both\nare pushed around one shared field of noise. Because every component samples the\nsame field, the whole page reads as one drawing rather than as a set of\nseparately wobbled boxes.\n\n## The presets\n\nSeven looks ship with the package, and each is a complete set of dials rather\nthan a style name:\n\n- **Pencil.** Two graphite passes on their own seeds, so the outline disagrees\n with itself the way a hand coming back round does.\n- **Marker.** A broad translucent nib gone round twice on the same line, so the\n overlap darkens and the ink pools where it slows.\n- **Whiteboard.** The fattest nib on glass, with a mask that streaks the fill\n like a half-wiped board.\n- **Hatched.** An etching. The fill is angled shading and the outline a single\n hard-edged scratch.\n- **Dashed.** A drafting outline: one slow drift along the ruler, broken into\n strokes. The clean pole.\n- **Napkin.** Ballpoint in a hurry. Everything loose at once.\n- **Dry marker.** Ink that ran out. One scratchy pass over a mostly eaten fill.\n\nPick one, then move whatever you like. **Save** keeps your dials under a name of\nyour own, alongside the shipped seven, as a file under\n`src/live-tokens/data/sketch-presets/`.\n\n## The dials\n\n- **Border.** How far the outline travels and how long its wave is, then its\n width, ink, pressure and pooling. A second pass either copies the first line a\n few pixels off or runs it through the pen again on its own seed.\n- **Fill.** Solid or hatched, how far the fill's edge travels, and how far each\n instance is offset, rotated and scaled from its neighbours. **Ink coverage**\n thins the fill with a field of blotches: set their size, how many levels of\n detail, how pale and how dense they go, and how soft their edges are.\n- **Shape.** **Corner spread** rounds each corner by its own share of the dial,\n so no two match. **Corner travel** leans the drawn box into a quadrilateral\n with no two sides parallel. This is the dial that stops a component reading as\n a rectangle.\n- **Icons and SVG.** Glyph travel and wavelength on their own scale. A glyph is\n all curves already, so it needs more travel than a card's long straight edge\n before the wobble reads at all.\n- **Noise.** The shared field itself: its wavelength, how many layers of detail\n sit on it, and the shape of its wave. A square wave sends nearly every edge to\n full travel, which is what makes the effect stronger rather than bigger.\n\n## Where the settings live\n\nThe dials you are moving live in your browser, so the effect follows you across\nreloads and stays off everyone else's screen. **Save** writes a named preset to\n`src/live-tokens/data/sketch-presets/`, which is the only thing that reaches\ndisk.\n\nSketch mode is a tool for looking at the page, not a layer the page can ship.\nNothing is written into a theme, `tokens.generated.css` never sees it, and a\nproduction build has no sketch layer in it at all.\n\n## Drawing your own elements\n\nThe layer draws a fixed set of parts: the shipped components, and four classes\nit reserves for you. Nothing else is touched, so a page element or a\nconsumer-authored component is left crisp until it carries one of them.\n\n| Class | For |\n|---------------------|-----------------------------------------------------------|\n| `sketch-surface` | A box. The default treatment. |\n| `sketch-container` | A large box. Tilts less, so the type inside stays readable. |\n| `sketch-chip` | A small box. Finer fill mask, more rotation, less travel. |\n| `sketch-rule` | A line rather than a box. No rotation, no rounded ends. |\n\nPick by size, not by kind: a card and a modal both take `sketch-container`, a\nbadge and a pill both take `sketch-chip`.\n\nThe class opts the element in; it names no colours, so the element states its\nown. `--sketch-fill`, `--sketch-stroke`, `--sketch-hatch-color`,\n`--sketch-radius` and `--sketch-shadow` name the fill, the outline, the hatching\nink, the corners and the shadow for one element and everything inside it. The\nlayer blanks the real background and border, so an element whose fill matters\nunder Sketch mode has to name it here as well as paint it.\n\nThe layer also paints on the element's `::before` and `::after`, forces its\n`overflow` visible, and gives it a stacking context of its own. Keep the class\noff anything that owns a pseudo-element, clips its content, or is positioned\nabsolutely, and put it on a wrapper instead.\n\n```css\n.my-callout {\n background: var(--surface-brand-lowest);\n border: var(--border-width-1) solid var(--border-brand);\n border-radius: var(--radius-xl);\n\n --sketch-fill: var(--surface-brand-lowest);\n --sketch-stroke: var(--border-brand);\n --sketch-radius: var(--radius-xl);\n}\n```\n\nA gradient is a valid fill: the shorthand's last layer takes a colour or an\nimage, so `--sketch-fill` accepts either. States work the same way, since\nnothing is competing with you for the value:\n\n```css\n.my-callout:hover { --sketch-stroke: var(--border-brand-strong); }\n```\n\n## Images inside a drawn part\n\nA drawn part's `overflow` is forced visible, because the fill and outline are\npainted on pseudo-elements that travel past the box and would otherwise be cut\noff at its edge. A background that bleeds is the effect working. An image that\nbleeds is not: it keeps its square corners while the card around it turns.\n\nMedia that runs to a part's edge therefore has to carry that part's corners\nitself. `--sketch-radius` is the radius the layer drew, and it inherits, so a\nchild can read it and fall back to its own value when Sketch mode is off:\n\n```css\n.cover {\n overflow: hidden;\n border-top-left-radius: var(--sketch-radius, var(--card-default-radius));\n border-top-right-radius: var(--sketch-radius, var(--card-default-radius));\n}\n```\n\nCorner spread is per-corner and per-instance, so at high spread the crop is the\nmean rather than an exact trace of the drawn edge.\n\nA rule made from a `border` is not a box and cannot be displaced. Make it an\nelement, give it `sketch-rule`, and name its ink:\n\n```html\n<div class=\"rule sketch-rule\"></div>\n```\n```css\n.rule {\n height: var(--border-width-2);\n background: var(--border-brand);\n --sketch-fill: var(--border-brand);\n}\n```\n\nIcons and inline SVG take the wobble directly, since a glyph has no box to\nredraw. Body type is left alone: an icon is a shape and survives a wobble, a\nparagraph is not.\n\n`--sketch-icon-off` names what a subtree's glyphs are drawn with instead. It\ninherits, so one declaration covers everything under it:\n\n```css\n/* Crisp. Chrome, a logo, anything that has to stay exact. */\n.app-bar { --sketch-icon-off: none; }\n\n/* Drawn back rather than off, at a third of the travel. Small artwork, and\n type set as an SVG, which the layer reads as one large glyph. */\n.wordmark { --sketch-icon-off: var(--sketch-icon-soft); }\n```\n\nThe **Blotch size** dial under Icons and SVG is a share of the glyph rather than\na px size, because no px size is right for both a 16px icon and a page-wide\ndrawing. At 100% every glyph gets one period of the field across it whatever its\nsize. Below that the field repeats inside the glyph and the blotches get finer.\nAbove it a glyph reads part of one blotch, so the mask thins the whole glyph\nunevenly instead of breaking it up. The fill's blotches stay in px, since a\ncomponent does have a size to state one against.\n",
9
+ "sketch-mode": "# Sketch mode\n\nSketch mode redraws your whole page as if it had been drawn by hand. Every\ncomponent keeps its own colours, spacing and corners; what changes is the line\nthey are drawn with.\n\nIt is an effect layer, not a set of token values. It reads nothing from your\ntheme and writes nothing back, so it never touches a token, never lands in a\ntheme file, and never reaches the CSS you ship. Turn it off and every trace of\nit goes.\n\nOpen the **Sketch Style** view in the editor and switch **Sketch mode** on. The effect\napplies to the page behind the editor as well as to the preview, so what you see\nin context is what it does.\n\n## What it draws\n\nEach component's fill and outline are repainted from the tokens that component\nalready owns. The real background and border are hidden behind them, then both\nare pushed around one shared field of noise. Because every component samples the\nsame field, the whole page reads as one drawing rather than as a set of\nseparately wobbled boxes.\n\n## The presets\n\nSeven looks ship with the package, and each is a complete set of dials rather\nthan a style name:\n\n- **Pencil.** Two graphite passes on their own seeds, so the outline disagrees\n with itself the way a hand coming back round does.\n- **Marker.** A broad translucent nib gone round twice on the same line, so the\n overlap darkens and the ink pools where it slows.\n- **Whiteboard.** The fattest nib on glass, with a mask that streaks the fill\n like a half-wiped board.\n- **Hatched.** An etching. The fill is angled shading and the outline a single\n hard-edged scratch.\n- **Dashed.** A drafting outline: one slow drift along the ruler, broken into\n strokes. The clean pole.\n- **Napkin.** Ballpoint in a hurry. Everything loose at once.\n- **Dry marker.** Ink that ran out. One scratchy pass over a mostly eaten fill.\n\nPick one, then move whatever you like. **Save** keeps your dials under a name of\nyour own, alongside the shipped seven, as a file under\n`src/live-tokens/data/sketch-presets/`.\n\n## The dials\n\n- **Border.** How far the outline travels and how long its wave is, then its\n width, ink, pressure and pooling. A second pass either copies the first line a\n few pixels off or runs it through the pen again on its own seed.\n- **Fill.** Solid or hatched, how far the fill's edge travels, and how far each\n instance is offset, rotated and scaled from its neighbours. **Ink coverage**\n thins the fill with a field of blotches: set their size, how many levels of\n detail, how pale and how dense they go, and how soft their edges are.\n- **Shape.** **Corner spread** rounds each corner by its own share of the dial,\n so no two match. **Corner travel** leans the drawn box into a quadrilateral\n with no two sides parallel. This is the dial that stops a component reading as\n a rectangle.\n- **Icons and SVG.** Glyph travel and wavelength on their own scale. A glyph is\n all curves already, so it needs more travel than a card's long straight edge\n before the wobble reads at all.\n- **Noise.** The shared field itself: its wavelength, how many layers of detail\n sit on it, and the shape of its wave. A square wave sends nearly every edge to\n full travel, which is what makes the effect stronger rather than bigger.\n\n## Where the settings live\n\nThe dials you are moving live in your browser, so the effect follows you across\nreloads and stays off everyone else's screen. **Save** writes a named preset to\n`src/live-tokens/data/sketch-presets/`, which is the only thing that reaches\ndisk.\n\nSketch mode is a tool for looking at the page, not a layer the page can ship.\nNothing is written into a theme, `tokens.generated.css` never sees it, and a\nproduction build has no sketch layer in it at all.\n\n## Drawing your own elements\n\nThe layer draws a fixed set of parts: the shipped components, and four classes\nit reserves for you. Nothing else is touched, so a page element or a\nconsumer-authored component is left crisp until it carries one of them.\n\n| Class | For |\n|---------------------|-----------------------------------------------------------|\n| `sketch-surface` | A box. The default treatment. |\n| `sketch-container` | A large box. Tilts less, so the type inside stays readable. |\n| `sketch-chip` | A small box. Finer fill mask, more rotation, less travel. |\n| `sketch-rule` | A line rather than a box. No rotation, no rounded ends. |\n\nPick by size, not by kind: a card and a modal both take `sketch-container`, a\nbadge and a pill both take `sketch-chip`.\n\nThe class opts the element in; it names no colours, so the element states its\nown. `--sketch-fill`, `--sketch-stroke`, `--sketch-hatch-color`,\n`--sketch-radius` and `--sketch-shadow` name the fill, the outline, the hatching\nink, the corners and the shadow for one element and everything inside it. The\nlayer blanks the real background and border, so an element whose fill matters\nunder Sketch mode has to name it here as well as paint it.\n\nThe layer also paints on the element's `::before` and `::after`, forces its\n`overflow` visible, and gives it a stacking context of its own. Keep the class\noff anything that owns a pseudo-element, clips its content, or is positioned\nabsolutely, and put it on a wrapper instead.\n\n```css\n.my-callout {\n background: var(--surface-brand-lowest);\n border: var(--border-width-1) solid var(--border-brand);\n border-radius: var(--radius-xl);\n\n --sketch-fill: var(--surface-brand-lowest);\n --sketch-stroke: var(--border-brand);\n --sketch-radius: var(--radius-xl);\n}\n```\n\nA gradient is a valid fill: the shorthand's last layer takes a colour or an\nimage, so `--sketch-fill` accepts either. States work the same way, since\nnothing is competing with you for the value:\n\n```css\n.my-callout:hover { --sketch-stroke: var(--border-brand-strong); }\n```\n\n## Images inside a drawn part\n\nA drawn part's `overflow` is forced visible, because the fill and outline are\npainted on pseudo-elements that travel past the box and would otherwise be cut\noff at its edge. A background that bleeds is the effect working. An image that\nbleeds is not: it keeps its square corners while the card around it turns.\n\nMedia that runs to a part's edge therefore has to carry that part's corners\nitself. `--sketch-radius` is the radius the layer drew, and it inherits, so a\nchild can read it and fall back to its own value when Sketch mode is off:\n\n```css\n.cover {\n overflow: hidden;\n border-top-left-radius: var(--sketch-radius, var(--card-default-radius));\n border-top-right-radius: var(--sketch-radius, var(--card-default-radius));\n}\n```\n\nCorner spread is per-corner and per-instance, so at high spread the crop is the\nmean rather than an exact trace of the drawn edge.\n\nA rule made from a `border` is not a box and cannot be displaced. Make it an\nelement, give it `sketch-rule`, and name its ink:\n\n```html\n<div class=\"rule sketch-rule\"></div>\n```\n```css\n.rule {\n height: var(--border-width-2);\n background: var(--border-brand);\n --sketch-fill: var(--border-brand);\n}\n```\n\nIcons and inline SVG take the wobble directly, since a glyph has no box to\nredraw. Body type is left alone: an icon is a shape and survives a wobble, a\nparagraph is not.\n\n`--sketch-icon-off` names what a subtree's glyphs are drawn with instead. It\ninherits, so one declaration covers everything under it, and it takes the ink\nmask off as well as the wobble:\n\n```css\n/* Crisp. Chrome, a logo, anything that has to stay exact. */\n.app-bar { --sketch-icon-off: none; }\n\n/* Drawn back rather than off, at a third of the travel. Small artwork, and\n type set as an SVG, which the layer reads as one large glyph. */\n.wordmark { --sketch-icon-off: var(--sketch-icon-soft); }\n```\n\nThe **Blotch size** dial under Icons and SVG is a share of the glyph rather than\na px size, because no px size is right for both a 16px icon and a page-wide\ndrawing. At 100% every glyph gets one period of the field across it whatever its\nsize. Below that the field repeats inside the glyph and the blotches get finer.\nAbove it a glyph reads part of one blotch, so the mask thins the whole glyph\nunevenly instead of breaking it up. The fill's blotches stay in px, since a\ncomponent does have a size to state one against.\n",
10
10
  "themes-workflow": "# Themes\n\nSave your work, switch between looks, and ship one to production.\n\n## The Theme panel\n\nThe **Theme** panel at the foot of the editor sidebar holds the whole look:\ncolors, type, and a setting for every component, in one file. It carries the\nname the look ships under, whether production is running it, and **Adopt**.\nTwo parts sit under it, each a read-out rather than a file to manage.\n\n- **Colors & Type** holds the design tokens. Components read those tokens to\n define their appearance. It names the two faces the page is showing.\n- **Components** counts how many components have an unsaved edit that has not\n been saved into the theme, and opens the component editors.\n\nA theme holds its own copy of every part, so one theme can never break another.\n\n## How themes work\n\nA theme is a document, and the editor works the way any editor does.\n\n- **A theme** is a named JSON file in `src/live-tokens/data/themes/`. It carries\n the whole look: the colors and type plus a setting for every component.\n- **The open theme** is the one the editor is working on, named in\n `themes/_active.json`. One at a time.\n- **Your unsaved edits** are what the page shows right now. The editor keeps\n them in your browser as you work and writes them to a buffer, `_working.json`,\n one slot per part of the look. **Save** captures that buffer into the open\n theme.\n- **The production theme** is the one your site ships, named in\n `themes/_production.json`. **Adopt** changes it; saving a preset in the Theme\n Picker performs that Adopt for you.\n\nAbsence is the answer for anything untouched: a buffer exists only where the\nlive look diverges from the active theme, so a newly opened theme has none.\n\n## Fonts\n\nType is part of the look, so it saves, loads and ships with the theme rather\nthan on its own. Four named stacks carry it:\n\n| Stack | Used by |\n|---|---|\n| `--font-display` | headings |\n| `--font-sans` | body text and most UI |\n| `--font-serif` | anywhere you ask for it |\n| `--font-mono` | code |\n\nEach stack is a family followed by its fallbacks, so a page still reads while a\nweb font loads, and still reads if it never does. **Project fonts**, in the\nColors and type editor, is where families come from: type a Google Fonts family\nname and the editor checks it, or paste a fonts URL, an embed tag, or your own\n`@font-face` rules. Removing a family puts the stack back on its fallbacks.\n\nYou can also set both faces at once from the command line:\n\n```bash\nnpx live-tokens set-fonts fonts.json\n```\n\nwith a brief naming the families:\n\n```json\n{ \"display\": \"Fraunces\", \"body\": \"Nunito Sans\" }\n```\n\nIt checks each family against Google Fonts, works out the weights that family\nactually has, and binds it to its stack. Like every other edit, the result lands\nin the buffer, so **Save** keeps it. In Claude Code, asking for a font pairing in\nplain English runs the same command.\n\nA font is only requested by the browser once something on the page uses it, so\ncarrying a family you no longer reference costs nothing at load. Adopting is\nwhat writes the font imports your site ships, into `fonts.css`.\n\n## Saving\n\nIn the Theme panel:\n\n- **Save** captures the look on screen into the open theme. Your colors and type\n go in as part of it, so there is nothing to save first.\n- **Save As** names a new theme. Use it for your first save and for forking.\n\nComponent editors keep their own unsaved state. If one or more components are\nwaiting when you use **Save**, **Save As**, or **Adopt**, the Theme panel offers\nto save all of them before continuing. You can accept once instead of visiting\neach component, or cancel to review them individually. A component editor's\n**Save As** creates a reusable component preset.\n\nNames are tidied to lowercase with hyphens, so \"My Brand!\" becomes `my-brand`,\nand a leading underscore is dropped: those names are reserved for the buffer.\n**Motion Proto** is the built-in theme and is read-only. You can always return\nto it, and the editor never overwrites it, so start your own with **Save As**.\n\n## Switching\n\n**Load**—or clicking the active theme's name—opens the Theme Picker. Picking a\ntheme shows it on the page as a preview with nothing written to disk, so you can\ntry each look and compare. **Save** in that window opens and adopts the previewed\ntheme in one step: the active pointer changes, the buffers clear, the editor\nworks on it, and production ships it. **Cancel** returns you to where you were.\nPreviewing alone never changes what your site ships.\n\n**Colors and type only. Keep my shapes.** narrows the load to the palette and\nthe fonts: your component settings stay as they are and the theme you have open\nstays open. Saved colors and type files are listed there too, marked *colors &\ntype*, and picking one is always that narrower load.\n\n## Shipping\n\n**Adopt**, in the Theme panel, is the \"ship it\" step, and it ships the whole\nlook. It saves the open theme, then bakes that theme into\n`src/live-tokens/data/tokens.generated.css`, which your build bundles alongside\n`tokens.css`: the colors and type plus every component the theme carries. Fonts\nregenerate to match. The line under the theme name says whether production is\nrunning this theme.\n\nProduction is one saved theme, so nothing else publishes. Trying a look, moving\na token, saving a theme: all of it leaves the generated CSS alone until you\nAdopt. A component editor's Adopt runs the same whole-look step, because a\ncomponent never ships alone. Adopting while Motion Proto is open saves your look\nas a theme of your own first, since the built-in one is read-only.\n\nProduction builds (`npm run build`) ship only that plain CSS and your\ncomponents. No editor, no JSON loading, no runtime indirection.\n\n## Keeping your work safe\n\nEverything under `src/live-tokens/data/` is plain JSON, so commit it. Themes show\nup as readable diffs you can review per branch, and the buffer shows up as the\nwork you have not saved into a theme yet. Nothing is backed up anywhere else:\ngit is your safety net. To experiment freely, **Save As** a new name first, then\nedit.\n\n## Where to go next\n\n- **[Where themes live](where-themes-live.md)**: the files behind all of this,\n and what writes each one.\n- **[Creating components](creating-components.md)**: make your own components\n editable in the same editor.\n",
11
11
  "where-themes-live": "# Where themes live\n\nEverything the editor writes is plain JSON and CSS inside your project. There\nis no database and no hidden state: the files are the storage, and git is the\nhistory.\n\n## The data tree\n\n```\nsrc/live-tokens/data/\n themes/\n _active.json names the theme the editor has open\n _production.json names the theme your site ships\n default.json Motion Proto, the built-in look, rewritten at boot\n my-brand.json a saved theme: the whole look in one file\n colors-and-type/\n _working.json unsaved colors and type edits\n component-configs/\n button/\n default.json Button's shipped settings, derived at boot\n _working.json unsaved Button edits\n my-button.json a preset you saved from the Button editor\n tokens.generated.css the baked CSS your production build ships\nsrc/system/styles/\n tokens.css your token vocabulary, hand-authored, never written\n fonts.css font imports, rewritten when you Adopt\n```\n\nA saved theme carries the whole look by value: the colors and type plus a\nsetting for every component. It depends on no other file, so deleting\nanything else never breaks it.\n\n## What writes when\n\n- **Editing** changes the page through CSS variables. The editor keeps your\n edits in the browser as you work and writes them to the `_working.json`\n buffers when you save a component. When the Theme panel finds several dirty\n components, **Save all** writes those buffers together.\n- **Save** captures the buffers into the open theme's file. That file is the\n durable copy of your look; matching buffers are then removed.\n- **Load** clears the buffers and points `themes/_active.json` at the theme you\n picked. Live reads fall through to that file. Nothing else changes, so trying\n looks is free and ordinary switching changes only the pointer.\n- **Adopt** points `themes/_production.json` at the open theme, bakes it into\n `tokens.generated.css`, and rewrites `fonts.css` to match. It is the only\n action that changes what your site ships.\n\nThe `default.json` files are the shipped baseline. The editor derives them at\nboot and refreshes them when the package updates; it never saves your work\nover them.\n\nProjects upgraded from 0.48 may initially contain working files copied from the\nactive theme. On the first dev-server boot, exact copies are removed\nautomatically. Any file that differs is kept as unsaved work, so no migration\ncommand is required.\n\n## What to commit\n\nAll of it. The data tree is designed to live in git: themes diff readably, the\ntwo pointers say what is open and what ships, and a `_working.json` in a diff\nis exactly the work you have not yet saved into a theme. Nothing is backed up\nanywhere else.\n\n## Where to go next\n\n- **[Themes](themes-workflow.md)**: the workflow built on these files: saving,\n loading, and shipping.\n",
12
12
  };