@motion-proto/live-tokens 0.52.1 → 0.53.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 (60) hide show
  1. package/.claude/skills/live-tokens-adjust-shape-space/SKILL.md +7 -1
  2. package/CHANGELOG.md +63 -0
  3. package/bin/adjust.mjs +16 -39
  4. package/bin/generate-theme.mjs +20 -6
  5. package/dist-plugin/adjust/index.cjs +781 -21
  6. package/dist-plugin/adjust/index.d.cts +2 -1
  7. package/dist-plugin/adjust/index.d.ts +2 -1
  8. package/dist-plugin/adjust/index.js +43 -21
  9. package/dist-plugin/{chunk-6OZFXIQI.js → chunk-232GZGQU.js} +207 -12
  10. package/dist-plugin/chunk-NE6N66EE.js +753 -0
  11. package/dist-plugin/{chunk-JIGIIE5P.js → chunk-PB2JTK2H.js} +123 -0
  12. package/dist-plugin/generateColorsAndType/index.cjs +48 -32
  13. package/dist-plugin/generateColorsAndType/index.d.cts +2 -1
  14. package/dist-plugin/generateColorsAndType/index.d.ts +2 -1
  15. package/dist-plugin/generateColorsAndType/index.js +7 -773
  16. package/dist-plugin/{themeTypes-Dky03HL9.d.cts → index-DpTIRZ2H.d.cts} +49 -21
  17. package/dist-plugin/{themeTypes-Dky03HL9.d.ts → index-DpTIRZ2H.d.ts} +49 -21
  18. package/dist-plugin/index.cjs +1159 -85
  19. package/dist-plugin/index.js +102 -80
  20. package/dist-plugin/migrateData/index.cjs +956 -14
  21. package/dist-plugin/migrateData/index.js +10 -3
  22. package/package.json +6 -4
  23. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +9 -23
  24. package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +8 -8
  25. package/src/editor/core/components/adjustAliases.ts +49 -22
  26. package/src/editor/core/components/aliasKinds.ts +7 -2
  27. package/src/editor/core/components/componentConfigService.ts +1 -1
  28. package/src/editor/core/store/editorStore.ts +7 -197
  29. package/src/editor/core/store/editorTypes.ts +9 -30
  30. package/src/editor/core/themes/lookSummary.ts +3 -25
  31. package/src/editor/core/themes/migrateComponentConfig.ts +205 -0
  32. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +1 -1
  33. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +1 -1
  34. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +1 -1
  35. package/src/editor/core/themes/parsers/gradient.ts +211 -0
  36. package/src/editor/core/themes/slices/gradients.ts +5 -58
  37. package/src/editor/core/themes/themeService.ts +22 -9
  38. package/src/editor/core/themes/themeTypes.ts +71 -21
  39. package/src/editor/docs/content/themes-workflow.md +9 -11
  40. package/src/editor/docs/content/where-themes-live.md +2 -2
  41. package/src/editor/docs/content.generated.ts +2 -2
  42. package/src/editor/index.ts +1 -0
  43. package/src/editor/styles/ui-editor.css +5 -2
  44. package/src/editor/ui/PaletteEditor.svelte +30 -15
  45. package/src/editor/ui/ThemePanel.svelte +24 -15
  46. package/src/editor/ui/Toggle.svelte +22 -22
  47. package/src/editor/ui/UIPillButton.svelte +1 -1
  48. package/src/editor/ui/UIReveal.svelte +47 -0
  49. package/src/editor/ui/UISquareButton.svelte +1 -1
  50. package/src/editor/ui/colors/ColorsTab.svelte +4 -5
  51. package/src/editor/ui/palette/OverridesPanel.svelte +4 -3
  52. package/src/editor/ui/palette/PaletteJumpButton.svelte +3 -3
  53. package/src/editor/ui/palette/ScaleCurveEditor.svelte +3 -2
  54. package/src/live-tokens/data/themes/autumn.json +77 -41
  55. package/src/live-tokens/data/themes/halloween.json +53 -52
  56. package/src/live-tokens/data/themes/midnight-study.json +136 -68
  57. package/src/live-tokens/data/themes/ocean.json +77 -41
  58. package/src/live-tokens/data/themes/royal-velvet.json +43 -42
  59. package/src/live-tokens/data/themes/spring-meadow.json +86 -52
  60. package/src/live-tokens/data/themes/sunset.json +101 -40
@@ -1,6 +1,23 @@
1
1
  import type { CurveAnchor } from '../../ui/curveEngine';
2
+ import type { GradientValue } from './parsers/gradient';
2
3
  import type { Oklch } from '../palettes/oklch';
3
4
  import type { HarmonyAxis } from '../palettes/colorHarmony';
5
+ /** Single source of truth for the theme schema version
6
+ * (docs/plans/theme-completeness.md, Wave 2 step 5). It lives here, on the
7
+ * shipped side: `vite-plugin/` is build tooling and is not in the tarball, so
8
+ * anything under `src/` that reaches into it resolves in this repo and
9
+ * nowhere else. `normalizeTheme.ts` re-exports this. */
10
+ export const THEME_SCHEMA_VERSION = 4;
11
+
12
+ /** What the completeness fill had to add to reach a whole theme. `components`
13
+ * names each component the input carried no entry for; `aliases` counts keys
14
+ * added to components it did carry; `orphans` counts keys it carries that the
15
+ * current default no longer declares. */
16
+ export interface ThemeFillReport {
17
+ components: string[];
18
+ aliases: number;
19
+ orphans: number;
20
+ }
4
21
 
5
22
  export type GradientStyle = 'linear' | 'radial' | 'conic';
6
23
 
@@ -123,11 +140,22 @@ export interface GradientDiskToken {
123
140
  }
124
141
 
125
142
  /**
126
- * Where a live read resolved from: the unsaved `_working` buffer, the open
127
- * theme's embedded copy, or the shipped default. A `working` source is an
128
- * unsaved delta from the active theme.
143
+ * Where a live component config resolved from: the unsaved `_working`
144
+ * buffer, or the open theme's embedded copy. Every theme carries every
145
+ * component complete, so `working` is the only genuine divergence —
146
+ * anything else is what the open document itself resolves to.
147
+ */
148
+ export type LiveSource = 'working' | 'theme';
149
+
150
+ /**
151
+ * Where a live colors-and-type read resolved from. Unlike `LiveSource`, this
152
+ * keeps a third value: colors-and-type resolution is unchanged by the theme
153
+ * completeness work (docs/plans/theme-completeness.md Wave 4), so `default`
154
+ * still answers the one case completeness can't reach — the open theme's
155
+ * file itself is unreadable (a stale pointer, or deleted mid-session), so
156
+ * there is no document to resolve `working` or `theme` against.
129
157
  */
130
- export type LiveSource = 'working' | 'theme' | 'default';
158
+ export type ColorsAndTypeSource = 'working' | 'theme' | 'default';
131
159
 
132
160
  export interface ColorsAndType {
133
161
  name: string;
@@ -148,8 +176,8 @@ export interface ColorsAndType {
148
176
  * handlers; read by `themeInit` to seed `openThemeSlug`. Not persisted.
149
177
  */
150
178
  _fileName?: string;
151
- /** Server-attached marker: which of the three live layers answered. */
152
- _source?: LiveSource;
179
+ /** Server-attached marker: which live layer answered. See `ColorsAndTypeSource`. */
180
+ _source?: ColorsAndTypeSource;
153
181
  /**
154
182
  * Migration stamp. Absent on legacy files, treated as 0; the loader runs
155
183
  * any registered theme migrations whose `fromVersion >= file.schemaVersion`.
@@ -174,7 +202,7 @@ export interface ColorsAndTypeMeta {
174
202
  * gradients that can't compress to a single string. */
175
203
  export type AliasDiskValue =
176
204
  | string
177
- | { kind: 'gradient'; value: { type: 'linear' | 'radial' | 'solid' | 'none'; angle: number; radius?: number; centerX?: number; aspectX?: number; aspectY?: number; stops: { position: number; color: string; opacity?: number }[] } };
205
+ | { kind: 'gradient'; value: GradientValue };
178
206
 
179
207
  export interface ComponentConfig {
180
208
  name: string;
@@ -188,7 +216,10 @@ export interface ComponentConfig {
188
216
  * theme, not a config file. Set by the component-configs GET handlers.
189
217
  */
190
218
  _fileName?: string;
191
- /** Server-attached marker: which of the three live layers answered. */
219
+ /** Server-attached marker: which live layer answered — `working` the
220
+ * buffer, `theme` the open document's embedded copy. No third value:
221
+ * every theme carries every component, so there is no shipped-default
222
+ * layer left to report (unlike `ColorsAndTypeSource`). */
192
223
  _source?: LiveSource;
193
224
  /**
194
225
  * Migration stamp. Absent on legacy files, treated as 0. See `ColorsAndType.schemaVersion`.
@@ -204,11 +235,11 @@ export interface ComponentConfigMeta {
204
235
 
205
236
  /**
206
237
  * A saved look, encapsulated: the colors and type plus a config for every
207
- * component that sits off its default, all carried by value. Themes are the
208
- * documents of the editor. Applying one opens it by clearing the reserved
209
- * `_working` buffers and updating `themes/_active.json`; live reads fall through
210
- * to its embedded copies. Saving captures live deltas back into it; Adopt
211
- * publishes it, and only then is
238
+ * component this install has, all carried by value. Themes are the documents
239
+ * of the editor. Applying one opens it by clearing the reserved `_working`
240
+ * buffers and updating `themes/_active.json`; live reads fall through to its
241
+ * embedded copies. Saving captures live deltas back into it; Adopt publishes
242
+ * it, and only then is
212
243
  * `tokens.generated.css` rebaked.
213
244
  */
214
245
  export interface Theme {
@@ -217,14 +248,32 @@ export interface Theme {
217
248
  updatedAt: string;
218
249
  /** Migration stamp. 1 was the pointer form (colors-and-type + config
219
250
  * basenames); 2 encapsulated it under a `theme` key; 3 spells that key
220
- * `colorsAndType`. The server rewrites older files at boot. */
221
- schemaVersion: 3;
251
+ * `colorsAndType`; 4 makes the theme complete (`docs/plans/
252
+ * theme-completeness.md`, Wave 2) — every known component and every alias
253
+ * key its `default.json` declares, by value. The server rewrites older
254
+ * files at boot. */
255
+ schemaVersion: typeof THEME_SCHEMA_VERSION;
222
256
  /** Full colors-and-type content. */
223
257
  colorsAndType: ColorsAndType;
224
- /** Component id → its config. Delta encoding: a component absent here is on
225
- * its default. Defaults are never inlined — the local `default.json` derived
226
- * from the component source is canonical, and a frozen copy would drift. */
258
+ /** Component id → its config, by value, one entry per component this
259
+ * install has and every alias key that component's `default.json`
260
+ * declares. `normalizeTheme` migrates each embedded config to the current
261
+ * component schema and then fills any gap from the local default, on every
262
+ * read (Waves 1 and 2 of `docs/plans/theme-completeness.md`); both the
263
+ * migration and the fill are persisted on the next write, so a theme is a
264
+ * whole-look document rather than a diff against a moving baseline.
265
+ * `component-configs/<id>/default.json` is the derivation product of that
266
+ * component's `:global(:root)`, not a resolution layer this map defers to
267
+ * — a config for a component this install does not have, or an alias key
268
+ * its current default no longer declares, is kept exactly as read (RJC 3)
269
+ * rather than dropped. */
227
270
  componentConfigs: Record<string, ComponentConfig>;
271
+ /** Migration stamp for every embedded component config in this theme, one
272
+ * field for all of them: `CURRENT_COMPONENT_SCHEMA_VERSION` is a single
273
+ * global counter over every component migration, so a per-entry stamp would
274
+ * buy no isolation. `normalizeTheme` sets this on every read; an embedded
275
+ * config's own `schemaVersion` (if hand-authored) is ignored and stripped. */
276
+ componentSchemaVersion: number;
228
277
  /** Server-attached file-name marker. Same role as `ColorsAndType._fileName`. */
229
278
  _fileName?: string;
230
279
  }
@@ -238,13 +287,14 @@ export interface Theme {
238
287
  */
239
288
  export interface ThemeBundle {
240
289
  /** Discriminator for safe identification of bundle JSON files. Paired with
241
- * the version below: import accepts this kind only at v3, and the
290
+ * the version below: import accepts this kind at v3 and v4, and the
242
291
  * `manifest-bundle` every release through 0.47.1 wrote only at v1. */
243
292
  kind: 'theme-bundle';
244
293
  /** Tracks the enclosed theme's schema. Import still accepts 1, where the
245
294
  * envelope carried a pointer theme plus separately inlined colors and type
246
- * and `${component}/${configName}`-keyed configs. */
247
- schemaVersion: 3;
295
+ * and `${component}/${configName}`-keyed configs, and 3, from before the
296
+ * Wave 2 completeness bump — `normalizeTheme` fills either on the way in. */
297
+ schemaVersion: typeof THEME_SCHEMA_VERSION;
248
298
  /** Sender's `@motion-proto/live-tokens` package version. Receiver can
249
299
  * compare to its own to warn about compatibility drift. */
250
300
  liveTokensVersion: string;
@@ -5,14 +5,14 @@ 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, and a setting for every component you changed, in one file. It
9
- carries the name the look ships under, whether production is running it, and
10
- **Adopt**. Two parts sit under it, each a read-out rather than a file to manage.
8
+ colors, type, and a setting for every component, in one file. It carries the
9
+ name the look ships under, whether production is running it, and **Adopt**.
10
+ Two parts sit under it, each a read-out rather than a file to manage.
11
11
 
12
12
  - **Colors & Type** holds the design tokens. Components read those tokens to
13
13
  define their appearance. It names the two faces the page is showing.
14
- - **Components** counts how many components run something the theme does not
15
- carry, and opens the component editors.
14
+ - **Components** counts how many components have an unsaved edit that has not
15
+ been saved into the theme, and opens the component editors.
16
16
 
17
17
  A theme holds its own copy of every part, so one theme can never break another.
18
18
 
@@ -21,8 +21,7 @@ A theme holds its own copy of every part, so one theme can never break another.
21
21
  A theme is a document, and the editor works the way any editor does.
22
22
 
23
23
  - **A theme** is a named JSON file in `src/live-tokens/data/themes/`. It carries
24
- the whole look: the colors and type plus a setting for every component you
25
- changed.
24
+ the whole look: the colors and type plus a setting for every component.
26
25
  - **The open theme** is the one the editor is working on, named in
27
26
  `themes/_active.json`. One at a time.
28
27
  - **Your unsaved edits** are what the page shows right now. The editor keeps
@@ -60,10 +59,9 @@ to it, and the editor never overwrites it, so start your own with **Save As**.
60
59
  **Load**—or clicking the active theme's name—opens the Theme Picker. Picking a
61
60
  theme shows it on the page as a preview with nothing written to disk, so you can
62
61
  try each look and compare. **Save** in that window opens and adopts the previewed
63
- theme in one step: the active pointer changes, the buffers clear, components it
64
- does not carry fall through to their defaults, the editor works on it, and
65
- production ships it. **Cancel** returns you to where you were. Previewing alone
66
- never changes what your site ships.
62
+ theme in one step: the active pointer changes, the buffers clear, the editor
63
+ works on it, and production ships it. **Cancel** returns you to where you were.
64
+ Previewing alone never changes what your site ships.
67
65
 
68
66
  **Colors and type only. Keep my shapes.** narrows the load to the palette and
69
67
  the fonts: your component settings stay as they are and the theme you have open
@@ -27,8 +27,8 @@ src/system/styles/
27
27
  ```
28
28
 
29
29
  A saved theme carries the whole look by value: the colors and type plus a
30
- setting for every component you changed. It depends on no other file, so
31
- deleting anything else never breaks it.
30
+ setting for every component. It depends on no other file, so deleting
31
+ anything else never breaks it.
32
32
 
33
33
  ## What writes when
34
34
 
@@ -6,6 +6,6 @@ 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 two views:\n\n- **Tokens**: the design-system primitives (colour, type, spacing, and so on).\n They apply everywhere your site uses them.\n- **Components**: per-component editors. Re-Assign what tokens a component uses\n without changing the underlying system.\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
- "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 you changed, in one file. It\ncarries the name the look ships under, whether production is running it, and\n**Adopt**. Two 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 run something the theme does not\n carry, 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 you\n changed.\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## 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, components it\ndoes not carry fall through to their defaults, the editor works on it, and\nproduction ships it. **Cancel** returns you to where you were. Previewing alone\nnever 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",
10
- "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 you changed. It depends on no other file, so\ndeleting anything 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",
9
+ "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## 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",
10
+ "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",
11
11
  };
@@ -36,6 +36,7 @@ export type {
36
36
  ColorsAndType,
37
37
  ColorsAndTypeMeta,
38
38
  LiveSource,
39
+ ColorsAndTypeSource,
39
40
  GradientStyle,
40
41
  GradientStop,
41
42
  FontSource,
@@ -36,10 +36,13 @@
36
36
  --ui-gray-850: #c7c7c7;
37
37
  --ui-gray-950: #f2f2f2;
38
38
 
39
+ /* Every level down to muted clears WCAG AA (4.5:1) on the chrome surfaces
40
+ it lands on, so no rung of the ramp is unreadable: muted bottoms out at
41
+ 4.66:1 on --ui-surface. Only disabled sits below AA, which 1.4.3 exempts. */
39
42
  --ui-text-primary: #ffffff;
40
43
  --ui-text-secondary: var(--ui-gray-800);
41
- --ui-text-tertiary: var(--ui-gray-700);
42
- --ui-text-muted: var(--ui-gray-500);
44
+ --ui-text-tertiary: var(--ui-gray-750);
45
+ --ui-text-muted: var(--ui-gray-700);
43
46
  --ui-text-disabled: var(--ui-gray-400);
44
47
  --ui-text-accent: var(--ui-gray-950);
45
48
  --ui-text-success: var(--ui-gray-850);
@@ -5,6 +5,7 @@
5
5
  import ColorEditPanel from './ColorEditPanel.svelte';
6
6
  import OverridesPanel from './palette/OverridesPanel.svelte';
7
7
  import UIPillButton from './UIPillButton.svelte';
8
+ import UIReveal from './UIReveal.svelte';
8
9
  import GradientStopEditor from './palette/GradientStopEditor.svelte';
9
10
  import ScaleCurveEditor from './palette/ScaleCurveEditor.svelte';
10
11
  import PaletteBase from './palette/PaletteBase.svelte';
@@ -639,8 +640,8 @@
639
640
  </button>
640
641
  <div class="swatch gray-swatch bookend" style="background: #000000"></div>
641
642
  </div>
642
- {#if paletteEditorOpen}
643
- <div class="curve-grid-span" style="grid-column: 2 / {paletteStepLightness.length + 2}">
643
+ <UIReveal open={paletteEditorOpen} style="grid-column: 2 / {paletteStepLightness.length + 2}">
644
+ <div class="curve-grid-span">
644
645
  {#if anchorUnlockPrompt && anchorToBase}
645
646
  <div class="anchor-unlock-notice" role="alert">
646
647
  <i class="fas fa-triangle-exclamation" aria-hidden="true"></i>
@@ -700,7 +701,7 @@
700
701
  onAutoSmoothChange={handleAutoSmooth}
701
702
  />
702
703
  </div>
703
- {/if}
704
+ </UIReveal>
704
705
  </div>
705
706
 
706
707
  {#if emptySelector && emptyMode === 'gradient'}
@@ -766,18 +767,20 @@
766
767
  <span>Text, Surfaces &amp; Borders</span>
767
768
  </button>
768
769
 
769
- {#if showDerived}
770
- <div class="scales-row">
771
- {#each scales.filter(s => s.isText) as scale}
772
- {@render overridesPanel(scale, true, derivedHexForBase)}
773
- {/each}
774
- </div>
775
- <div class="scales-row">
776
- {#each scales.filter(s => !s.isText) as scale}
777
- {@render overridesPanel(scale, true, derivedHexForBase)}
778
- {/each}
770
+ <UIReveal open={showDerived}>
771
+ <div class="derived-rows">
772
+ <div class="scales-row">
773
+ {#each scales.filter(s => s.isText) as scale}
774
+ {@render overridesPanel(scale, true, derivedHexForBase)}
775
+ {/each}
776
+ </div>
777
+ <div class="scales-row">
778
+ {#each scales.filter(s => !s.isText) as scale}
779
+ {@render overridesPanel(scale, true, derivedHexForBase)}
780
+ {/each}
781
+ </div>
779
782
  </div>
780
- {/if}
783
+ </UIReveal>
781
784
 
782
785
  <!-- Color Edit Panel (non-base edits) -->
783
786
  {#if !isEditingBase && panelOpen && editingColor}
@@ -855,6 +858,14 @@
855
858
  min-width: 0;
856
859
  }
857
860
 
861
+ /* Carries the column gap the two rows used to get from .palette-editor,
862
+ now that the reveal wrapper sits between them and it. */
863
+ .derived-rows {
864
+ display: flex;
865
+ flex-direction: column;
866
+ gap: var(--ui-space-20);
867
+ }
868
+
858
869
  .scale-section {
859
870
  display: flex;
860
871
  flex-direction: column;
@@ -964,7 +975,11 @@
964
975
  align-items: start;
965
976
  justify-content: start;
966
977
  min-width: 0;
967
- max-width: calc(var(--swatch-cols) * 4.5rem + (var(--swatch-cols) - 1) * var(--swatch-gap, var(--ui-space-4)));
978
+ /* Definite width, not max-width: the grid is a shrink-to-fit flex item, so
979
+ without it the palette sized itself to the curve row below and shrank
980
+ every time the graphs were collapsed. */
981
+ width: calc(var(--swatch-cols) * 4.5rem + (var(--swatch-cols) - 1) * var(--swatch-gap, var(--ui-space-4)));
982
+ max-width: 100%;
968
983
  }
969
984
 
970
985
  /* The picker rides the same track geometry as the curve editors below, so
@@ -21,6 +21,7 @@
21
21
  saveActiveTheme,
22
22
  exportTheme,
23
23
  importTheme,
24
+ type ThemeFillReport,
24
25
  } from '../core/themes/themeService';
25
26
  import {
26
27
  THEME_APPLIED_EVENT,
@@ -119,7 +120,6 @@
119
120
  if (active) {
120
121
  openThemeSlug.set(active._fileName ?? 'default');
121
122
  currentDisplayName = active.name ?? $openThemeSlug;
122
- lookConfigs = active.componentConfigs;
123
123
  }
124
124
  } catch {
125
125
  // silent
@@ -149,15 +149,11 @@
149
149
  // ── Components ────────────────────────────────────────────────────────
150
150
  //
151
151
  // The per-component file managers live in the component editors; the list
152
- // here answers one question — how many components run something the open
153
- // theme does not carry.
152
+ // here answers one question — how many components carry an unsaved buffer.
154
153
 
155
154
  let components: ComponentSummary[] = $state([]);
156
- let lookConfigs: Theme['componentConfigs'] | null = $state(null);
157
155
 
158
- let componentsOffLook = $derived(
159
- lookConfigs ? countComponentsOffLook(components, lookConfigs, activeIsProtected) : 0,
160
- );
156
+ let componentsOffLook = $derived(countComponentsOffLook(components));
161
157
 
162
158
  async function refreshComponents() {
163
159
  try {
@@ -194,7 +190,6 @@
194
190
  const handleThemeApplied = (event: Event) => {
195
191
  const { result } = (event as CustomEvent<AppliedThemeDetail>).detail;
196
192
  currentDisplayName = result.theme.name;
197
- lookConfigs = result.theme.componentConfigs;
198
193
  };
199
194
  document.addEventListener(THEME_APPLIED_EVENT, handleThemeApplied);
200
195
  return () => document.removeEventListener(THEME_APPLIED_EVENT, handleThemeApplied);
@@ -453,6 +448,13 @@
453
448
  await commitWholeLook();
454
449
  }
455
450
 
451
+ function filledNote(filled: ThemeFillReport): string | null {
452
+ const n = filled.components.length;
453
+ if (n === 0) return null;
454
+ return `${n} component${n === 1 ? '' : 's'} arrived with gaps. `
455
+ + `The editor filled them from the current defaults.`;
456
+ }
457
+
456
458
  async function commitWholeLook() {
457
459
  const row = previewRow;
458
460
  if (!row) return;
@@ -468,14 +470,19 @@
468
470
  let result: Awaited<ReturnType<typeof applyTheme>>;
469
471
  try {
470
472
  result = await applyTheme(row.slug);
473
+ const notes: string[] = [];
471
474
  if (result.skippedComponents.length > 0) {
472
- window.alert(
473
- `Loaded "${row.name}". These components are not installed here, so their `
474
- + `saved settings were skipped:\n\n${result.skippedComponents.join(', ')}`,
475
+ notes.push(
476
+ `These components are not installed here, so their saved settings were `
477
+ + `skipped:\n${result.skippedComponents.join(', ')}`,
475
478
  );
476
479
  }
480
+ const filled = filledNote(result.filled);
481
+ if (filled) notes.push(filled);
482
+ if (notes.length > 0) {
483
+ window.alert(`Loaded "${row.name}".\n\n${notes.join('\n\n')}`);
484
+ }
477
485
  currentDisplayName = result.theme.name;
478
- lookConfigs = result.theme.componentConfigs;
479
486
  showFileList = false;
480
487
  } catch (err) {
481
488
  window.alert(`Failed to load theme: ${(err as Error).message}`);
@@ -600,6 +607,8 @@
600
607
  `The file was missing this data, which fell back to the default:\n${result.dropped.join('\n')}`,
601
608
  );
602
609
  }
610
+ const filled = filledNote(result.filled);
611
+ if (filled) notes.push(filled);
603
612
  if (notes.length > 0) {
604
613
  window.alert(`Imported as "${result.theme}".\n\n${notes.join('\n\n')}`);
605
614
  }
@@ -671,7 +680,7 @@
671
680
  <span class="mfm-header-label">Theme</span>
672
681
  <UIInfoPopover title="Theme" ariaLabel="About the theme">
673
682
  <p>
674
- A <strong>theme</strong> is a whole look in one file: the colors and type plus a setting for every component you changed.
683
+ A <strong>theme</strong> is a whole look in one file: the colors and type plus a setting for every component.
675
684
  </p>
676
685
  <p>
677
686
  It holds its own copy of that data, so the theme you open is the whole look, and one theme can never break another.
@@ -680,13 +689,13 @@
680
689
  <strong>Load</strong> opens the list. Picking a theme shows it on the page as a preview, so you can try each look with nothing written to disk. Pick another to compare, or <strong>Cancel</strong> to go back to where you were.
681
690
  </p>
682
691
  <p>
683
- <strong>Save</strong> opens and adopts the previewed theme: the editor works on it from then on and production ships it immediately. Components it does not carry go back to their defaults. Previewing and cancelling never change what your site ships.
692
+ <strong>Save</strong> opens and adopts the previewed theme: the editor works on it from then on and production ships it immediately. Previewing and cancelling never change what your site ships.
684
693
  </p>
685
694
  <p>
686
695
  <strong>Colors and type only</strong> in that window takes the palette and the fonts and leaves your shapes and component settings alone.
687
696
  </p>
688
697
  <p>
689
- The <strong>active</strong> theme is the one the editor has open. <strong>Adopt</strong> saves it and ships it to production, colors and type plus every component you changed. The line under the name says whether production is running this theme.
698
+ The <strong>active</strong> theme is the one the editor has open. <strong>Adopt</strong> saves it and ships it to production, colors and type plus every component. The line under the name says whether production is running this theme.
690
699
  </p>
691
700
  <p>
692
701
  If components have unsaved edits, Save and Adopt offer to save all of them before continuing. You can cancel to review or save components individually.
@@ -45,7 +45,7 @@
45
45
  .toggle {
46
46
  display: inline-flex;
47
47
  align-items: center;
48
- gap: var(--space-8);
48
+ gap: var(--ui-space-8);
49
49
  cursor: pointer;
50
50
  user-select: none;
51
51
 
@@ -57,16 +57,16 @@
57
57
  cursor: not-allowed;
58
58
 
59
59
  .toggle-track {
60
- background: var(--surface-neutral-lower);
61
- border-color: var(--border-neutral-faint);
60
+ background: var(--ui-surface-lower);
61
+ border-color: var(--ui-border-low);
62
62
  }
63
63
 
64
64
  .toggle-thumb {
65
- background: var(--text-disabled);
65
+ background: var(--ui-text-disabled);
66
66
  }
67
67
 
68
68
  .toggle-label {
69
- color: var(--text-disabled);
69
+ color: var(--ui-text-disabled);
70
70
  }
71
71
  }
72
72
  }
@@ -74,13 +74,13 @@
74
74
  .toggle-track {
75
75
  position: relative;
76
76
  width: 2.25rem;
77
- height: var(--space-20);
78
- border-radius: var(--radius-2xl);
79
- border: var(--border-width-1) solid var(--border-neutral);
80
- background: var(--surface-neutral-low);
77
+ height: var(--ui-space-20);
78
+ border-radius: var(--ui-radius-2xl);
79
+ border: 1px solid var(--ui-border);
80
+ background: var(--ui-surface-low);
81
81
  padding: 0;
82
82
  cursor: inherit;
83
- transition: background var(--duration-150), border-color var(--duration-150);
83
+ transition: background var(--ui-transition-fast), border-color var(--ui-transition-fast);
84
84
  flex-shrink: 0;
85
85
 
86
86
  &.on {
@@ -89,34 +89,34 @@
89
89
  }
90
90
 
91
91
  &:hover:not(:disabled) {
92
- border-color: var(--border-neutral-medium);
92
+ border-color: var(--ui-border-high);
93
93
  }
94
94
 
95
95
  &:focus-visible {
96
- outline: var(--border-width-2) solid var(--border-brand);
97
- outline-offset: var(--space-2);
96
+ outline: 2px solid var(--ui-text-primary);
97
+ outline-offset: var(--ui-space-2);
98
98
  }
99
99
  }
100
100
 
101
101
  .toggle-thumb {
102
102
  position: absolute;
103
- top: var(--space-2);
104
- left: var(--space-2);
103
+ top: var(--ui-space-2);
104
+ left: var(--ui-space-2);
105
105
  width: 0.875rem;
106
106
  height: 0.875rem;
107
- border-radius: var(--radius-full);
108
- background: var(--text-secondary);
109
- transition: transform var(--duration-150), background var(--duration-150);
107
+ border-radius: var(--ui-radius-full);
108
+ background: var(--ui-text-secondary);
109
+ transition: transform var(--ui-transition-fast), background var(--ui-transition-fast);
110
110
 
111
111
  .on & {
112
- transform: translateX(var(--space-16));
113
- background: var(--text-primary);
112
+ transform: translateX(var(--ui-space-16));
113
+ background: var(--ui-text-primary);
114
114
  }
115
115
  }
116
116
 
117
117
  .toggle-label {
118
- font-size: var(--font-size-md);
119
- color: var(--text-secondary);
118
+ font-size: var(--ui-font-size-md);
119
+ color: var(--ui-text-secondary);
120
120
  line-height: 1;
121
121
  }
122
122
  </style>
@@ -115,7 +115,7 @@
115
115
  .ui-pill[aria-disabled='true'] {
116
116
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.10) 100%);
117
117
  border-color: rgba(255, 255, 255, 0.22);
118
- color: var(--ui-text-muted, #4d4d4d);
118
+ color: var(--ui-text-disabled, #333333);
119
119
  cursor: not-allowed;
120
120
  pointer-events: none;
121
121
  }