@motion-proto/live-tokens 0.54.0 → 0.54.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.54.2 — Palette focus is public API
4
+
5
+ ### Added
6
+
7
+ - **`openPaletteInTokens` and `openPaletteInWheel` are exported.** Both hand a
8
+ palette family to a view, and both were already what the palette jump buttons
9
+ call; only the package's own code could reach them. A consumer building a deep
10
+ link into the editor — "open the app with Brand's palette editor showing" —
11
+ now has the same entry point the UI uses. `selectedPalette` comes with them,
12
+ so a consumer can read which family is current. The one-shot
13
+ `pendingPaletteFocus` store they drive stays internal.
14
+
15
+ ## 0.54.1 — Palette editing opens on a curve
16
+
17
+ ### Changed
18
+
19
+ - **The base-color panel opens with the rest of the palette editor.** The panel
20
+ above the swatch grid appeared and vanished in one frame while the swatch
21
+ grid and the derived row animated, so opening a base color read as a jump. It
22
+ now reveals on the same height transition as its neighbours.
23
+
24
+ - **The floating token pills read as polished chrome.** The gradient was tuned
25
+ against a dark ground and sat too low in the scale for a light page, where it
26
+ looked tarnished. The bands keep their spacing and move up the scale, the
27
+ border becomes a dark hairline, and the strings drop to 0.3 opacity on light
28
+ pages, where a black stroke at 0.5 reads far heavier than the white stroke it
29
+ mirrors. The darkest stop stays at `#8f9090`, so the black label keeps AA.
30
+
31
+ - **The demo hero labels its theme picker.** The trigger showed the open
32
+ theme's name with nothing saying what it picked. A "Theme" label sits above
33
+ it, and the actions row bottom-aligns so the picker and the buttons sit on
34
+ one line.
35
+
3
36
  ## 0.54.0 — Shadow weight and text insets follow their context
4
37
 
5
38
  ### Fixed
package/README.md CHANGED
@@ -1,26 +1,25 @@
1
1
  # Live Tokens
2
2
 
3
- A foundational design system for quickly styling and building Svelte + Vite microsites. **Edit your tokens and components in real time** — colors, typography, spacing, per-component aliases and see the site update as you drag the slider. Save the result as a portable configuration you can carry from project to project.
3
+ A design system for styling and building Svelte + Vite microsites. Edit tokens and components in a dev-only editor and watch the running site repaint on every input. Save the result as a theme file and carry it between projects.
4
4
 
5
- `npm install @motion-proto/live-tokens` into your app — install once, style fast. The editor is dev-only; production builds get plain CSS variables and your chosen components, nothing else.
5
+ ```bash
6
+ npm install @motion-proto/live-tokens
7
+ ```
8
+
9
+ The editor is dev-only. Production builds get plain CSS variables and the components you used.
6
10
 
7
11
  ## What you get
8
12
 
9
- - **Real-time token editing.** Pick a color, drag a hue slider, retype a font size the page repaints on every input event via CSS-variable writes. No reload, no save-and-refresh, no build step. Works across colors, typography, spacing, radii, shadows, motion, palettes, and gradients.
10
- - **Real-time component editing.** Each of ~24 shipped Svelte components (Button, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, and more) declares its own design-token aliases in a `:global(:root)` block. Rewire any alias from a per-component picker and see that component update everywhere it's used — live, on your real pages, not in a Storybook sandbox.
11
- - **Theme editor** (`/live-tokens/editor` route, dev-only) the home of real-time token editing. Save themes to disk as JSON, then Adopt one to bake it into static CSS for the build.
12
- - **Per-component editor** (`/live-tokens/components` route, dev-only) the home of real-time component-alias editing. Pick token aliases per component without writing CSS.
13
- - **Live editor overlay** pins to the top-right of every dev page. Opens the editor in a side panel or floating window so you edit *on the page you're styling*, not in a separate tab. Includes a "Page Source" button that opens the current page's `.svelte` file in VS Code.
14
- - **Themes** a theme is a whole look in one file: colors and type plus a config for every component you changed, held by value. In the editor this is the Theme panel, with Colors & Type and Components as its parts. Themes are documents: loading one opens it with a one-file `_active.json` pointer change, live reads fall through to its content, and nothing your site ships changes until you Adopt. A narrower load takes the colors and type alone and leaves your shapes. Export one and import it into another project to restore the full styling in one step.
15
- - **Seven example looks** Autumn, Halloween, Midnight Study, Ocean, Royal Velvet, Spring Meadow and Sunset each ship as a full theme: preset colors and type plus a shape personality of radius, padding, gap and border-width aliases. Each preset also names its own Google Fonts pairing, one display family and one body family, so a look carries type as well as colour and shape. They need no local files, so Load one to try a whole look on your own pages and load Motion Proto to come back. Saving over a preset writes a local copy that shadows the shipped one; delete that copy and the shipped version returns.
16
- - **Vite plugin** hosts the `/api/live-tokens/{colors-and-type,component-configs,themes}/*` routes the editor reads and saves through. The single namespace keeps live-tokens' routes from colliding with anything your app serves under `/api`.
17
- - **Claude Code skill suite** — five bundled skills so you can drive the package in plain English. `build-page` composes pages from the shipped components. `pick-component` decides between confusing pairs (TabBar vs SegmentedControl, Card vs CollapsibleSection). `create-component` authors a new editable component against the project's naming, state-model, and import rules. `generate-theme` turns a mood brief ("bright and cheerful", "dark night theme") into a complete AA-checked color theme. `adjust-shape-space` turns "make the buttons pill shaped" or "space it out" into new radius, padding, gap, and border-width aliases. One command to install them all: `npx @motion-proto/live-tokens setup-claude`. See [Claude Code skills](#claude-code-skills) below.
13
+ - **Live token editing.** Colors, typography, spacing, radii, shadows, motion, palettes, and gradients. Every input writes a CSS variable, so the page repaints with no reload and no build step.
14
+ - **Live component editing.** 25 shipped Svelte components (Button, IconButton, Input, Card, Dialog, Badge, Callout, Table, Tooltip, Toggle, TabBar, SegmentedControl, RadioButton, MenuSelect, ProgressBar, CornerBadge, SectionDivider, CollapsibleSection, Notification, Image, ImageLightbox, CodeSnippet, SideNavigation, Panel, InlineEditActions) declare their design-token aliases in a `:global(:root)` block. Rewire an alias from the component's editor and it updates everywhere that component is used, on your real pages.
15
+ - **Four dev-only routes.** `/live-tokens/editor` for tokens, `/live-tokens/colors` for palettes, `/live-tokens/components` for per-component aliases, `/live-tokens/docs` for the user guide.
16
+ - **Editor overlay.** Pins to the top right of every dev page and opens the editor in a side panel or floating window, so you edit on the page you are styling. Its "Page Source" button opens the current page's `.svelte` file in VS Code.
17
+ - **Themes.** A theme is a whole look in one file: colors and type plus a config for every component, stored by value. Loading one changes a single pointer file, and nothing your site ships changes until you Adopt. Export a theme and import it into another project to restore the look in one step.
18
+ - **Seven example looks.** Autumn, Halloween, Midnight Study, Ocean, Royal Velvet, Spring Meadow, and Sunset each ship as a full theme: colors, a Google Fonts pairing, and a shape personality of radius, padding, gap, and border-width aliases. They ship inside the package, so trying one needs no local files. Load Motion Proto to return to the default. Saving over a preset writes a local copy that shadows the shipped one; delete the copy and the shipped version returns.
19
+ - **Vite plugin.** Hosts the `/api/live-tokens/{colors-and-type,component-configs,themes}/*` routes the editor reads and writes through. The single namespace keeps these routes clear of anything your app serves under `/api`.
20
+ - **Claude Code skills.** Five bundled skills that drive the package from plain English. See [Claude Code skills](#claude-code-skills).
18
21
 
19
- ## Quick install
20
-
21
- ```bash
22
- npm install @motion-proto/live-tokens
23
- ```
22
+ ## Install
24
23
 
25
24
  ### Vite config
26
25
 
@@ -33,7 +32,7 @@ import { themeFileApi } from '@motion-proto/live-tokens/vite-plugin';
33
32
  export default defineConfig({
34
33
  plugins: [
35
34
  // vitePreprocess compiles the shipped components' `<style lang="scss">`
36
- // blocks install `sass` alongside it.
35
+ // blocks. Install `sass` alongside it.
37
36
  svelte({ preprocess: vitePreprocess() }),
38
37
  themeFileApi({
39
38
  tokensCssPath: 'src/system/styles/tokens.css',
@@ -43,43 +42,15 @@ export default defineConfig({
43
42
  ```
44
43
 
45
44
  The `themeFileApi` plugin:
46
- - Resolves the `default` colors and type from the installed package, so you start on the shipped defaults without a local copy.
47
- - Discovers components at `src/components/*.svelte` (and `src/system/components/*.svelte` for back-compat) and seeds `src/live-tokens/data/component-configs/{comp}/default.json` from each component's `:global(:root)` block.
48
- - Writes `src/live-tokens/data/themes/default.json` on dev-server start: the Default theme, derived from the shipped colors and type and those component defaults, and regenerated whenever they change. It is protected, so the editor never deletes it and an outside deletion heals on the next start.
49
- - Bakes `tokens.generated.css` from the production theme at startup, so a fresh checkout builds against the look you shipped.
50
- - Hosts the `/api/live-tokens/*` routes the editor uses to save and load themes + per-component configs.
51
- - Auto-injects `__PROJECT_ROOT__` for the overlay's "Page Source" link and `__LIVE_TOKENS_API_BASE__` so the client uses whatever `apiBase` you configured.
52
-
53
- A project last opened on 0.47.1 or earlier still keeps its colors and type in `themes/` and its whole looks in `manifests/`, the names 0.48 reassigned. The plugin recognises that layout, writes nothing at all, and says so. `npx live-tokens migrate` moves `themes/` to `colors-and-type/` and `manifests/` to `themes/`, then heals what is inside: it reads what the retired per-layer `_active.json` / `_production.json` pointers resolved to, records it as the production theme, and clears them. Restart the dev server afterwards.
54
-
55
- ### Where data lands — and how to move it
56
-
57
- By default, the plugin reads and writes under one folder: `src/live-tokens/data/`. Inside that folder live three subdirectories — `colors-and-type/`, `themes/`, `component-configs/` — each owned by the plugin.
58
-
59
- `themes/` holds the documents: one file per whole look, plus `_active.json` naming the one the editor has open and `_production.json` naming the one your site ships. `colors-and-type/` and `component-configs/{comp}/` hold each layer's `default.json` baseline, any preset you save by name, and the `_working.json` buffer for edits you have not saved into the active theme. A buffer is a delta from that open document, so ordinary theme switching leaves none.
60
-
61
- To move them, create a `live-tokens.config.json` at your project root:
62
-
63
- ```json
64
- {
65
- "dataDir": "src/live-tokens/data"
66
- }
67
- ```
68
-
69
- All four keys are optional. `dataDir` is the headline knob — it relocates all three subfolders at once. The per-folder overrides exist for unusual layouts (e.g. a monorepo where colors and type are shared across packages but component-configs aren't):
70
-
71
- ```json
72
- {
73
- "dataDir": "src/live-tokens/data",
74
- "colorsAndTypeDir": "../shared/colors-and-type",
75
- "componentConfigsDir": "src/live-tokens/data/component-configs",
76
- "themesDir": "src/live-tokens/data/themes"
77
- }
78
- ```
79
45
 
80
- `colorsAndTypeDir` holds the colors-and-type files; `themesDir` holds the whole-look themes.
46
+ - Resolves the `default` colors and type from the installed package, so you start on the shipped defaults with no local copy.
47
+ - Discovers components at `src/components/*.svelte` and `src/system/components/*.svelte`, and seeds `src/live-tokens/data/component-configs/{comp}/default.json` from each component's `:global(:root)` block.
48
+ - Writes `src/live-tokens/data/themes/default.json` at dev-server start: the Default theme, derived from the shipped colors and type plus those component defaults, and regenerated when either changes. It is protected, so the editor never deletes it and an outside deletion heals on the next start.
49
+ - Bakes `tokens.generated.css` from the production theme at startup, so a fresh checkout builds against the look you shipped.
50
+ - Hosts the `/api/live-tokens/*` routes the editor saves and loads through.
51
+ - Injects `__PROJECT_ROOT__` for the overlay's "Page Source" link and `__LIVE_TOKENS_API_BASE__` so the client uses whatever `apiBase` you configured.
81
52
 
82
- Resolution order, per folder: explicit `themeFileApi(opts)` argument > matching key in `live-tokens.config.json` > `<dataDir>/<sub>`. The dev server reads the file once at startup restart vite to pick up changes.
53
+ A project last opened on 0.47.1 or earlier keeps its colors and type in `themes/` and its whole looks in `manifests/`, the names 0.48 reassigned. The plugin recognises that layout, writes nothing, and says so. `npx live-tokens migrate` moves `themes/` to `colors-and-type/` and `manifests/` to `themes/`, records what the retired per-layer pointers resolved to as the production theme, and clears them. Restart the dev server afterwards.
83
54
 
84
55
  ### Bootstrap in `main.ts`
85
56
 
@@ -94,28 +65,7 @@ import App from './App.svelte';
94
65
  bootLiveTokens(App, '#app');
95
66
  ```
96
67
 
97
- `bootLiveTokens` orchestrates the editor's idempotent init hooks, fetches the
98
- active theme in dev, optionally registers consumer-authored components, and
99
- mounts the app. FontAwesome is side-effect-imported by the bootstrap (the dev
100
- overlay always needs icons). The three token-CSS imports stay with the
101
- consumer because order matters and `tokens.generated.css` is project-local.
102
-
103
- Pass `components` to register consumer-authored editable components:
104
-
105
- ```ts
106
- import MyWidgetEditor, { allTokens as myWidgetTokens } from './components/MyWidgetEditor.svelte';
107
-
108
- bootLiveTokens(App, '#app', {
109
- components: [{
110
- id: 'mywidget',
111
- label: 'My Widget',
112
- icon: 'fas fa-magic',
113
- sourceFile: 'src/components/MyWidget.svelte',
114
- editorComponent: MyWidgetEditor,
115
- schema: myWidgetTokens,
116
- }],
117
- });
118
- ```
68
+ `bootLiveTokens` runs the editor's idempotent init hooks, fetches the active theme in dev, registers any consumer-authored components, and mounts the app. It side-effect-imports FontAwesome, because the dev overlay always needs icons. The three token-CSS imports stay with you: order matters, and `tokens.generated.css` is project-local.
119
69
 
120
70
  ### Mount routes with `<LiveTokensRouter>`
121
71
 
@@ -130,26 +80,9 @@ bootLiveTokens(App, '#app', {
130
80
  }} />
131
81
  ```
132
82
 
133
- `<LiveTokensRouter>` owns the dev overlay (`<LiveEditorOverlay>` +
134
- `<ColumnsOverlay>`), the editor routes (`/live-tokens/editor`, `/live-tokens/components`, `/live-tokens/docs`), the
135
- in-app link-click interception, and the nav-rail/page-source plumbing the
136
- overlay needs. Each entry in `pages` is one of your routes; entries with a
137
- `label` appear in the overlay's nav rail. Pass pages as `lazy: () => import('./Page.svelte')`
138
- so each page's stylesheet side-effects only evaluate when that route is
139
- visited; pass `component: PageComponent` instead for an eagerly-imported
140
- page. The editor routes are dispatched internally, so you don't have to
141
- dynamic-import the library's editor pages yourself.
142
-
143
- For routes you can't enumerate ahead of time (a `/:id` or `/:slug`, a path
144
- prefix, or a page shown only when some condition holds), add a `resolve`
145
- function from the current path to a `RouteEntry`; return `null` to fall
146
- through. It's plain code, so params, prefixes, and gating are a regex and an
147
- `if`, and the package ships no route syntax of its own. Resolution order is
148
- `pages[path]`, then `resolve(path)`, then the `pages['/']` fallback, so adding
149
- `resolve` never changes how existing `pages` entries match. A resolved entry
150
- can carry `props`, letting one page component serve many paths (such as the
151
- matched id), and its `source` gives the dynamic route a working "Page Source"
152
- button just like a static one.
83
+ `<LiveTokensRouter>` owns the dev overlay (`<LiveEditorOverlay>` and `<ColumnsOverlay>`), the four editor routes, in-app link-click interception, and the nav-rail and page-source plumbing the overlay needs. Each entry in `pages` is one of your routes; entries with a `label` appear in the overlay's nav rail. Pass `lazy: () => import('./Page.svelte')` so each page's stylesheet side-effects evaluate only when that route is visited, or `component: PageComponent` for an eager import. The editor routes dispatch internally, so you never import the library's editor pages yourself.
84
+
85
+ For routes you cannot enumerate ahead of time (a `/:id`, a path prefix, a page shown only under some condition), add a `resolve` function from the current path to a `RouteEntry` and return `null` to fall through. Resolution order is `pages[path]`, then `resolve(path)`, then the `pages['/']` fallback, so adding `resolve` never changes how existing entries match. A resolved entry can carry `props`, letting one component serve many paths, and its `source` gives the dynamic route a working "Page Source" button.
153
86
 
154
87
  ```svelte
155
88
  <LiveTokensRouter
@@ -162,26 +95,9 @@ button just like a static one.
162
95
  />
163
96
  ```
164
97
 
165
- You can also relocate or disable a default editor route via the
166
- `editorRoutes` prop: `<LiveTokensRouter pages={…} editorRoutes={{ editor: '/admin/editor', components: false }} />`.
167
- Pass a string to move a route; pass `false` to remove the route entirely
168
- (no dispatch and, for `components`, no auto-injected nav-rail entry).
169
-
170
- The whole overlay surface is dev-only and tree-shakes out of production
171
- builds — no `{#if import.meta.env.DEV}` guards needed.
172
-
173
- ### Lower-level API (when you need it)
98
+ Relocate or disable an editor route with the `editorRoutes` prop: `<LiveTokensRouter pages={…} editorRoutes={{ editor: '/admin/editor', components: false }} />`. A string moves the route; `false` removes it, along with its nav-rail entry.
174
99
 
175
- `bootLiveTokens` and `<LiveTokensRouter>` are convenience wrappers. The
176
- individual init functions (`initCssVarSync`, `initRouter`,
177
- `initColumnsOverlay`, `initEditorStore`, `initializeTheme`),
178
- `<LiveEditorOverlay>`, `<ColumnsOverlay>`, and the editor page exports
179
- (`@motion-proto/live-tokens/editor`,
180
- `@motion-proto/live-tokens/component-editor-page`) all stay exported. Use
181
- them directly to build a custom shell: render arbitrary markup per route, host
182
- a foreign matcher, or drive the overlay yourself. You do **not** need this for
183
- dynamic or gated routes; reach for `resolve` above, which keeps the overlay,
184
- nav rail, and page-source intact.
100
+ The whole overlay surface is dev-only and tree-shakes out of production builds. No `{#if import.meta.env.DEV}` guards needed.
185
101
 
186
102
  ### Use components
187
103
 
@@ -195,16 +111,11 @@ nav rail, and page-source intact.
195
111
  <Button variant="primary">Save</Button>
196
112
  ```
197
113
 
198
- The components carry their own design-token aliases (declared inside each `.svelte` file). They'll pick up your `tokens.css` overrides automatically. Strip out the ones you don't use; nothing is forced.
114
+ Each component carries its own design-token aliases and picks up your `tokens.css` values automatically. Import only the ones you use.
199
115
 
200
116
  ### Styles
201
117
 
202
- Editor chrome (`ui-editor.css`, `ui-form-controls.css`) and the icon font are
203
- **auto-loaded by the editor pages themselves**; you don't import them. The
204
- only stylesheet a consumer needs is a `tokens.css` declaring the design-token
205
- CSS variables on `:root`.
206
-
207
- You can use the package's default as a starting point:
118
+ The editor pages load their own chrome (`ui-editor.css`, `ui-form-controls.css`) and the icon font. The only stylesheet you need is a `tokens.css` declaring the design-token CSS variables on `:root`.
208
119
 
209
120
  ```ts
210
121
  import '@motion-proto/live-tokens/app/tokens.css';
@@ -212,47 +123,40 @@ import '@motion-proto/live-tokens/app/site.css'; // optional: themed h1/p/a st
212
123
  import '@motion-proto/live-tokens/app/fonts.css'; // optional: Fraunces + Manrope @font-face
213
124
  ```
214
125
 
215
- …or copy `node_modules/@motion-proto/live-tokens/src/system/styles/tokens.css` into
216
- your project and edit. It stays hand-authored: the editor writes what you Adopt
217
- into the sidecar `tokens.generated.css`, never back into `tokens.css`.
126
+ Or copy `node_modules/@motion-proto/live-tokens/src/system/styles/tokens.css` into your project and edit it. It stays hand-authored: what you Adopt lands in the sidecar `tokens.generated.css`, never back in `tokens.css`.
218
127
 
219
- ## Consuming live-tokens from scratch
128
+ ### Lower-level API
220
129
 
221
- The minimum a consumer needs after `npm install @motion-proto/live-tokens`:
130
+ `bootLiveTokens` and `<LiveTokensRouter>` are wrappers. The individual init functions (`initCssVarSync`, `initRouter`, `initColumnsOverlay`, `initEditorStore`, `initializeTheme`), `<LiveEditorOverlay>`, `<ColumnsOverlay>`, and the editor page exports (`@motion-proto/live-tokens/editor`, `@motion-proto/live-tokens/component-editor-page`) are all exported. Use them to build a custom shell: arbitrary markup per route, a foreign matcher, or your own overlay wiring. Dynamic and gated routes do not need this; use `resolve` above, which keeps the overlay, nav rail, and page source intact.
222
131
 
223
- ```ts
224
- // src/main.ts
225
- import '@motion-proto/live-tokens/app/tokens.css';
226
- import { mount } from 'svelte';
227
- import App from './App.svelte';
132
+ ## Where data lands, and how to move it
228
133
 
229
- mount(App, { target: document.getElementById('app')! });
230
- ```
134
+ The plugin reads and writes under one folder, `src/live-tokens/data/`, which holds three subdirectories it owns: `colors-and-type/`, `themes/`, and `component-configs/`.
231
135
 
232
- ```svelte
233
- <!-- src/App.svelte -->
234
- <script lang="ts">
235
- import Editor from '@motion-proto/live-tokens/editor';
236
- </script>
136
+ `themes/` holds one file per whole look, plus `_active.json` naming the one the editor has open and `_production.json` naming the one your site ships. `colors-and-type/` and `component-configs/{comp}/` hold each layer's `default.json` baseline, any preset you save by name, and the `_working.json` buffer for edits you have not saved into the active theme. A buffer is a delta from the open theme, so ordinary theme switching leaves none behind.
237
137
 
238
- <Editor />
138
+ To move the data, create `live-tokens.config.json` at your project root:
139
+
140
+ ```json
141
+ {
142
+ "dataDir": "src/live-tokens/data"
143
+ }
239
144
  ```
240
145
 
241
- ```ts
242
- // vite.config.ts
243
- import { defineConfig } from 'vite';
244
- import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';
146
+ All four keys are optional. `dataDir` relocates all three subfolders at once. The per-folder overrides cover unusual layouts, such as a monorepo where colors and type are shared across packages but component configs are not:
245
147
 
246
- export default defineConfig({
247
- plugins: [svelte({ preprocess: vitePreprocess() })],
248
- });
148
+ ```json
149
+ {
150
+ "dataDir": "src/live-tokens/data",
151
+ "colorsAndTypeDir": "../shared/colors-and-type",
152
+ "componentConfigsDir": "src/live-tokens/data/component-configs",
153
+ "themesDir": "src/live-tokens/data/themes"
154
+ }
249
155
  ```
250
156
 
251
- No `css: 'injected'` workaround, no `optimizeDeps` excludes `vite build` works as-is. (You'll want the full `themeFileApi` plugin and `bootLiveTokens` / `<LiveTokensRouter>` from the Quick install section above when you're ready to persist edits to disk and ship a real app.)
252
-
253
- ## Greenfield? Scaffold a new app
157
+ Resolution order, per folder: an explicit `themeFileApi(opts)` argument, then the matching key in `live-tokens.config.json`, then `<dataDir>/<sub>`. The dev server reads the file once at startup, so restart Vite to pick up changes.
254
158
 
255
- If you're starting from scratch, skip the manual wiring:
159
+ ## Scaffold a new app
256
160
 
257
161
  ```bash
258
162
  npx @motion-proto/live-tokens create my-app
@@ -261,22 +165,20 @@ npm install
261
165
  npm run dev
262
166
  ```
263
167
 
264
- This generates a Svelte + Vite app that **depends on** the package `vite.config.ts`, `main.ts`, `App.svelte`, the `themeFileApi` plugin, and a placeholder `src/pages/Home.svelte` are all pre-wired. The token CSS is seeded from the version you scaffolded against, so it never drifts. Open http://localhost:5173 and replace `Home.svelte` with your content; upgrade the package later with `npm update`.
265
-
266
- (The older `npx degit motionproto/live-tokens` route cloned this whole repo as your app — the package's source, tests, and all. `create` gives you a thin consumer app instead.)
168
+ This generates a Svelte + Vite app that depends on the package, with `vite.config.ts`, `main.ts`, `App.svelte`, the `themeFileApi` plugin, and a placeholder `src/pages/Home.svelte` already wired. The token CSS is seeded from the version you scaffolded against. Open http://localhost:5173, replace `Home.svelte` with your content, and upgrade later with `npm update`.
267
169
 
268
170
  ## Recommended project layout
269
171
 
270
- `create` scaffolds the preferred integration surface. Whether you scaffolded or wired up by hand, conforming to this layout keeps upgrades non-destructive and consistent across projects.
172
+ `create` scaffolds this layout. Conforming to it by hand keeps upgrades non-destructive and projects consistent.
271
173
 
272
174
  ```
273
175
  src/
274
- main.ts # token CSS chain bootLiveTokens(App, '#app')
176
+ main.ts # token CSS chain, then bootLiveTokens(App, '#app')
275
177
  App.svelte # routes (e.g. <LiveTokensRouter {pages} />)
276
178
  pages/ # your pages
277
179
  styles/site.css # your themed page typography (yours to edit)
278
- system/styles/tokens.css # vendored Layer-1 tokens committed
279
- live-tokens/data/ # editor state committed
180
+ system/styles/tokens.css # vendored Layer-1 tokens, committed
181
+ live-tokens/data/ # editor state, committed
280
182
  tokens.generated.css # editor output
281
183
  themes/ # one file per whole look, plus the two pointers
282
184
  colors-and-type/ component-configs/
@@ -284,17 +186,51 @@ vite.config.ts # svelte({ preprocess: vitePreprocess() }) + t
284
186
  svelte.config.js # vitePreprocess()
285
187
  ```
286
188
 
287
- Conventions that make this work:
189
+ Conventions that make it work:
190
+
191
+ - **Vendor `tokens.css` into `src/` and commit it.** Point `themeFileApi({ tokensCssPath })` at that file, never at one inside `node_modules`, which `npm install` wipes.
192
+ - **Keep all editable state under `src/` and commit it**: `tokens.css`, `tokens.generated.css`, and everything in `live-tokens/data/`. This invariant is what makes upgrades safe, since `npm install` only touches `node_modules`, `package.json`, and the lockfile.
193
+ - **Nothing is backed up for you.** The dev server keeps no snapshots, so git is the safety net. Commit a theme you care about before editing over it.
194
+ - **Preprocess with `vitePreprocess()`** (bundled in `@sveltejs/vite-plugin-svelte`) and keep `sass` installed for the components' SCSS. No `svelte-preprocess`, no `legacy-peer-deps`.
195
+ - **Import only from the public surface**: `@motion-proto/live-tokens`, `/components/*`, `/vite-plugin`, `/app/*`.
196
+
197
+ ## Minimal setup without the plugin
198
+
199
+ The least a consumer needs after `npm install @motion-proto/live-tokens`:
200
+
201
+ ```ts
202
+ // src/main.ts
203
+ import '@motion-proto/live-tokens/app/tokens.css';
204
+ import { mount } from 'svelte';
205
+ import App from './App.svelte';
206
+
207
+ mount(App, { target: document.getElementById('app')! });
208
+ ```
209
+
210
+ ```svelte
211
+ <!-- src/App.svelte -->
212
+ <script lang="ts">
213
+ import Editor from '@motion-proto/live-tokens/editor';
214
+ </script>
288
215
 
289
- - **Vendor `tokens.css` into `src/` and commit it.** Point `themeFileApi({ tokensCssPath })` at that file, not at one inside `node_modules`. The dev server writes your edits there; a copy under `node_modules` is wiped on every `npm install`.
290
- - **All editable state lives under `src/` and is committed** — `tokens.css`, `tokens.generated.css`, and everything in `live-tokens/data/`. This is the invariant that makes upgrades safe: `npm install` only ever touches `node_modules` + `package.json` + the lockfile, never your `src/`.
291
- - **Nothing is backed up for you.** The dev server keeps no snapshots, so git is the safety net: commit a theme you care about before editing over it.
292
- - **Preprocess with `vitePreprocess()`** (bundled in `@sveltejs/vite-plugin-svelte`), keeping `sass` installed for the components' `scss`. No `svelte-preprocess`, no `legacy-peer-deps` `.npmrc` — the dependency tree resolves cleanly on its own (since 0.19.1).
293
- - **Import only from the public surface** — `@motion-proto/live-tokens`, `/components/*`, `/vite-plugin`, `/app/*`.
216
+ <Editor />
217
+ ```
218
+
219
+ ```ts
220
+ // vite.config.ts
221
+ import { defineConfig } from 'vite';
222
+ import { svelte, vitePreprocess } from '@sveltejs/vite-plugin-svelte';
223
+
224
+ export default defineConfig({
225
+ plugins: [svelte({ preprocess: vitePreprocess() })],
226
+ });
227
+ ```
228
+
229
+ `vite build` works as-is: no `css: 'injected'` workaround, no `optimizeDeps` excludes. Add `themeFileApi` and `bootLiveTokens` from the Install section when you want edits persisted to disk.
294
230
 
295
231
  ## Consumer-authored components
296
232
 
297
- The shipped components are first-party by default, but you can author your own and get the same real-time editing experience. Co-locate runtime and editor files in `src/components/` (or `src/system/components/`, both are scanned by default) and pass them to `bootLiveTokens`:
233
+ The shipped components are first-party, but you can author your own and get the same live editing. Co-locate the runtime and editor files in `src/components/` or `src/system/components/`, then pass them to `bootLiveTokens`:
298
234
 
299
235
  ```ts
300
236
  // src/main.ts
@@ -314,21 +250,30 @@ bootLiveTokens(App, '#app', {
314
250
  });
315
251
  ```
316
252
 
317
- (`bootLiveTokens` calls `registerComponent` internally for each entry, gated on `import.meta.env.DEV` so the registration tree-shakes out of production builds. Call `registerComponent` directly if you need finer control over timing.)
253
+ `bootLiveTokens` calls `registerComponent` for each entry, gated on `import.meta.env.DEV` so registration tree-shakes out of production. Call `registerComponent` directly if you need finer control over timing.
318
254
 
319
- The component appears in the `/live-tokens/components` page under a **CUSTOM** group in the nav rail. Token rows, linked-block sharing, per-component config persistence, and reset-to-default work identically to the built-in set. All imports must come from `@motion-proto/live-tokens` or `@motion-proto/live-tokens/component-editor`; never deep-import from `src/`.
255
+ The component appears on `/live-tokens/components` under a **CUSTOM** group. Token rows, linked-block sharing, per-component config persistence, and reset-to-default behave exactly as they do for the built-in set. Import only from `@motion-proto/live-tokens` or `@motion-proto/live-tokens/component-editor`; never deep-import from `src/`.
320
256
 
321
- ## Claude Code skills
257
+ ## CLI
322
258
 
323
- The package ships a suite of Claude Code skills that encode the project's conventions so Claude can drive the package in plain English. They cover the jobs the README itself can't carry well: deciding which shipped component fits a need, composing a page from the catalogue, generating a color theme from a mood brief, adjusting shape and space from plain language, and (for the long-tail case) authoring a new editable component. Each skill auto-triggers from natural-language requests — no slash commands. (Plain `npm install` plus the README handle first-time setup.)
259
+ ```bash
260
+ npx @motion-proto/live-tokens <command>
261
+ ```
262
+
263
+ | Command | What it does |
264
+ |---|---|
265
+ | `create <dir> [--force]` | Scaffold a new Svelte + Vite app wired up with live-tokens. |
266
+ | `setup-claude [--force]` | Install the bundled Claude Code skills into `./.claude/skills/`. |
267
+ | `check-component <id>` | Validate a component's runtime, editor, and registration against the authoring contract. |
268
+ | `generate-theme <brief.json> [--no-activate] [--dry-run] [--carry-from <name>]` | Build a full theme from a 10-seed OKLCH brief, enforce AA contrast, write `themes/<slug>.json`, and open it. |
269
+ | `adjust <ops.json> [--dry-run]` | Move radius, padding, gap, and border-width aliases along their token scales. |
270
+ | `migrate [--check] [--write] [--tokens <path>]` | Reconcile the project with the installed package: additive `tokens.css` migrations, the pre-0.48 data-tree move, and a report on source references to the routes that moved in 0.35.0. |
324
271
 
325
- | Skill | Triggers on | What it knows |
326
- |--------------------------------|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
327
- | `live-tokens-build-page` | "build a pricing page using live-tokens components" | shipped-component catalogue, column grid, `pageSources` registration, token-only styling rule |
328
- | `live-tokens-pick-component` | "what's the difference between TabBar and SegmentedControl?" | decision tables for each confusable family (selection, container, messaging, on/off); when to author a new one instead |
329
- | `live-tokens-create-component` | "author a new Toggle component for my live-tokens project" | runtime + editor + `registerComponent()` recipe, naming scheme, state model, public-imports rule, verification checklist |
330
- | `live-tokens-generate-theme` | "make me a bright and cheerful color theme" | mood → OKLCH seed framework (chroma budget, per-role bands, gamut guardrails, holiday palettes); drives `npx live-tokens generate-theme`, which enforces AA contrast |
331
- | `live-tokens-adjust-shape-space` | "make the buttons pill shaped", "space it out" | shape and space idioms (pill, sharper, softer, tighter, airier) → radius/padding/gap/border-width ops; drives `npx live-tokens adjust`, which moves aliases along the shipped token scales |
272
+ Once installed in a project, the same commands are available as `npx live-tokens <command>`.
273
+
274
+ ## Claude Code skills
275
+
276
+ The package bundles five Claude Code skills. They encode the conventions this README cannot carry in full: which component fits a need, how a page is wired, what a valid theme looks like in OKLCH, and how shape and space move along the token scales. Each triggers from an ordinary request, so there are no slash commands to learn.
332
277
 
333
278
  ### Install
334
279
 
@@ -336,60 +281,92 @@ The package ships a suite of Claude Code skills that encode the project's conven
336
281
  npx @motion-proto/live-tokens setup-claude
337
282
  ```
338
283
 
339
- Copies every bundled skill into `./.claude/skills/` in the current directory. Re-run after upgrading the package to pick up new or updated skills (pass `--force` to overwrite). macOS/Linux only.
340
-
341
- If you'd rather avoid the CLI, the equivalent one-liner:
284
+ This copies every bundled skill into `./.claude/skills/` in the current directory. Re-run it after upgrading the package to pick up new and changed skills, adding `--force` to overwrite. macOS and Linux only. The equivalent by hand:
342
285
 
343
286
  ```bash
344
287
  mkdir -p .claude/skills && cp -R node_modules/@motion-proto/live-tokens/.claude/skills/. .claude/skills/
345
288
  ```
346
289
 
347
- ### Validate authored components
290
+ ### `live-tokens-pick-component`
291
+
292
+ Ask "TabBar or SegmentedControl?", "how do I let someone pick one of four options?", or "what is the difference between a Callout and a Notification?".
293
+
294
+ The skill holds the catalogue grouped by job (action, input, selection, containers, messaging, display) and a decision table for each confusable family: `SegmentedControl` vs `TabBar` vs `RadioButton` vs `MenuSelect`, `Card` vs `CollapsibleSection` vs `Dialog`, `Callout` vs `Notification` vs `Tooltip` vs `Badge`, `Button` vs `IconButton`, and the on/off case. It answers the question and writes nothing. Read it before authoring anything new.
348
295
 
349
- The same CLI ships a static validator that turns the `create-component` skill's verification checklist into a runnable command:
296
+ ### `live-tokens-build-page`
297
+
298
+ Ask for a page, a route, or a screen: "build a pricing page", "add a /settings route", "put a hero at the top of Home".
299
+
300
+ The skill composes the page from shipped components, styles every value with `var(--token-*)` (no hex, no pixel literals, so editor changes repaint the page), places content on the column grid via `--columns-count`, `--columns-gutter`, and `--columns-max-width`, adds the route as a `lazy` import with a `source` so the overlay's "Page Source" button works, and imports `site.css` from the page rather than `main.ts` so page CSS stays out of the editor routes. It writes your page files and the route entry, and never touches the data tree.
301
+
302
+ ### `live-tokens-generate-theme`
303
+
304
+ Ask for a look: "a dark, moody night theme", "a St Patrick's Day theme in green and gold", "warmer", "more contrast", "calmer".
305
+
306
+ The skill translates the brief into ten OKLCH seeds (Brand, Accent, Special, Canvas, Neutral, Alternate, Info, Success, Warning, Danger) plus a light or dark scheme, then runs `npx live-tokens generate-theme <brief.json>`. The CLI assembles the curves, enforces AA contrast on derived text tokens and auto-corrects where it can, writes `themes/<slug>.json`, opens it, and prints a contrast report. Exit 1 means the seeds themselves are unworkable, and each failure line names the seed to change.
307
+
308
+ Most of the skill is the judgment the generator cannot supply: a chroma budget scaled to how much screen area each palette covers, per-role lightness and hue bands for each scheme, gamut guardrails against impossible seeds, harmony modes, the optional canvas gradient, shadow weight for the canvas, and OKLCH anchors for named colors.
309
+
310
+ Scope: colors only. Fonts, gradients, and component aliases carry forward from the open theme, or from `--carry-from <name>`. `--dry-run` prints the report without writing; `--no-activate` writes without opening. Opening a theme never changes what your site ships; Adopt does. Regenerating replaces that theme's whole color state, including palette edits made in the editor since the last run.
311
+
312
+ ### `live-tokens-adjust-shape-space`
313
+
314
+ Ask for shape or space: "make the buttons pill shaped", "sharper corners on the cards", "space it out", "tighter", "thinner borders".
315
+
316
+ The skill turns the phrase into ops (`kind` of `radius`, `padding`, `gap`, or `border-width`, with `shift: N` or `set: <token>`, optionally scoped to one component id), then runs `npx live-tokens adjust <ops.json>`. The CLI moves each matching alias along its ladder, reads the live config first so "a bit more" compounds, and prints every change and every skip.
317
+
318
+ It also knows where these edits go wrong: controls run out of room long before containers do, so a global compaction is one step and anything deeper is aimed at named containers; a pill needs more horizontal padding than a square-cornered control, not less; and content insets stop at `--space-4`, below which a relative "tighter" reports as clamped instead of writing.
319
+
320
+ Edits land in each affected component's `_working.json` buffer, which is what the running page reads, so save the open theme in the editor to keep them. `--dry-run` reports without writing, and the inverse op is the undo.
321
+
322
+ ### `live-tokens-create-component`
323
+
324
+ Ask for something the catalogue lacks: "author a Rating component", "make my Chip component editable in the editor".
325
+
326
+ The skill covers the four-step recipe: the runtime `.svelte` file with its `:global(:root)` token block, the editor `.svelte` file exporting `allTokens` and its variant groups, the `registerComponent()` call, and the catalogue entry that keeps `live-tokens-pick-component` current. It carries the naming scheme, the token suffix vocabulary, the state model (component states such as selected and disabled are separate from interaction states such as hover), linked siblings, the public-imports rule, and the shipped `Toggle` as a worked example from runtime file to registration.
327
+
328
+ Verify the result:
350
329
 
351
330
  ```bash
352
331
  npx @motion-proto/live-tokens check-component <id>
353
332
  ```
354
333
 
355
- It enforces the file layout, `:global(:root)` block, token-suffix vocabulary, the state-before-property rule, the no-raw-colour-defaults rule, the public-imports rule, and the `registerComponent({ id })` call. Exit code 0 means the static contract is met. Useful both as a self-check after Claude generates a component and as a pre-commit guard on human-authored ones.
334
+ The validator checks the file layout, the `:global(:root)` block, the token-suffix vocabulary, the state-before-property rule, the no-raw-color-defaults rule, the public-imports rule, and the `registerComponent({ id })` call. Exit code 0 means the static contract is met. Use it after Claude generates a component, and as a pre-commit guard on hand-authored ones.
356
335
 
357
- ## How the editor ships changes to prod
336
+ ## How the editor ships changes to production
358
337
 
359
- 1. Edit in `/live-tokens/editor` or `/live-tokens/components`. Your edits sit in the working buffer (`_working.json`); **Save** in the Theme panel captures that buffer into the open theme at `<dataDir>/themes/{name}.json`.
338
+ 1. Edit on `/live-tokens/editor`, `/live-tokens/colors`, or `/live-tokens/components`. Edits sit in the working buffer (`_working.json`). **Save** in the Theme panel captures the buffer into the open theme at `<dataDir>/themes/{name}.json`.
360
339
  2. **Adopt** the theme. It becomes the production theme, and its variables are baked into `tokens.generated.css` next to your authored `tokens.css`. Nothing else writes that file, so trying a look never changes what you ship.
361
- 3. `npm run build` bundles both as plain CSS. No editor code, no JSON lookups, no dev surfaces ship to prod.
362
-
363
- ## File ownership — what the plugin writes
340
+ 3. `npm run build` bundles both as plain CSS. No editor code, no JSON lookups, no dev surfaces reach production.
364
341
 
365
- Knowing which files the plugin touches matters when upgrading the package or working in a repo you don't want overwritten. For a plain-language version of how your saved look stays safe across upgrades while `tokens.css` holds the building blocks, see [TOKENS.md](./TOKENS.md).
342
+ ## File ownership: what the plugin writes
366
343
 
367
- **On `npm install` or `npm update`: nothing outside `node_modules/`.** No install hooks. Upgrading versions never touches your `src/live-tokens/data/`, or any file in `src/` outside it.
344
+ Knowing which files the plugin touches matters when you upgrade the package or work in a repo you do not want overwritten. For how a saved look stays safe across upgrades while `tokens.css` holds the building blocks, see [TOKENS.md](./TOKENS.md).
368
345
 
369
- **The plugin only writes inside two locations on disk:**
346
+ **On `npm install` or `npm update`: nothing outside `node_modules/`.** There are no install hooks. Upgrading never touches `src/live-tokens/data/` or any other file in `src/`.
370
347
 
371
- - `src/live-tokens/data/` (configurable via `live-tokens.config.json` see "Where data lands").
372
- - The CSS sidecars next to your `tokensCssPath` (`tokens.generated.css`, `fonts.css`).
348
+ **The plugin writes in two places only:**
373
349
 
374
- It never writes to your project root, your `src/` outside the data folder, or anywhere else.
350
+ - `src/live-tokens/data/`, configurable through `live-tokens.config.json`.
351
+ - The CSS sidecars next to your `tokensCssPath`: `tokens.generated.css` and `fonts.css`.
375
352
 
376
- **At dev-server startup, the plugin fills gaps and refreshes its own derived files it never overwrites authored ones:**
353
+ **At dev-server startup it fills gaps and refreshes its own derived files, and overwrites no authored file:**
377
354
 
378
- - `<dataDir>/themes/default.json` the derived Default theme, regenerated when the shipped colors and type or a component default changes.
379
- - `<dataDir>/themes/_active.json` and `_production.json` written **only if missing**, and healed when they name a theme that no longer resolves.
380
- - `<dataDir>/component-configs/{comp}/default.json` regenerated from the component's `:global(:root)` block **only when the `.svelte` source is newer than the existing default**. This file is a build artifact of the source; don't hand-edit it.
381
- - `<tokensCssPath sibling>/tokens.generated.css` rebaked from the production theme, so a fresh checkout builds against the look you shipped.
355
+ - `<dataDir>/themes/default.json`, the derived Default theme, regenerated when the shipped colors and type or a component default changes.
356
+ - `<dataDir>/themes/_active.json` and `_production.json`, written only when missing, and healed when they name a theme that no longer resolves.
357
+ - `<dataDir>/component-configs/{comp}/default.json`, regenerated from the component's `:global(:root)` block only when the `.svelte` source is newer than the existing file. It is a build artifact of the source, so do not hand-edit it.
358
+ - `tokens.generated.css` beside your `tokensCssPath`, rebaked from the production theme so a fresh checkout builds against the look you shipped.
382
359
 
383
- **At dev-time editor actions, these files get rewritten by what you do:**
360
+ **Editor actions rewrite these:**
384
361
 
385
- - `<dataDir>/colors-and-type/_working.json` and `<dataDir>/component-configs/{comp}/_working.json` the buffer, written as you save each part of the look and cleared when a theme you open does not carry it.
386
- - `<dataDir>/themes/{name}.json` every Save and Save As in the Theme panel.
387
- - `<dataDir>/colors-and-type/{name}.json` and `<dataDir>/component-configs/{comp}/{name}.json` only when you save a preset by name. Nothing machine-written lands among them.
388
- - `<tokensCssPath sibling>/tokens.generated.css` and `fonts.css` regenerated from the production theme when you Adopt.
362
+ - `<dataDir>/colors-and-type/_working.json` and `<dataDir>/component-configs/{comp}/_working.json`, the buffers, written as you edit and cleared when a theme you open does not carry them.
363
+ - `<dataDir>/themes/{name}.json`, on every Save and Save As in the Theme panel.
364
+ - `<dataDir>/colors-and-type/{name}.json` and `<dataDir>/component-configs/{comp}/{name}.json`, only when you save a preset by name.
365
+ - `tokens.generated.css` and `fonts.css`, regenerated from the production theme when you Adopt.
389
366
 
390
- The developer-authored `tokens.css` itself is **never written** by the plugin — it holds defaults you're free to hand-edit. The editor's overrides land in the sidecar `tokens.generated.css`, which the package imports immediately after `tokens.css`.
367
+ The plugin never writes your authored `tokens.css`. It holds defaults you are free to hand-edit, and the editor's overrides land in `tokens.generated.css`, which the app imports immediately after it.
391
368
 
392
- The one exception is the opt-in `themeFileApi({ autoMigrate: true })` option. When enabled, the dev server applies pending **additive** token migrations (new token names only) to your `tokens.css` at startup and writes the file, so it stays current with the package as you upgrade. The change shows up in git for review. Breaking migrations (rename/remove) are never auto-applied; run `npx live-tokens migrate` for those during a deliberate upgrade. Off by default, so the "never written" rule holds unless you turn it on. See [TOKENS.md](./TOKENS.md).
369
+ The one exception is `themeFileApi({ autoMigrate: true })`. With it enabled, the dev server applies pending additive token migrations (new token names only) to `tokens.css` at startup and writes the file, so it keeps up with the package as you upgrade. The change shows up in git for review. Breaking migrations that rename or remove tokens are never applied automatically; run `npx live-tokens migrate` for those during a deliberate upgrade. The option is off by default. See [TOKENS.md](./TOKENS.md).
393
370
 
394
371
  ## License
395
372
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.54.0",
3
+ "version": "0.54.2",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
6
6
  "keywords": [
@@ -9,6 +9,9 @@ export type { BootLiveTokensOptions } from './bootstrap';
9
9
  export { columnsVisible, toggleColumns, init as initColumnsOverlay } from './overlay/columnsOverlay';
10
10
  export { configureEditor, storageKey } from './core/store/editorConfig';
11
11
  export { openThemeSlug } from './core/store/editorConfigStore';
12
+ // Hand a palette family to a view. Deep links into the editor need these:
13
+ // the one-shot focus store they drive stays internal.
14
+ export { openPaletteInTokens, openPaletteInWheel, selectedPalette } from './core/store/paletteFocus';
12
15
  export { init as initRouter, route, navigate, setScrollReset } from './core/routing/router';
13
16
  export { init as initCssVarSync } from './core/cssVarSync';
14
17
  export {
@@ -545,7 +545,7 @@
545
545
  {/snippet}
546
546
  </PaletteBase>
547
547
 
548
- {#if paletteEditorOpen || (isEditingBase && panelOpen && editingColor)}
548
+ <UIReveal open={paletteEditorOpen || (isEditingBase && panelOpen && !!editingColor)}>
549
549
  <div class="swatch-grid" style="--swatch-cols: {paletteStepLightness.length + 2}">
550
550
  <div class="base-panel" style="grid-column: 2 / {paletteStepLightness.length + 2}">
551
551
  <ColorEditPanel
@@ -577,7 +577,7 @@
577
577
  </ColorEditPanel>
578
578
  </div>
579
579
  </div>
580
- {/if}
580
+ </UIReveal>
581
581
 
582
582
  <!-- Palette + Text row -->
583
583
  <div class="scales-row">
@@ -9,6 +9,7 @@
9
9
 
10
10
  .ftt-stage {
11
11
  --ftt-contrast-color: var(--color-white);
12
+ --ftt-string-opacity: 0.5;
12
13
  position: relative;
13
14
  width: 100%;
14
15
  height: 100%;
@@ -79,7 +80,7 @@
79
80
  stroke-width: 2;
80
81
  stroke-linecap: round;
81
82
  vector-effect: non-scaling-stroke;
82
- opacity: 0.5;
83
+ opacity: var(--ftt-string-opacity);
83
84
  transition: opacity 150ms ease, stroke 150ms ease;
84
85
  }
85
86
 
@@ -178,16 +179,18 @@
178
179
  align-items: center;
179
180
  gap: 0.25rem;
180
181
  padding: 0.375rem 0.75rem;
181
- /* Darkest stop stays #8f9090 so black text keeps ~6:1 AA contrast. */
182
+ /* Polished chrome, not brushed steel: the bands keep their spacing but sit
183
+ high in the scale, so the pill stays bright against a cream page. Keep
184
+ the darkest stop ≥ #8f9090 or the black label loses AA. */
182
185
  background: linear-gradient(
183
186
  135deg,
184
- #e4e5e5 0%,
185
- #a2a3a3 38%,
186
- #cdcece 62%,
187
- #8f9090 100%
187
+ #fbfbfb 0%,
188
+ #c9cbcb 38%,
189
+ #eff0f0 62%,
190
+ #b6b8b8 100%
188
191
  );
189
192
  color: #000;
190
- border: 1px solid rgba(255, 255, 255, 0.55);
193
+ border: 1px solid rgba(86, 96, 104, 0.45);
191
194
  border-radius: 9999px;
192
195
  box-shadow: var(--shadow-md);
193
196
  font-family: Manrope, system-ui, -apple-system, sans-serif;
@@ -200,6 +200,9 @@
200
200
  lastPageBackground = pageBackground;
201
201
  const token = contrastTokenForBackground(pageBackground);
202
202
  stageEl.style.setProperty('--ftt-contrast-color', `var(${token})`);
203
+ // Dark strokes on a light page read far heavier than white ones on a dark
204
+ // page at the same alpha, so the light theme gets a thinner string.
205
+ stageEl.style.setProperty('--ftt-string-opacity', token === '--color-black' ? '0.3' : '0.5');
203
206
  }
204
207
 
205
208
  function syncBoxLabelContrast() {