@motion-proto/live-tokens 0.9.0 → 0.11.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 (65) hide show
  1. package/README.md +50 -29
  2. package/dist-plugin/index.cjs +177 -125
  3. package/dist-plugin/index.d.cts +3 -2
  4. package/dist-plugin/index.d.ts +3 -2
  5. package/dist-plugin/index.js +177 -125
  6. package/package.json +4 -1
  7. package/src/editor/component-editor/BadgeEditor.svelte +44 -42
  8. package/src/editor/component-editor/ButtonEditor.svelte +224 -0
  9. package/src/editor/component-editor/CardEditor.svelte +2 -0
  10. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +1 -7
  11. package/src/editor/component-editor/CornerBadgeEditor.svelte +44 -34
  12. package/src/editor/component-editor/ImageLightboxEditor.svelte +58 -0
  13. package/src/editor/component-editor/InputEditor.svelte +272 -0
  14. package/src/editor/component-editor/NotificationEditor.svelte +44 -65
  15. package/src/editor/component-editor/ProgressBarEditor.svelte +71 -87
  16. package/src/editor/component-editor/SegmentedControlEditor.svelte +98 -37
  17. package/src/editor/component-editor/SideNavigationEditor.svelte +342 -0
  18. package/src/editor/component-editor/registry.ts +35 -2
  19. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +3 -2
  20. package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +10 -1
  21. package/src/editor/component-editor/scaffolding/StateBlock.svelte +9 -10
  22. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +60 -36
  23. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +38 -1
  24. package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -1
  25. package/src/editor/component-editor/scaffolding/siblings.ts +2 -2
  26. package/src/editor/component-editor/scaffolding/types.ts +2 -1
  27. package/src/editor/core/components/componentConfigService.ts +7 -6
  28. package/src/editor/core/manifests/manifestService.ts +5 -4
  29. package/src/editor/core/storage/apiBase.ts +15 -0
  30. package/src/editor/core/storage/files/versionedFileResourceClient.ts +1 -1
  31. package/src/editor/core/store/editorRenderer.ts +1 -4
  32. package/src/editor/core/store/editorStore.ts +5 -9
  33. package/src/editor/core/store/editorTypes.ts +6 -13
  34. package/src/editor/core/themes/migrations/2026-05-13-primary-to-brand.ts +2 -29
  35. package/src/editor/core/themes/migrations/2026-05-24-collapsiblesection-drop-active-state.ts +28 -0
  36. package/src/editor/core/themes/migrations/2026-05-24-progressbar-collapse-variants.ts +41 -0
  37. package/src/editor/core/themes/migrations/2026-05-24-promote-state-shared-tokens.ts +59 -0
  38. package/src/editor/core/themes/migrations/2026-05-24-segmentedcontrol-divider-inset.ts +29 -0
  39. package/src/editor/core/themes/migrations/2026-05-25-cornerbadge-flatten-variants.ts +46 -0
  40. package/src/editor/core/themes/migrations/2026-05-26-drop-overlay-extra-stops.ts +46 -0
  41. package/src/editor/core/themes/migrations/index.ts +16 -0
  42. package/src/editor/core/themes/slices/overlays.ts +44 -75
  43. package/src/editor/core/themes/themeInit.ts +3 -2
  44. package/src/editor/core/themes/themeService.ts +3 -2
  45. package/src/editor/ui/SurfacesTab.svelte +3 -7
  46. package/src/editor/ui/UIEasingSelector.svelte +240 -0
  47. package/src/editor/ui/UIPaddingSelector.svelte +2 -2
  48. package/src/editor/ui/UIPaletteSelector.svelte +151 -36
  49. package/src/editor/ui/{UIRelinkConfirmPopover.svelte → UIRelinkConfirmDialog.svelte} +107 -75
  50. package/src/editor/ui/UITokenSelector.svelte +15 -2
  51. package/src/editor/ui/sections/OverlaysSection.svelte +107 -540
  52. package/src/editor/ui/variantScales.ts +34 -0
  53. package/src/system/components/Button.svelte +34 -85
  54. package/src/system/components/Card.svelte +2 -1
  55. package/src/system/components/CollapsibleSection.svelte +1 -48
  56. package/src/system/components/CornerBadge.svelte +72 -138
  57. package/src/system/components/ImageLightbox.svelte +578 -0
  58. package/src/system/components/Input.svelte +387 -0
  59. package/src/system/components/ProgressBar.svelte +62 -258
  60. package/src/system/components/SegmentedControl.svelte +81 -15
  61. package/src/system/components/SideNavigation.svelte +777 -0
  62. package/src/system/components/TabBar.svelte +1 -1
  63. package/src/system/styles/tokens.css +48 -5
  64. package/src/system/styles/tokens.generated.css +33 -185
  65. package/src/editor/component-editor/StandardButtonsEditor.svelte +0 -190
@@ -1,13 +1,19 @@
1
1
  <script lang="ts">
2
2
  /**
3
- * Overlay channels (dark + hover) UI lifted from VariablesTab.
4
- *
5
- * State lives in $editorState.overlays. The store's subscriber fans the
6
- * rgba values out to :root, so this component only orchestrates mutations
7
- * and reads derived display state.
3
+ * Overlay + hover stops. Each stop binds an existing color token through
4
+ * `UIPaletteSelector`; the picker handles family/step selection and
5
+ * opacity, and writes route through `onwrite` into the overlays slice
6
+ * (see editorStore.makeDefaultOverlaysState). The slice fans the
7
+ * resulting color-mix expressions out to :root.
8
8
  */
9
- import { editorState, mutate, beginSliderGesture } from '../../core/store/editorStore';
10
- import type { OverlayToken, OverlayChannelGlobals, EditorState } from '../../core/store/editorTypes';
9
+ import { editorState, mutate } from '../../core/store/editorStore';
10
+ import type { OverlayToken } from '../../core/store/editorTypes';
11
+ import {
12
+ makeDefaultOverlayTokens,
13
+ makeDefaultHoverTokens,
14
+ parseOverlayCss,
15
+ } from '../../core/themes/slices/overlays';
16
+ import UIPaletteSelector from '../UIPaletteSelector.svelte';
11
17
 
12
18
  interface Props {
13
19
  copiedVar?: string | null;
@@ -16,96 +22,36 @@
16
22
 
17
23
  let { copiedVar = null, oncopy }: Props = $props();
18
24
 
19
- function copy(v: string) { oncopy?.(v); }
20
-
21
- let editingOverlay: string | null = $state(null);
22
-
23
- function clampNum(v: number, lo: number, hi: number): number {
24
- return Math.max(lo, Math.min(hi, Math.round(v)));
25
- }
26
-
27
- function getOverlayCss(t: OverlayToken): string {
28
- return `rgba(${t.r}, ${t.g}, ${t.b}, ${t.opacity})`;
29
- }
30
-
31
- function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
32
- s /= 100; l /= 100;
33
- const c = (1 - Math.abs(2 * l - 1)) * s;
34
- const x = c * (1 - Math.abs((h / 60) % 2 - 1));
35
- const m = l - c / 2;
36
- let r1 = 0, g1 = 0, b1 = 0;
37
- if (h < 60) { r1 = c; g1 = x; }
38
- else if (h < 120) { r1 = x; g1 = c; }
39
- else if (h < 180) { g1 = c; b1 = x; }
40
- else if (h < 240) { g1 = x; b1 = c; }
41
- else if (h < 300) { r1 = x; b1 = c; }
42
- else { r1 = c; b1 = x; }
43
- return {
44
- r: Math.round((r1 + m) * 255),
45
- g: Math.round((g1 + m) * 255),
46
- b: Math.round((b1 + m) * 255),
47
- };
48
- }
49
-
50
- function applyChannelColor(tokens: OverlayToken[], g: OverlayChannelGlobals): void {
51
- const rgb = hslToRgb(g.hue, g.saturation, g.lightness);
52
- for (const t of tokens) { t.r = rgb.r; t.g = rgb.g; t.b = rgb.b; }
53
- }
54
- function applyChannelOpacity(tokens: OverlayToken[], g: OverlayChannelGlobals): void {
55
- const last = tokens.length - 1;
56
- tokens.forEach((t, i) => {
57
- const frac = last > 0 ? i / last : 0.5;
58
- t.opacity = Math.round((g.opacityMin + frac * (g.opacityMax - g.opacityMin)) * 100) / 100;
59
- });
60
- }
25
+ type Channel = 'overlay' | 'hover';
61
26
 
62
- type OverlayChannel = 'overlay' | 'hover';
63
- type ColorField = 'hue' | 'saturation' | 'lightness';
64
- type OpacityField = 'opacityMin' | 'opacityMax';
65
-
66
- function pickChannelTokens(s: EditorState, ch: OverlayChannel): OverlayToken[] {
67
- return ch === 'overlay' ? s.overlays.tokens : s.overlays.hoverTokens;
68
- }
69
-
70
- function setOverlayColor(ch: OverlayChannel, field: ColorField, value: number) {
71
- mutate(`${ch} ${field}`, (s) => {
72
- const g = s.overlays.globals[ch];
73
- if (field === 'hue') g.hue = clampNum(value, 0, 360);
74
- else if (field === 'saturation') g.saturation = clampNum(value, 0, 100);
75
- else g.lightness = clampNum(value, 0, 100);
76
- applyChannelColor(pickChannelTokens(s, ch), g);
77
- });
78
- }
79
-
80
- function setOverlayOpacity(ch: OverlayChannel, field: OpacityField, value01: number) {
81
- mutate(`${ch} ${field}`, (s) => {
82
- const g = s.overlays.globals[ch];
83
- const clamped = Math.max(0, Math.min(1, value01));
84
- if (field === 'opacityMin') g.opacityMin = clamped;
85
- else g.opacityMax = clamped;
86
- applyChannelOpacity(pickChannelTokens(s, ch), g);
87
- });
88
- }
27
+ function copy(v: string) { oncopy?.(v); }
89
28
 
90
- function setOverlayTokenOpacity(ch: OverlayChannel, idx: number, value01: number) {
91
- mutate(`${ch} token opacity`, (s) => {
92
- const arr = pickChannelTokens(s, ch);
29
+ /** Translate a UITokenSelector write payload into a slice mutation.
30
+ * - `null` → restore this stop's default alias + opacity (reset).
31
+ * - bare `--name` 100% opacity on that alias.
32
+ * - `color-mix(in srgb, var(--name) N%, transparent)` → alias + N%.
33
+ * - other (incl. `transparent`) → ignored; the picker's "None" option
34
+ * doesn't apply to overlays. */
35
+ function handleWrite(channel: Channel, idx: number, value: string | null) {
36
+ mutate(`${channel} ${idx} edit`, (s) => {
37
+ const arr = channel === 'overlay' ? s.overlays.tokens : s.overlays.hoverTokens;
93
38
  const t = arr[idx];
94
39
  if (!t) return;
95
- t.opacity = Math.max(0, Math.min(1, value01));
40
+ if (value === null) {
41
+ const defaults = channel === 'overlay' ? makeDefaultOverlayTokens() : makeDefaultHoverTokens();
42
+ const d = defaults[idx];
43
+ if (d) { t.alias = d.alias; t.opacity = d.opacity; }
44
+ return;
45
+ }
46
+ if (value.startsWith('--')) { t.alias = value; t.opacity = 1; return; }
47
+ const parsed = parseOverlayCss(value);
48
+ if (parsed) { t.alias = parsed.alias; t.opacity = parsed.opacity; }
96
49
  });
97
50
  }
98
51
 
99
- function overlayHueGrad(g: OverlayChannelGlobals): string {
100
- return `linear-gradient(to right, ${
101
- [0, 60, 120, 180, 240, 300, 360].map(h => `hsl(${h},${g.saturation}%,${g.lightness}%)`).join(',')
102
- })`;
103
- }
104
- function overlaySatGrad(g: OverlayChannelGlobals): string {
105
- return `linear-gradient(to right, hsl(${g.hue},0%,${g.lightness}%), hsl(${g.hue},100%,${g.lightness}%))`;
106
- }
107
- function overlayLightGrad(g: OverlayChannelGlobals): string {
108
- return `linear-gradient(to right, hsl(${g.hue},${g.saturation}%,0%), hsl(${g.hue},${g.saturation}%,50%), hsl(${g.hue},${g.saturation}%,100%))`;
52
+ function copyCss(t: OverlayToken): string {
53
+ const pct = Math.round(t.opacity * 100);
54
+ return pct >= 100 ? `var(${t.alias})` : `color-mix(in srgb, var(${t.alias}) ${pct}%, transparent)`;
109
55
  }
110
56
  </script>
111
57
 
@@ -114,223 +60,69 @@
114
60
 
115
61
  <h3 class="group-title">Dark Overlays</h3>
116
62
  <div class="overlays-grid">
117
- {#each $editorState.overlays.tokens as token, i}
118
- <div class="overlay-item">
63
+ {#each $editorState.overlays.tokens as token, i (token.variable)}
64
+ <div class="overlay-row">
119
65
  <div class="overlay-swatch-wrap">
120
- <div class="overlay-swatch" style="background: {getOverlayCss(token)};"></div>
66
+ <div class="overlay-swatch" style="background: var({token.variable});"></div>
121
67
  </div>
122
- <div class="token-info">
123
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} onclick={() => copy(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
68
+ <div class="overlay-meta">
69
+ <button
70
+ class="token-variable copyable"
71
+ class:copied={copiedVar === token.variable}
72
+ onclick={() => copy(token.variable)}
73
+ >{copiedVar === token.variable ? 'copied!' : token.variable}</button>
124
74
  <span class="token-value">{token.label} — {Math.round(token.opacity * 100)}%</span>
125
75
  </div>
126
- <button class="shadow-edit-btn" onclick={() => editingOverlay = editingOverlay === token.variable ? null : token.variable}>
127
- {editingOverlay === token.variable ? 'Close' : 'Edit'}
76
+ <div class="overlay-picker">
77
+ <UIPaletteSelector
78
+ variable={token.variable}
79
+ showNone={false}
80
+ onwrite={(v) => handleWrite('overlay', i, v)}
81
+ />
82
+ </div>
83
+ <button
84
+ class="copy-css-btn"
85
+ title="Copy resolved CSS"
86
+ onclick={() => copy(copyCss(token))}
87
+ >
88
+ {copiedVar === copyCss(token) ? 'copied!' : 'copy css'}
128
89
  </button>
129
- {#if editingOverlay === token.variable}
130
- <div class="shadow-editor">
131
- <div class="shadow-slider-row">
132
- <span class="shadow-slider-label">Opacity</span>
133
- <input type="range" min="0" max="100" value={Math.round(token.opacity * 100)}
134
- onpointerdown={() => beginSliderGesture(`edit ${token.variable} opacity`)}
135
- oninput={(e) => setOverlayTokenOpacity('overlay', i, +e.currentTarget.value / 100)} />
136
- <input class="shadow-slider-input" type="number" min="0" max="100"
137
- value={Math.round(token.opacity * 100)}
138
- onchange={(e) => setOverlayTokenOpacity('overlay', i, Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
139
- <span class="shadow-slider-unit">%</span>
140
- </div>
141
- <div class="shadow-css-output">
142
- <code>{getOverlayCss(token)}</code>
143
- <button class="shadow-copy-btn" onclick={() => copy(getOverlayCss(token))}>
144
- {copiedVar === getOverlayCss(token) ? 'Copied!' : 'Copy CSS'}
145
- </button>
146
- </div>
147
- </div>
148
- {/if}
149
90
  </div>
150
91
  {/each}
151
92
  </div>
152
93
 
153
- <!-- Global overlay editor -->
154
- <div class="overlay-global-editor">
155
- <h4 class="global-shadow-title">Global Overlay Controls</h4>
156
- <div class="overlay-global-columns">
157
- <div class="overlay-global-col">
158
- <div class="global-color-group">
159
- <div class="global-color-swatch" style="background: hsl({$editorState.overlays.globals.overlay.hue}, {$editorState.overlays.globals.overlay.saturation}%, {$editorState.overlays.globals.overlay.lightness}%);"></div>
160
- <div class="global-color-sliders">
161
- <div class="global-shadow-row">
162
- <span class="shadow-slider-label">H</span>
163
- <div class="slider-track" style="background: {overlayHueGrad($editorState.overlays.globals.overlay)}">
164
- <input type="range" min="0" max="360" value={$editorState.overlays.globals.overlay.hue}
165
- onpointerdown={() => beginSliderGesture('overlay hue')}
166
- oninput={(e) => setOverlayColor('overlay', 'hue', +e.currentTarget.value)} />
167
- </div>
168
- <input class="shadow-slider-input" type="number" min="0" max="360"
169
- value={$editorState.overlays.globals.overlay.hue}
170
- onchange={(e) => setOverlayColor('overlay', 'hue', +e.currentTarget.value)} />
171
- <span class="shadow-slider-unit">&deg;</span>
172
- </div>
173
- <div class="global-shadow-row">
174
- <span class="shadow-slider-label">S</span>
175
- <div class="slider-track" style="background: {overlaySatGrad($editorState.overlays.globals.overlay)}">
176
- <input type="range" min="0" max="100" value={$editorState.overlays.globals.overlay.saturation}
177
- onpointerdown={() => beginSliderGesture('overlay saturation')}
178
- oninput={(e) => setOverlayColor('overlay', 'saturation', +e.currentTarget.value)} />
179
- </div>
180
- <input class="shadow-slider-input" type="number" min="0" max="100"
181
- value={$editorState.overlays.globals.overlay.saturation}
182
- onchange={(e) => setOverlayColor('overlay', 'saturation', +e.currentTarget.value)} />
183
- <span class="shadow-slider-unit">%</span>
184
- </div>
185
- <div class="global-shadow-row">
186
- <span class="shadow-slider-label">L</span>
187
- <div class="slider-track" style="background: {overlayLightGrad($editorState.overlays.globals.overlay)}">
188
- <input type="range" min="0" max="100" value={$editorState.overlays.globals.overlay.lightness}
189
- onpointerdown={() => beginSliderGesture('overlay lightness')}
190
- oninput={(e) => setOverlayColor('overlay', 'lightness', +e.currentTarget.value)} />
191
- </div>
192
- <input class="shadow-slider-input" type="number" min="0" max="100"
193
- value={$editorState.overlays.globals.overlay.lightness}
194
- onchange={(e) => setOverlayColor('overlay', 'lightness', +e.currentTarget.value)} />
195
- <span class="shadow-slider-unit">%</span>
196
- </div>
197
- </div>
198
- </div>
199
- </div>
200
- <div class="overlay-global-col">
201
- <div class="global-shadow-row">
202
- <span class="shadow-slider-label">Op. Min</span>
203
- <input type="range" min="0" max="100" value={Math.round($editorState.overlays.globals.overlay.opacityMin * 100)}
204
- onpointerdown={() => beginSliderGesture('overlay opacity min')}
205
- oninput={(e) => setOverlayOpacity('overlay', 'opacityMin', +e.currentTarget.value / 100)} />
206
- <input class="shadow-slider-input" type="number" min="0" max="100"
207
- value={Math.round($editorState.overlays.globals.overlay.opacityMin * 100)}
208
- onchange={(e) => setOverlayOpacity('overlay', 'opacityMin', Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
209
- <span class="shadow-slider-unit">%</span>
210
- </div>
211
- <div class="global-shadow-row">
212
- <span class="shadow-slider-label">Op. Max</span>
213
- <input type="range" min="0" max="100" value={Math.round($editorState.overlays.globals.overlay.opacityMax * 100)}
214
- onpointerdown={() => beginSliderGesture('overlay opacity max')}
215
- oninput={(e) => setOverlayOpacity('overlay', 'opacityMax', +e.currentTarget.value / 100)} />
216
- <input class="shadow-slider-input" type="number" min="0" max="100"
217
- value={Math.round($editorState.overlays.globals.overlay.opacityMax * 100)}
218
- onchange={(e) => setOverlayOpacity('overlay', 'opacityMax', Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
219
- <span class="shadow-slider-unit">%</span>
220
- </div>
221
- </div>
222
- </div>
223
- </div>
224
-
225
- <h3 class="group-title" style="margin-top: var(--ui-space-16);">Hover Overlays</h3>
94
+ <h3 class="group-title">Hover Overlays</h3>
226
95
  <div class="overlays-grid">
227
- {#each $editorState.overlays.hoverTokens as token, i}
228
- <div class="overlay-item">
96
+ {#each $editorState.overlays.hoverTokens as token, i (token.variable)}
97
+ <div class="overlay-row">
229
98
  <div class="overlay-swatch-wrap overlay-swatch-wrap--dark">
230
- <div class="overlay-swatch" style="background: {getOverlayCss(token)};"></div>
99
+ <div class="overlay-swatch" style="background: var({token.variable});"></div>
231
100
  </div>
232
- <div class="token-info">
233
- <button class="token-variable copyable" class:copied={copiedVar === token.variable} onclick={() => copy(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
101
+ <div class="overlay-meta">
102
+ <button
103
+ class="token-variable copyable"
104
+ class:copied={copiedVar === token.variable}
105
+ onclick={() => copy(token.variable)}
106
+ >{copiedVar === token.variable ? 'copied!' : token.variable}</button>
234
107
  <span class="token-value">{token.label} — {Math.round(token.opacity * 100)}%</span>
235
108
  </div>
236
- <button class="shadow-edit-btn" onclick={() => editingOverlay = editingOverlay === token.variable ? null : token.variable}>
237
- {editingOverlay === token.variable ? 'Close' : 'Edit'}
109
+ <div class="overlay-picker">
110
+ <UIPaletteSelector
111
+ variable={token.variable}
112
+ showNone={false}
113
+ onwrite={(v) => handleWrite('hover', i, v)}
114
+ />
115
+ </div>
116
+ <button
117
+ class="copy-css-btn"
118
+ title="Copy resolved CSS"
119
+ onclick={() => copy(copyCss(token))}
120
+ >
121
+ {copiedVar === copyCss(token) ? 'copied!' : 'copy css'}
238
122
  </button>
239
- {#if editingOverlay === token.variable}
240
- <div class="shadow-editor">
241
- <div class="shadow-slider-row">
242
- <span class="shadow-slider-label">Opacity</span>
243
- <input type="range" min="0" max="100" value={Math.round(token.opacity * 100)}
244
- onpointerdown={() => beginSliderGesture(`edit ${token.variable} opacity`)}
245
- oninput={(e) => setOverlayTokenOpacity('hover', i, +e.currentTarget.value / 100)} />
246
- <input class="shadow-slider-input" type="number" min="0" max="100"
247
- value={Math.round(token.opacity * 100)}
248
- onchange={(e) => setOverlayTokenOpacity('hover', i, Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
249
- <span class="shadow-slider-unit">%</span>
250
- </div>
251
- <div class="shadow-css-output">
252
- <code>{getOverlayCss(token)}</code>
253
- <button class="shadow-copy-btn" onclick={() => copy(getOverlayCss(token))}>
254
- {copiedVar === getOverlayCss(token) ? 'Copied!' : 'Copy CSS'}
255
- </button>
256
- </div>
257
- </div>
258
- {/if}
259
123
  </div>
260
124
  {/each}
261
125
  </div>
262
-
263
- <!-- Global hover editor -->
264
- <div class="overlay-global-editor">
265
- <h4 class="global-shadow-title">Global Hover Controls</h4>
266
- <div class="overlay-global-columns">
267
- <div class="overlay-global-col">
268
- <div class="global-color-group">
269
- <div class="global-color-swatch" style="background: hsl({$editorState.overlays.globals.hover.hue}, {$editorState.overlays.globals.hover.saturation}%, {$editorState.overlays.globals.hover.lightness}%);"></div>
270
- <div class="global-color-sliders">
271
- <div class="global-shadow-row">
272
- <span class="shadow-slider-label">H</span>
273
- <div class="slider-track" style="background: {overlayHueGrad($editorState.overlays.globals.hover)}">
274
- <input type="range" min="0" max="360" value={$editorState.overlays.globals.hover.hue}
275
- onpointerdown={() => beginSliderGesture('hover hue')}
276
- oninput={(e) => setOverlayColor('hover', 'hue', +e.currentTarget.value)} />
277
- </div>
278
- <input class="shadow-slider-input" type="number" min="0" max="360"
279
- value={$editorState.overlays.globals.hover.hue}
280
- onchange={(e) => setOverlayColor('hover', 'hue', +e.currentTarget.value)} />
281
- <span class="shadow-slider-unit">&deg;</span>
282
- </div>
283
- <div class="global-shadow-row">
284
- <span class="shadow-slider-label">S</span>
285
- <div class="slider-track" style="background: {overlaySatGrad($editorState.overlays.globals.hover)}">
286
- <input type="range" min="0" max="100" value={$editorState.overlays.globals.hover.saturation}
287
- onpointerdown={() => beginSliderGesture('hover saturation')}
288
- oninput={(e) => setOverlayColor('hover', 'saturation', +e.currentTarget.value)} />
289
- </div>
290
- <input class="shadow-slider-input" type="number" min="0" max="100"
291
- value={$editorState.overlays.globals.hover.saturation}
292
- onchange={(e) => setOverlayColor('hover', 'saturation', +e.currentTarget.value)} />
293
- <span class="shadow-slider-unit">%</span>
294
- </div>
295
- <div class="global-shadow-row">
296
- <span class="shadow-slider-label">L</span>
297
- <div class="slider-track" style="background: {overlayLightGrad($editorState.overlays.globals.hover)}">
298
- <input type="range" min="0" max="100" value={$editorState.overlays.globals.hover.lightness}
299
- onpointerdown={() => beginSliderGesture('hover lightness')}
300
- oninput={(e) => setOverlayColor('hover', 'lightness', +e.currentTarget.value)} />
301
- </div>
302
- <input class="shadow-slider-input" type="number" min="0" max="100"
303
- value={$editorState.overlays.globals.hover.lightness}
304
- onchange={(e) => setOverlayColor('hover', 'lightness', +e.currentTarget.value)} />
305
- <span class="shadow-slider-unit">%</span>
306
- </div>
307
- </div>
308
- </div>
309
- </div>
310
- <div class="overlay-global-col">
311
- <div class="global-shadow-row">
312
- <span class="shadow-slider-label">Op. Min</span>
313
- <input type="range" min="0" max="100" value={Math.round($editorState.overlays.globals.hover.opacityMin * 100)}
314
- onpointerdown={() => beginSliderGesture('hover opacity min')}
315
- oninput={(e) => setOverlayOpacity('hover', 'opacityMin', +e.currentTarget.value / 100)} />
316
- <input class="shadow-slider-input" type="number" min="0" max="100"
317
- value={Math.round($editorState.overlays.globals.hover.opacityMin * 100)}
318
- onchange={(e) => setOverlayOpacity('hover', 'opacityMin', Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
319
- <span class="shadow-slider-unit">%</span>
320
- </div>
321
- <div class="global-shadow-row">
322
- <span class="shadow-slider-label">Op. Max</span>
323
- <input type="range" min="0" max="100" value={Math.round($editorState.overlays.globals.hover.opacityMax * 100)}
324
- onpointerdown={() => beginSliderGesture('hover opacity max')}
325
- oninput={(e) => setOverlayOpacity('hover', 'opacityMax', +e.currentTarget.value / 100)} />
326
- <input class="shadow-slider-input" type="number" min="0" max="100"
327
- value={Math.round($editorState.overlays.globals.hover.opacityMax * 100)}
328
- onchange={(e) => setOverlayOpacity('hover', 'opacityMax', Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
329
- <span class="shadow-slider-unit">%</span>
330
- </div>
331
- </div>
332
- </div>
333
- </div>
334
126
  </section>
335
127
 
336
128
  <style>
@@ -356,52 +148,27 @@
356
148
  margin: 0;
357
149
  }
358
150
 
359
- .token-info {
151
+ .overlays-grid {
360
152
  display: flex;
361
153
  flex-direction: column;
362
- gap: var(--ui-space-2);
363
- }
364
-
365
- .token-variable.copyable {
366
- all: unset;
367
- font-size: var(--ui-font-size-md);
368
- color: var(--ui-text-tertiary);
369
- font-family: var(--ui-font-mono);
370
- cursor: pointer;
371
- transition: color var(--ui-transition-fast);
372
- }
373
-
374
- .token-variable.copyable:hover {
375
- color: var(--ui-text-accent);
376
- }
377
-
378
- .token-variable.copyable.copied {
379
- color: var(--ui-text-success);
380
- }
381
-
382
- .token-value {
383
- font-size: var(--ui-font-size-md);
384
- color: var(--ui-text-muted);
154
+ gap: var(--ui-space-8);
385
155
  }
386
156
 
387
- /* Overlays */
388
- .overlays-grid {
157
+ .overlay-row {
389
158
  display: grid;
390
- grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
391
- gap: var(--ui-space-16);
392
- }
393
-
394
- .overlay-item {
395
- display: flex;
396
- flex-direction: column;
159
+ grid-template-columns: 3.5rem minmax(10rem, 1fr) minmax(14rem, 1.5fr) auto;
160
+ gap: var(--ui-space-12);
397
161
  align-items: center;
398
- gap: var(--ui-space-8);
162
+ padding: var(--ui-space-8) var(--ui-space-12);
163
+ background: var(--ui-surface-lowest);
164
+ border: 1px solid var(--ui-border-low);
165
+ border-radius: var(--ui-radius-md);
399
166
  }
400
167
 
401
168
  .overlay-swatch-wrap {
402
- width: 4rem;
403
- height: 4rem;
404
- border-radius: var(--ui-radius-md);
169
+ width: 3.5rem;
170
+ height: 3.5rem;
171
+ border-radius: var(--ui-radius-sm);
405
172
  position: relative;
406
173
  overflow: hidden;
407
174
  border: 1px solid var(--ui-border-low);
@@ -422,8 +189,6 @@
422
189
  linear-gradient(-45deg, #333 25%, transparent 25%),
423
190
  linear-gradient(45deg, transparent 75%, #333 75%),
424
191
  linear-gradient(-45deg, transparent 75%, #333 75%);
425
- background-size: 12px 12px;
426
- background-position: 0 0, 0 6px, 6px -6px, -6px 0px;
427
192
  }
428
193
 
429
194
  .overlay-swatch {
@@ -431,162 +196,33 @@
431
196
  inset: 0;
432
197
  }
433
198
 
434
- .overlay-item .token-info {
435
- align-items: center;
436
- text-align: center;
437
- }
438
-
439
- .overlay-global-editor {
440
- display: flex;
441
- flex-direction: column;
442
- gap: var(--ui-space-8);
443
- padding: var(--ui-space-12);
444
- background: var(--ui-surface-lowest);
445
- border: 1px solid var(--ui-border-low);
446
- border-radius: var(--ui-radius-md);
447
- margin-top: var(--ui-space-8);
448
- }
449
-
450
- .overlay-global-columns {
451
- display: grid;
452
- grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
453
- gap: var(--ui-space-16);
454
- align-items: start;
455
- }
456
-
457
- .overlay-global-col {
199
+ .overlay-meta {
458
200
  display: flex;
459
201
  flex-direction: column;
460
- gap: var(--ui-space-6);
202
+ gap: var(--ui-space-2);
461
203
  min-width: 0;
462
204
  }
463
205
 
464
- .global-shadow-title {
465
- font-size: var(--ui-font-size-xs);
466
- font-weight: var(--ui-font-weight-semibold);
467
- color: var(--ui-text-secondary);
468
- margin: 0;
469
- }
470
-
471
- .global-shadow-row {
472
- display: flex;
473
- align-items: center;
474
- gap: var(--ui-space-8);
475
- }
476
-
477
- .global-shadow-row .shadow-slider-label {
478
- width: 3rem;
479
- }
480
-
481
- .global-shadow-row input[type="range"] {
482
- flex: 1;
483
- min-width: 4rem;
484
- accent-color: var(--ui-text-accent);
485
- height: 4px;
486
- cursor: pointer;
487
- }
488
-
489
- .shadow-slider-row {
490
- display: flex;
491
- align-items: center;
492
- gap: var(--ui-space-8);
493
- }
494
-
495
- .shadow-slider-row input[type="range"] {
496
- flex: 1;
497
- min-width: 5rem;
498
- accent-color: var(--ui-text-accent);
499
- height: 4px;
500
- cursor: pointer;
501
- }
502
-
503
- .shadow-slider-label {
504
- font-size: var(--ui-font-size-xs);
505
- font-weight: var(--ui-font-weight-semibold);
206
+ .token-variable.copyable {
207
+ all: unset;
208
+ font-size: var(--ui-font-size-md);
506
209
  color: var(--ui-text-tertiary);
507
- width: 4rem;
508
- text-align: right;
509
- flex-shrink: 0;
510
- }
511
-
512
- .shadow-slider-input {
513
- font-size: var(--ui-font-size-xs);
514
- color: var(--ui-text-primary);
515
- font-family: var(--ui-font-mono);
516
- width: 2.5rem;
517
- text-align: right;
518
- flex-shrink: 0;
519
- background: var(--ui-surface-lowest);
520
- border: 1px solid var(--ui-border-low);
521
- border-radius: var(--ui-radius-sm);
522
- padding: var(--ui-space-2) var(--ui-space-4);
523
- -moz-appearance: textfield;
524
- appearance: textfield;
525
- }
526
-
527
- .shadow-slider-input::-webkit-inner-spin-button,
528
- .shadow-slider-input::-webkit-outer-spin-button {
529
- -webkit-appearance: none;
530
- margin: 0;
531
- }
532
-
533
- .shadow-slider-input:focus {
534
- outline: none;
535
- border-color: var(--ui-border-high);
536
- }
537
-
538
- .shadow-slider-unit {
539
- font-size: var(--ui-font-size-xs);
540
- color: var(--ui-text-muted);
541
210
  font-family: var(--ui-font-mono);
542
- width: 1rem;
543
- flex-shrink: 0;
544
- }
545
-
546
- .shadow-edit-btn {
547
- all: unset;
548
- font-size: var(--ui-font-size-xs);
549
- color: var(--ui-text-muted);
550
211
  cursor: pointer;
551
- padding: var(--ui-space-2) var(--ui-space-8);
552
- border-radius: var(--ui-radius-sm);
553
- transition: color var(--ui-transition-fast), background var(--ui-transition-fast);
212
+ transition: color var(--ui-transition-fast);
554
213
  }
555
214
 
556
- .shadow-edit-btn:hover {
557
- color: var(--ui-text-accent);
558
- background: var(--ui-surface-low);
559
- }
215
+ .token-variable.copyable:hover { color: var(--ui-text-accent); }
216
+ .token-variable.copyable.copied { color: var(--ui-text-success); }
560
217
 
561
- .shadow-editor {
562
- display: flex;
563
- flex-direction: column;
564
- gap: var(--ui-space-8);
565
- padding: var(--ui-space-12);
566
- background: var(--ui-surface-lowest);
567
- border: 1px solid var(--ui-border-low);
568
- border-radius: var(--ui-radius-md);
569
- width: 100%;
570
- min-width: 14rem;
571
- }
572
-
573
- .shadow-css-output {
574
- display: flex;
575
- flex-direction: column;
576
- gap: var(--ui-space-4);
577
- margin-top: var(--ui-space-4);
578
- padding-top: var(--ui-space-8);
579
- border-top: 1px solid var(--ui-border-low);
218
+ .token-value {
219
+ font-size: var(--ui-font-size-md);
220
+ color: var(--ui-text-muted);
580
221
  }
581
222
 
582
- .shadow-css-output code {
583
- font-size: var(--ui-font-size-xs);
584
- color: var(--ui-text-accent);
585
- font-family: var(--ui-font-mono);
586
- word-break: break-all;
587
- }
223
+ .overlay-picker { min-width: 0; }
588
224
 
589
- .shadow-copy-btn {
225
+ .copy-css-btn {
590
226
  all: unset;
591
227
  font-size: var(--ui-font-size-xs);
592
228
  color: var(--ui-text-muted);
@@ -594,80 +230,11 @@
594
230
  padding: var(--ui-space-2) var(--ui-space-6);
595
231
  border: 1px solid var(--ui-border-low);
596
232
  border-radius: var(--ui-radius-sm);
597
- text-align: center;
598
233
  transition: color var(--ui-transition-fast), border-color var(--ui-transition-fast);
599
234
  }
600
235
 
601
- .shadow-copy-btn:hover {
236
+ .copy-css-btn:hover {
602
237
  color: var(--ui-text-primary);
603
238
  border-color: var(--ui-border-high);
604
239
  }
605
-
606
- .slider-track {
607
- flex: 1;
608
- min-width: 4rem;
609
- position: relative;
610
- height: 12px;
611
- border-radius: var(--ui-radius-sm);
612
- border: 1px solid var(--ui-border-low);
613
- }
614
-
615
- .slider-track input[type="range"] {
616
- position: absolute;
617
- top: 0;
618
- left: 0;
619
- width: 100%;
620
- height: 100%;
621
- cursor: pointer;
622
- margin: 0;
623
- -webkit-appearance: none;
624
- appearance: none;
625
- background: transparent;
626
- }
627
-
628
- .slider-track input[type="range"]::-webkit-slider-thumb {
629
- -webkit-appearance: none;
630
- width: 10px;
631
- height: 14px;
632
- border-radius: 2px;
633
- background: white;
634
- border: 1px solid var(--ui-border-low);
635
- box-shadow: 0 1px 3px rgba(0,0,0,0.4);
636
- cursor: pointer;
637
- }
638
-
639
- .slider-track input[type="range"]::-moz-range-thumb {
640
- width: 10px;
641
- height: 14px;
642
- border-radius: 2px;
643
- background: white;
644
- border: 1px solid var(--ui-border-low);
645
- box-shadow: 0 1px 3px rgba(0,0,0,0.4);
646
- cursor: pointer;
647
- }
648
-
649
- .slider-track input[type="range"]::-moz-range-track {
650
- background: transparent;
651
- border: none;
652
- }
653
-
654
- .global-color-group {
655
- display: flex;
656
- gap: var(--ui-space-8);
657
- align-items: stretch;
658
- }
659
-
660
- .global-color-swatch {
661
- width: 2rem;
662
- flex-shrink: 0;
663
- border-radius: var(--ui-radius-sm);
664
- border: 1px solid var(--ui-border-low);
665
- }
666
-
667
- .global-color-sliders {
668
- flex: 1;
669
- display: flex;
670
- flex-direction: column;
671
- gap: var(--ui-space-6);
672
- }
673
240
  </style>