@motion-proto/live-tokens 0.1.1 → 0.3.1

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 (224) hide show
  1. package/README.md +160 -21
  2. package/dist-plugin/index.cjs +823 -336
  3. package/dist-plugin/index.d.cts +9 -7
  4. package/dist-plugin/index.d.ts +9 -7
  5. package/dist-plugin/index.js +822 -335
  6. package/package.json +46 -20
  7. package/src/assets/newspaper.webp +0 -0
  8. package/src/assets/offering.webp +0 -0
  9. package/src/component-editor/BadgeEditor.svelte +170 -0
  10. package/src/component-editor/CalloutEditor.svelte +103 -0
  11. package/src/component-editor/CardEditor.svelte +184 -0
  12. package/src/component-editor/CollapsibleSectionEditor.svelte +167 -0
  13. package/src/component-editor/CornerBadgeEditor.svelte +207 -0
  14. package/src/component-editor/DialogEditor.svelte +172 -0
  15. package/src/component-editor/ImageEditor.svelte +72 -0
  16. package/src/component-editor/InlineEditActionsEditor.svelte +83 -0
  17. package/src/component-editor/NotificationEditor.svelte +160 -0
  18. package/src/component-editor/ProgressBarEditor.svelte +124 -0
  19. package/src/component-editor/RadioButtonEditor.svelte +140 -0
  20. package/src/component-editor/SectionDividerEditor.svelte +263 -0
  21. package/src/component-editor/SegmentedControlEditor.svelte +154 -0
  22. package/src/component-editor/StandardButtonsEditor.svelte +178 -0
  23. package/src/component-editor/TabBarEditor.svelte +137 -0
  24. package/src/component-editor/TableEditor.svelte +128 -0
  25. package/src/component-editor/TooltipEditor.svelte +122 -0
  26. package/src/component-editor/editorTokens.test.ts +93 -0
  27. package/src/component-editor/groupKeySlots.test.ts +67 -0
  28. package/src/component-editor/groupKeySnapshot.test.ts +52 -0
  29. package/src/component-editor/index.ts +5 -0
  30. package/src/component-editor/registry.ts +246 -0
  31. package/src/component-editor/scaffolding/AngleDial.svelte +185 -0
  32. package/src/component-editor/scaffolding/ComponentEditorBase.svelte +96 -0
  33. package/src/component-editor/scaffolding/ComponentFileManager.svelte +682 -0
  34. package/src/component-editor/scaffolding/ComponentFileMenu.svelte +312 -0
  35. package/src/component-editor/scaffolding/ComponentsTab.svelte +69 -0
  36. package/src/component-editor/scaffolding/CopyFromMenu.svelte +246 -0
  37. package/src/component-editor/scaffolding/DemoHeader.svelte +21 -0
  38. package/src/component-editor/scaffolding/DividerEditor.svelte +81 -0
  39. package/src/component-editor/scaffolding/FieldsetWrapper.svelte +46 -0
  40. package/src/component-editor/scaffolding/GradientCard.svelte +291 -0
  41. package/src/component-editor/scaffolding/LinkageChart.svelte +297 -0
  42. package/src/component-editor/scaffolding/LinkedBlock.svelte +418 -0
  43. package/src/component-editor/scaffolding/NonStylableConfig.svelte +57 -0
  44. package/src/component-editor/scaffolding/SaveAsDialog.svelte +177 -0
  45. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +25 -0
  46. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +56 -0
  47. package/src/component-editor/scaffolding/StateBlock.svelte +115 -0
  48. package/src/component-editor/scaffolding/TokenLayout.svelte +511 -0
  49. package/src/component-editor/scaffolding/TypeEditor.svelte +82 -0
  50. package/src/component-editor/scaffolding/VariantGroup.svelte +277 -0
  51. package/src/component-editor/scaffolding/buildTypeGroupTokens.ts +97 -0
  52. package/src/component-editor/scaffolding/componentSectionType.ts +8 -0
  53. package/src/component-editor/scaffolding/componentSources.ts +9 -0
  54. package/src/component-editor/scaffolding/defaultSections.ts +16 -0
  55. package/src/component-editor/scaffolding/editorContext.ts +44 -0
  56. package/src/component-editor/scaffolding/linkedBlock.ts +226 -0
  57. package/src/component-editor/scaffolding/siblings.ts +33 -0
  58. package/src/component-editor/scaffolding/types.ts +39 -0
  59. package/src/components/Badge.svelte +231 -42
  60. package/src/components/Button.svelte +324 -124
  61. package/src/components/Callout.svelte +145 -0
  62. package/src/components/Card.svelte +123 -25
  63. package/src/components/CollapsibleSection.svelte +213 -35
  64. package/src/components/CornerBadge.svelte +224 -0
  65. package/src/components/Dialog.svelte +137 -114
  66. package/src/components/Image.svelte +43 -0
  67. package/src/components/InlineEditActions.svelte +74 -14
  68. package/src/components/Notification.svelte +184 -163
  69. package/src/components/ProgressBar.svelte +216 -22
  70. package/src/components/RadioButton.svelte +110 -40
  71. package/src/components/SectionDivider.svelte +428 -74
  72. package/src/components/SegmentedControl.svelte +203 -0
  73. package/src/components/TabBar.svelte +146 -21
  74. package/src/components/Table.svelte +102 -0
  75. package/src/components/Tooltip.svelte +45 -19
  76. package/src/components/types.ts +51 -0
  77. package/src/data/google-fonts.json +75 -0
  78. package/src/lib/ColumnsOverlay.svelte +20 -7
  79. package/src/lib/LiveEditorOverlay.svelte +257 -78
  80. package/src/lib/columnsOverlay.ts +21 -17
  81. package/src/lib/componentConfig.test.ts +204 -0
  82. package/src/lib/componentConfigKeys.ts +19 -0
  83. package/src/lib/componentConfigService.ts +88 -0
  84. package/src/lib/copyPopover.ts +30 -0
  85. package/src/lib/cssVarSync.ts +59 -7
  86. package/src/lib/editorConfigStore.ts +0 -10
  87. package/src/lib/editorCore.ts +402 -0
  88. package/src/lib/editorKeybindings.ts +52 -0
  89. package/src/lib/editorPersistence.ts +106 -0
  90. package/src/lib/editorRenderer.ts +74 -0
  91. package/src/lib/editorStore.test.ts +328 -0
  92. package/src/lib/editorStore.ts +412 -0
  93. package/src/lib/editorTypes.ts +100 -0
  94. package/src/lib/editorViewStore.ts +55 -0
  95. package/src/lib/files/versionedFileResource.ts +140 -0
  96. package/src/lib/fontLoader.ts +130 -0
  97. package/src/lib/fontMigration.ts +140 -0
  98. package/src/lib/fontParse.ts +168 -0
  99. package/src/lib/index.ts +48 -30
  100. package/src/lib/lazyConfig.test.ts +54 -0
  101. package/src/lib/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +64 -0
  102. package/src/lib/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +71 -0
  103. package/src/lib/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +43 -0
  104. package/src/lib/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +68 -0
  105. package/src/lib/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +35 -0
  106. package/src/lib/migrations/2026-05-10-sectiondivider-gradient-stops.ts +50 -0
  107. package/src/lib/migrations/2026-05-13-primary-to-brand.ts +90 -0
  108. package/src/lib/migrations/index.ts +93 -0
  109. package/src/lib/migrations/migrations.test.ts +341 -0
  110. package/src/lib/navLinkTypes.ts +1 -0
  111. package/src/lib/overlayState.ts +3 -0
  112. package/src/lib/paletteDerivation.ts +300 -0
  113. package/src/lib/parentRouteStore.ts +42 -0
  114. package/src/lib/parsers/globalRootBlock.ts +32 -0
  115. package/src/lib/presetService.ts +94 -0
  116. package/src/lib/router.ts +42 -10
  117. package/src/lib/scrollSection.ts +45 -0
  118. package/src/lib/slices/columns.ts +59 -0
  119. package/src/lib/slices/components.ts +362 -0
  120. package/src/lib/slices/domainVars.ts +15 -0
  121. package/src/lib/slices/fonts.ts +30 -0
  122. package/src/lib/slices/gradients.ts +153 -0
  123. package/src/lib/slices/overlays.ts +132 -0
  124. package/src/lib/slices/palettes.ts +26 -0
  125. package/src/lib/slices/shadows.ts +123 -0
  126. package/src/lib/storage.ts +88 -0
  127. package/src/lib/themeInit.ts +74 -0
  128. package/src/lib/themeService.ts +101 -0
  129. package/src/lib/themeTypes.ts +146 -0
  130. package/src/lib/tokenRegistry.ts +148 -0
  131. package/src/pages/ComponentEditorPage.svelte +384 -0
  132. package/src/pages/ComponentEditorPage.svelte.d.ts +2 -0
  133. package/src/pages/Editor.svelte +98 -0
  134. package/src/pages/Editor.svelte.d.ts +2 -0
  135. package/src/pages/EditorShell.svelte +348 -0
  136. package/src/styles/_padding.scss +34 -0
  137. package/src/styles/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  138. package/src/styles/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  139. package/src/styles/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  140. package/src/styles/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  141. package/src/styles/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  142. package/src/styles/fonts/Manrope/Manrope-latin.woff2 +0 -0
  143. package/src/styles/fonts.css +22 -10
  144. package/src/styles/form-controls.css +14 -16
  145. package/src/styles/tokens.css +1322 -0
  146. package/src/styles/ui-editor.css +126 -0
  147. package/src/{showcase → ui}/BezierCurveEditor.svelte +14 -14
  148. package/src/{showcase → ui}/ColorEditPanel.svelte +42 -36
  149. package/src/ui/EditorViewSwitcher.svelte +180 -0
  150. package/src/ui/FontStackEditor.svelte +360 -0
  151. package/src/ui/GradientEditor.svelte +461 -0
  152. package/src/ui/GradientStopPicker.svelte +74 -0
  153. package/src/ui/PaletteEditor.svelte +1590 -0
  154. package/src/ui/PaletteEditor.test.ts +108 -0
  155. package/src/ui/PresetFileManager.svelte +567 -0
  156. package/src/ui/ProjectFontsSection.svelte +645 -0
  157. package/src/{showcase → ui}/SurfacesTab.svelte +39 -39
  158. package/src/{showcase → ui}/TextTab.svelte +27 -27
  159. package/src/{showcase/TokenFileManager.svelte → ui/ThemeFileManager.svelte} +196 -112
  160. package/src/ui/Toggle.svelte +108 -0
  161. package/src/ui/UICopyPopover.svelte +78 -0
  162. package/src/{showcase/EditorDialog.svelte → ui/UIDialog.svelte} +66 -25
  163. package/src/ui/UIFontFamilySelector.svelte +309 -0
  164. package/src/ui/UIFontSizeSelector.svelte +165 -0
  165. package/src/ui/UIFontWeightSelector.svelte +52 -0
  166. package/src/ui/UILineHeightSelector.svelte +47 -0
  167. package/src/ui/UILinkToggle.svelte +60 -0
  168. package/src/ui/UIOptionItem.svelte +74 -0
  169. package/src/ui/UIOptionList.svelte +27 -0
  170. package/src/ui/UIPaddingSelector.svelte +661 -0
  171. package/src/ui/UIPaletteSelector.svelte +1084 -0
  172. package/src/ui/UIRadio.svelte +72 -0
  173. package/src/ui/UIRadioGroup.svelte +59 -0
  174. package/src/ui/UIRelinkConfirmPopover.svelte +235 -0
  175. package/src/ui/UITokenSelector.svelte +509 -0
  176. package/src/ui/UIVariantSelector.svelte +145 -0
  177. package/src/ui/VariablesTab.svelte +252 -0
  178. package/src/ui/index.ts +31 -0
  179. package/src/ui/keepInViewport.ts +84 -0
  180. package/src/ui/palette/GradientStopEditor.svelte +482 -0
  181. package/src/ui/palette/OverridesPanel.svelte +526 -0
  182. package/src/ui/palette/PaletteBase.svelte +165 -0
  183. package/src/ui/palette/ScaleCurveEditor.svelte +38 -0
  184. package/src/ui/palette/paletteEditorState.ts +89 -0
  185. package/src/ui/sections/ColumnsSection.svelte +273 -0
  186. package/src/ui/sections/GradientsSection.svelte +147 -0
  187. package/src/ui/sections/OverlaysSection.svelte +670 -0
  188. package/src/ui/sections/ShadowsSection.svelte +1250 -0
  189. package/src/ui/sections/TokenScaleTable.svelte +332 -0
  190. package/src/ui/sections/tokenScales.ts +81 -0
  191. package/src/ui/variantScales.ts +108 -0
  192. package/src/components/DetailNav.svelte +0 -78
  193. package/src/components/Toggle.svelte +0 -86
  194. package/src/lib/tokenInit.ts +0 -29
  195. package/src/lib/tokenService.ts +0 -144
  196. package/src/lib/tokenTypes.ts +0 -45
  197. package/src/pages/Admin.svelte +0 -100
  198. package/src/pages/ShowcasePage.svelte +0 -144
  199. package/src/showcase/BackupBrowser.svelte +0 -617
  200. package/src/showcase/ComponentsTab.svelte +0 -105
  201. package/src/showcase/PaletteEditor.svelte +0 -2579
  202. package/src/showcase/PaletteSelector.svelte +0 -627
  203. package/src/showcase/TokenMap.svelte +0 -54
  204. package/src/showcase/VariablesTab.svelte +0 -2655
  205. package/src/showcase/VisualsTab.svelte +0 -231
  206. package/src/showcase/demos/BadgeDemo.svelte +0 -56
  207. package/src/showcase/demos/CardDemo.svelte +0 -50
  208. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -192
  209. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -54
  210. package/src/showcase/demos/DialogDemo.svelte +0 -42
  211. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -25
  212. package/src/showcase/demos/NotificationDemo.svelte +0 -147
  213. package/src/showcase/demos/ProgressBarDemo.svelte +0 -54
  214. package/src/showcase/demos/RadioButtonDemo.svelte +0 -56
  215. package/src/showcase/demos/SectionDividerDemo.svelte +0 -77
  216. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -455
  217. package/src/showcase/demos/TabBarDemo.svelte +0 -58
  218. package/src/showcase/demos/TooltipDemo.svelte +0 -52
  219. package/src/showcase/editor.css +0 -93
  220. package/src/showcase/index.ts +0 -17
  221. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  222. package/src/styles/fonts/Domine/OFL.txt +0 -97
  223. package/src/styles/fonts/Domine/README.txt +0 -66
  224. /package/src/{showcase → ui}/curveEngine.ts +0 -0
@@ -0,0 +1,670 @@
1
+ <script lang="ts">
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.
8
+ */
9
+ import { createEventDispatcher } from 'svelte';
10
+ import { editorState, mutate, beginSliderGesture } from '../../lib/editorStore';
11
+ import type { OverlayToken, OverlayChannelGlobals, EditorState } from '../../lib/editorTypes';
12
+
13
+ export let copiedVar: string | null = null;
14
+
15
+ const dispatch = createEventDispatcher<{ copy: string }>();
16
+ function copy(v: string) { dispatch('copy', v); }
17
+
18
+ let editingOverlay: string | null = null;
19
+
20
+ function clampNum(v: number, lo: number, hi: number): number {
21
+ return Math.max(lo, Math.min(hi, Math.round(v)));
22
+ }
23
+
24
+ function getOverlayCss(t: OverlayToken): string {
25
+ return `rgba(${t.r}, ${t.g}, ${t.b}, ${t.opacity})`;
26
+ }
27
+
28
+ function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
29
+ s /= 100; l /= 100;
30
+ const c = (1 - Math.abs(2 * l - 1)) * s;
31
+ const x = c * (1 - Math.abs((h / 60) % 2 - 1));
32
+ const m = l - c / 2;
33
+ let r1 = 0, g1 = 0, b1 = 0;
34
+ if (h < 60) { r1 = c; g1 = x; }
35
+ else if (h < 120) { r1 = x; g1 = c; }
36
+ else if (h < 180) { g1 = c; b1 = x; }
37
+ else if (h < 240) { g1 = x; b1 = c; }
38
+ else if (h < 300) { r1 = x; b1 = c; }
39
+ else { r1 = c; b1 = x; }
40
+ return {
41
+ r: Math.round((r1 + m) * 255),
42
+ g: Math.round((g1 + m) * 255),
43
+ b: Math.round((b1 + m) * 255),
44
+ };
45
+ }
46
+
47
+ function applyChannelColor(tokens: OverlayToken[], g: OverlayChannelGlobals): void {
48
+ const rgb = hslToRgb(g.hue, g.saturation, g.lightness);
49
+ for (const t of tokens) { t.r = rgb.r; t.g = rgb.g; t.b = rgb.b; }
50
+ }
51
+ function applyChannelOpacity(tokens: OverlayToken[], g: OverlayChannelGlobals): void {
52
+ const last = tokens.length - 1;
53
+ tokens.forEach((t, i) => {
54
+ const frac = last > 0 ? i / last : 0.5;
55
+ t.opacity = Math.round((g.opacityMin + frac * (g.opacityMax - g.opacityMin)) * 100) / 100;
56
+ });
57
+ }
58
+
59
+ type OverlayChannel = 'overlay' | 'hover';
60
+ type ColorField = 'hue' | 'saturation' | 'lightness';
61
+ type OpacityField = 'opacityMin' | 'opacityMax';
62
+
63
+ function pickChannelTokens(s: EditorState, ch: OverlayChannel): OverlayToken[] {
64
+ return ch === 'overlay' ? s.overlays.tokens : s.overlays.hoverTokens;
65
+ }
66
+
67
+ function setOverlayColor(ch: OverlayChannel, field: ColorField, value: number) {
68
+ mutate(`${ch} ${field}`, (s) => {
69
+ const g = s.overlays.globals[ch];
70
+ if (field === 'hue') g.hue = clampNum(value, 0, 360);
71
+ else if (field === 'saturation') g.saturation = clampNum(value, 0, 100);
72
+ else g.lightness = clampNum(value, 0, 100);
73
+ applyChannelColor(pickChannelTokens(s, ch), g);
74
+ });
75
+ }
76
+
77
+ function setOverlayOpacity(ch: OverlayChannel, field: OpacityField, value01: number) {
78
+ mutate(`${ch} ${field}`, (s) => {
79
+ const g = s.overlays.globals[ch];
80
+ const clamped = Math.max(0, Math.min(1, value01));
81
+ if (field === 'opacityMin') g.opacityMin = clamped;
82
+ else g.opacityMax = clamped;
83
+ applyChannelOpacity(pickChannelTokens(s, ch), g);
84
+ });
85
+ }
86
+
87
+ function setOverlayTokenOpacity(ch: OverlayChannel, idx: number, value01: number) {
88
+ mutate(`${ch} token opacity`, (s) => {
89
+ const arr = pickChannelTokens(s, ch);
90
+ const t = arr[idx];
91
+ if (!t) return;
92
+ t.opacity = Math.max(0, Math.min(1, value01));
93
+ });
94
+ }
95
+
96
+ function overlayHueGrad(g: OverlayChannelGlobals): string {
97
+ return `linear-gradient(to right, ${
98
+ [0, 60, 120, 180, 240, 300, 360].map(h => `hsl(${h},${g.saturation}%,${g.lightness}%)`).join(',')
99
+ })`;
100
+ }
101
+ function overlaySatGrad(g: OverlayChannelGlobals): string {
102
+ return `linear-gradient(to right, hsl(${g.hue},0%,${g.lightness}%), hsl(${g.hue},100%,${g.lightness}%))`;
103
+ }
104
+ function overlayLightGrad(g: OverlayChannelGlobals): string {
105
+ return `linear-gradient(to right, hsl(${g.hue},${g.saturation}%,0%), hsl(${g.hue},${g.saturation}%,50%), hsl(${g.hue},${g.saturation}%,100%))`;
106
+ }
107
+ </script>
108
+
109
+ <section class="section" id="overlays">
110
+ <h2 class="section-title">Overlays</h2>
111
+
112
+ <h3 class="group-title">Dark Overlays</h3>
113
+ <div class="overlays-grid">
114
+ {#each $editorState.overlays.tokens as token, i}
115
+ <div class="overlay-item">
116
+ <div class="overlay-swatch-wrap">
117
+ <div class="overlay-swatch" style="background: {getOverlayCss(token)};"></div>
118
+ </div>
119
+ <div class="token-info">
120
+ <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copy(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
121
+ <span class="token-value">{token.label} — {Math.round(token.opacity * 100)}%</span>
122
+ </div>
123
+ <button class="shadow-edit-btn" on:click={() => editingOverlay = editingOverlay === token.variable ? null : token.variable}>
124
+ {editingOverlay === token.variable ? 'Close' : 'Edit'}
125
+ </button>
126
+ {#if editingOverlay === token.variable}
127
+ <div class="shadow-editor">
128
+ <div class="shadow-slider-row">
129
+ <span class="shadow-slider-label">Opacity</span>
130
+ <input type="range" min="0" max="100" value={Math.round(token.opacity * 100)}
131
+ on:pointerdown={() => beginSliderGesture(`edit ${token.variable} opacity`)}
132
+ on:input={(e) => setOverlayTokenOpacity('overlay', i, +e.currentTarget.value / 100)} />
133
+ <input class="shadow-slider-input" type="number" min="0" max="100"
134
+ value={Math.round(token.opacity * 100)}
135
+ on:change={(e) => setOverlayTokenOpacity('overlay', i, Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
136
+ <span class="shadow-slider-unit">%</span>
137
+ </div>
138
+ <div class="shadow-css-output">
139
+ <code>{getOverlayCss(token)}</code>
140
+ <button class="shadow-copy-btn" on:click={() => copy(getOverlayCss(token))}>
141
+ {copiedVar === getOverlayCss(token) ? 'Copied!' : 'Copy CSS'}
142
+ </button>
143
+ </div>
144
+ </div>
145
+ {/if}
146
+ </div>
147
+ {/each}
148
+ </div>
149
+
150
+ <!-- Global overlay editor -->
151
+ <div class="overlay-global-editor">
152
+ <h4 class="global-shadow-title">Global Overlay Controls</h4>
153
+ <div class="overlay-global-columns">
154
+ <div class="overlay-global-col">
155
+ <div class="global-color-group">
156
+ <div class="global-color-swatch" style="background: hsl({$editorState.overlays.globals.overlay.hue}, {$editorState.overlays.globals.overlay.saturation}%, {$editorState.overlays.globals.overlay.lightness}%);"></div>
157
+ <div class="global-color-sliders">
158
+ <div class="global-shadow-row">
159
+ <span class="shadow-slider-label">H</span>
160
+ <div class="slider-track" style="background: {overlayHueGrad($editorState.overlays.globals.overlay)}">
161
+ <input type="range" min="0" max="360" value={$editorState.overlays.globals.overlay.hue}
162
+ on:pointerdown={() => beginSliderGesture('overlay hue')}
163
+ on:input={(e) => setOverlayColor('overlay', 'hue', +e.currentTarget.value)} />
164
+ </div>
165
+ <input class="shadow-slider-input" type="number" min="0" max="360"
166
+ value={$editorState.overlays.globals.overlay.hue}
167
+ on:change={(e) => setOverlayColor('overlay', 'hue', +e.currentTarget.value)} />
168
+ <span class="shadow-slider-unit">&deg;</span>
169
+ </div>
170
+ <div class="global-shadow-row">
171
+ <span class="shadow-slider-label">S</span>
172
+ <div class="slider-track" style="background: {overlaySatGrad($editorState.overlays.globals.overlay)}">
173
+ <input type="range" min="0" max="100" value={$editorState.overlays.globals.overlay.saturation}
174
+ on:pointerdown={() => beginSliderGesture('overlay saturation')}
175
+ on:input={(e) => setOverlayColor('overlay', 'saturation', +e.currentTarget.value)} />
176
+ </div>
177
+ <input class="shadow-slider-input" type="number" min="0" max="100"
178
+ value={$editorState.overlays.globals.overlay.saturation}
179
+ on:change={(e) => setOverlayColor('overlay', 'saturation', +e.currentTarget.value)} />
180
+ <span class="shadow-slider-unit">%</span>
181
+ </div>
182
+ <div class="global-shadow-row">
183
+ <span class="shadow-slider-label">L</span>
184
+ <div class="slider-track" style="background: {overlayLightGrad($editorState.overlays.globals.overlay)}">
185
+ <input type="range" min="0" max="100" value={$editorState.overlays.globals.overlay.lightness}
186
+ on:pointerdown={() => beginSliderGesture('overlay lightness')}
187
+ on:input={(e) => setOverlayColor('overlay', 'lightness', +e.currentTarget.value)} />
188
+ </div>
189
+ <input class="shadow-slider-input" type="number" min="0" max="100"
190
+ value={$editorState.overlays.globals.overlay.lightness}
191
+ on:change={(e) => setOverlayColor('overlay', 'lightness', +e.currentTarget.value)} />
192
+ <span class="shadow-slider-unit">%</span>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ <div class="overlay-global-col">
198
+ <div class="global-shadow-row">
199
+ <span class="shadow-slider-label">Op. Min</span>
200
+ <input type="range" min="0" max="100" value={Math.round($editorState.overlays.globals.overlay.opacityMin * 100)}
201
+ on:pointerdown={() => beginSliderGesture('overlay opacity min')}
202
+ on:input={(e) => setOverlayOpacity('overlay', 'opacityMin', +e.currentTarget.value / 100)} />
203
+ <input class="shadow-slider-input" type="number" min="0" max="100"
204
+ value={Math.round($editorState.overlays.globals.overlay.opacityMin * 100)}
205
+ on:change={(e) => setOverlayOpacity('overlay', 'opacityMin', Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
206
+ <span class="shadow-slider-unit">%</span>
207
+ </div>
208
+ <div class="global-shadow-row">
209
+ <span class="shadow-slider-label">Op. Max</span>
210
+ <input type="range" min="0" max="100" value={Math.round($editorState.overlays.globals.overlay.opacityMax * 100)}
211
+ on:pointerdown={() => beginSliderGesture('overlay opacity max')}
212
+ on:input={(e) => setOverlayOpacity('overlay', 'opacityMax', +e.currentTarget.value / 100)} />
213
+ <input class="shadow-slider-input" type="number" min="0" max="100"
214
+ value={Math.round($editorState.overlays.globals.overlay.opacityMax * 100)}
215
+ on:change={(e) => setOverlayOpacity('overlay', 'opacityMax', Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
216
+ <span class="shadow-slider-unit">%</span>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+
222
+ <h3 class="group-title" style="margin-top: var(--ui-space-16);">Hover Overlays</h3>
223
+ <div class="overlays-grid">
224
+ {#each $editorState.overlays.hoverTokens as token, i}
225
+ <div class="overlay-item">
226
+ <div class="overlay-swatch-wrap overlay-swatch-wrap--dark">
227
+ <div class="overlay-swatch" style="background: {getOverlayCss(token)};"></div>
228
+ </div>
229
+ <div class="token-info">
230
+ <button class="token-variable copyable" class:copied={copiedVar === token.variable} on:click={() => copy(token.variable)}>{copiedVar === token.variable ? 'copied!' : token.variable}</button>
231
+ <span class="token-value">{token.label} — {Math.round(token.opacity * 100)}%</span>
232
+ </div>
233
+ <button class="shadow-edit-btn" on:click={() => editingOverlay = editingOverlay === token.variable ? null : token.variable}>
234
+ {editingOverlay === token.variable ? 'Close' : 'Edit'}
235
+ </button>
236
+ {#if editingOverlay === token.variable}
237
+ <div class="shadow-editor">
238
+ <div class="shadow-slider-row">
239
+ <span class="shadow-slider-label">Opacity</span>
240
+ <input type="range" min="0" max="100" value={Math.round(token.opacity * 100)}
241
+ on:pointerdown={() => beginSliderGesture(`edit ${token.variable} opacity`)}
242
+ on:input={(e) => setOverlayTokenOpacity('hover', i, +e.currentTarget.value / 100)} />
243
+ <input class="shadow-slider-input" type="number" min="0" max="100"
244
+ value={Math.round(token.opacity * 100)}
245
+ on:change={(e) => setOverlayTokenOpacity('hover', i, Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
246
+ <span class="shadow-slider-unit">%</span>
247
+ </div>
248
+ <div class="shadow-css-output">
249
+ <code>{getOverlayCss(token)}</code>
250
+ <button class="shadow-copy-btn" on:click={() => copy(getOverlayCss(token))}>
251
+ {copiedVar === getOverlayCss(token) ? 'Copied!' : 'Copy CSS'}
252
+ </button>
253
+ </div>
254
+ </div>
255
+ {/if}
256
+ </div>
257
+ {/each}
258
+ </div>
259
+
260
+ <!-- Global hover editor -->
261
+ <div class="overlay-global-editor">
262
+ <h4 class="global-shadow-title">Global Hover Controls</h4>
263
+ <div class="overlay-global-columns">
264
+ <div class="overlay-global-col">
265
+ <div class="global-color-group">
266
+ <div class="global-color-swatch" style="background: hsl({$editorState.overlays.globals.hover.hue}, {$editorState.overlays.globals.hover.saturation}%, {$editorState.overlays.globals.hover.lightness}%);"></div>
267
+ <div class="global-color-sliders">
268
+ <div class="global-shadow-row">
269
+ <span class="shadow-slider-label">H</span>
270
+ <div class="slider-track" style="background: {overlayHueGrad($editorState.overlays.globals.hover)}">
271
+ <input type="range" min="0" max="360" value={$editorState.overlays.globals.hover.hue}
272
+ on:pointerdown={() => beginSliderGesture('hover hue')}
273
+ on:input={(e) => setOverlayColor('hover', 'hue', +e.currentTarget.value)} />
274
+ </div>
275
+ <input class="shadow-slider-input" type="number" min="0" max="360"
276
+ value={$editorState.overlays.globals.hover.hue}
277
+ on:change={(e) => setOverlayColor('hover', 'hue', +e.currentTarget.value)} />
278
+ <span class="shadow-slider-unit">&deg;</span>
279
+ </div>
280
+ <div class="global-shadow-row">
281
+ <span class="shadow-slider-label">S</span>
282
+ <div class="slider-track" style="background: {overlaySatGrad($editorState.overlays.globals.hover)}">
283
+ <input type="range" min="0" max="100" value={$editorState.overlays.globals.hover.saturation}
284
+ on:pointerdown={() => beginSliderGesture('hover saturation')}
285
+ on:input={(e) => setOverlayColor('hover', 'saturation', +e.currentTarget.value)} />
286
+ </div>
287
+ <input class="shadow-slider-input" type="number" min="0" max="100"
288
+ value={$editorState.overlays.globals.hover.saturation}
289
+ on:change={(e) => setOverlayColor('hover', 'saturation', +e.currentTarget.value)} />
290
+ <span class="shadow-slider-unit">%</span>
291
+ </div>
292
+ <div class="global-shadow-row">
293
+ <span class="shadow-slider-label">L</span>
294
+ <div class="slider-track" style="background: {overlayLightGrad($editorState.overlays.globals.hover)}">
295
+ <input type="range" min="0" max="100" value={$editorState.overlays.globals.hover.lightness}
296
+ on:pointerdown={() => beginSliderGesture('hover lightness')}
297
+ on:input={(e) => setOverlayColor('hover', 'lightness', +e.currentTarget.value)} />
298
+ </div>
299
+ <input class="shadow-slider-input" type="number" min="0" max="100"
300
+ value={$editorState.overlays.globals.hover.lightness}
301
+ on:change={(e) => setOverlayColor('hover', 'lightness', +e.currentTarget.value)} />
302
+ <span class="shadow-slider-unit">%</span>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ </div>
307
+ <div class="overlay-global-col">
308
+ <div class="global-shadow-row">
309
+ <span class="shadow-slider-label">Op. Min</span>
310
+ <input type="range" min="0" max="100" value={Math.round($editorState.overlays.globals.hover.opacityMin * 100)}
311
+ on:pointerdown={() => beginSliderGesture('hover opacity min')}
312
+ on:input={(e) => setOverlayOpacity('hover', 'opacityMin', +e.currentTarget.value / 100)} />
313
+ <input class="shadow-slider-input" type="number" min="0" max="100"
314
+ value={Math.round($editorState.overlays.globals.hover.opacityMin * 100)}
315
+ on:change={(e) => setOverlayOpacity('hover', 'opacityMin', Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
316
+ <span class="shadow-slider-unit">%</span>
317
+ </div>
318
+ <div class="global-shadow-row">
319
+ <span class="shadow-slider-label">Op. Max</span>
320
+ <input type="range" min="0" max="100" value={Math.round($editorState.overlays.globals.hover.opacityMax * 100)}
321
+ on:pointerdown={() => beginSliderGesture('hover opacity max')}
322
+ on:input={(e) => setOverlayOpacity('hover', 'opacityMax', +e.currentTarget.value / 100)} />
323
+ <input class="shadow-slider-input" type="number" min="0" max="100"
324
+ value={Math.round($editorState.overlays.globals.hover.opacityMax * 100)}
325
+ on:change={(e) => setOverlayOpacity('hover', 'opacityMax', Math.min(100, Math.max(0, +e.currentTarget.value)) / 100)} />
326
+ <span class="shadow-slider-unit">%</span>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ </section>
332
+
333
+ <style>
334
+ .section {
335
+ display: flex;
336
+ flex-direction: column;
337
+ gap: var(--ui-space-16);
338
+ }
339
+
340
+ .section-title {
341
+ font-size: var(--ui-font-size-lg);
342
+ font-weight: var(--ui-font-weight-semibold);
343
+ color: var(--ui-text-primary);
344
+ margin: 0;
345
+ padding-bottom: var(--ui-space-8);
346
+ border-bottom: 1px solid var(--ui-border-subtle);
347
+ }
348
+
349
+ .group-title {
350
+ font-size: var(--ui-font-size-md);
351
+ font-weight: var(--ui-font-weight-semibold);
352
+ color: var(--ui-text-secondary);
353
+ margin: 0;
354
+ }
355
+
356
+ .token-info {
357
+ display: flex;
358
+ flex-direction: column;
359
+ gap: var(--ui-space-2);
360
+ }
361
+
362
+ .token-variable.copyable {
363
+ all: unset;
364
+ font-size: var(--ui-font-size-md);
365
+ color: var(--ui-text-tertiary);
366
+ font-family: var(--ui-font-mono);
367
+ cursor: pointer;
368
+ transition: color var(--ui-transition-fast);
369
+ }
370
+
371
+ .token-variable.copyable:hover {
372
+ color: var(--ui-text-accent);
373
+ }
374
+
375
+ .token-variable.copyable.copied {
376
+ color: var(--ui-text-success);
377
+ }
378
+
379
+ .token-value {
380
+ font-size: var(--ui-font-size-md);
381
+ color: var(--ui-text-muted);
382
+ }
383
+
384
+ /* Overlays */
385
+ .overlays-grid {
386
+ display: grid;
387
+ grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
388
+ gap: var(--ui-space-16);
389
+ }
390
+
391
+ .overlay-item {
392
+ display: flex;
393
+ flex-direction: column;
394
+ align-items: center;
395
+ gap: var(--ui-space-8);
396
+ }
397
+
398
+ .overlay-swatch-wrap {
399
+ width: 4rem;
400
+ height: 4rem;
401
+ border-radius: var(--ui-radius-md);
402
+ position: relative;
403
+ overflow: hidden;
404
+ border: 1px solid var(--ui-border-subtle);
405
+ background-image:
406
+ linear-gradient(45deg, #ccc 25%, transparent 25%),
407
+ linear-gradient(-45deg, #ccc 25%, transparent 25%),
408
+ linear-gradient(45deg, transparent 75%, #ccc 75%),
409
+ linear-gradient(-45deg, transparent 75%, #ccc 75%);
410
+ background-size: 12px 12px;
411
+ background-position: 0 0, 0 6px, 6px -6px, -6px 0px;
412
+ background-color: #fff;
413
+ }
414
+
415
+ .overlay-swatch-wrap--dark {
416
+ background-color: #222;
417
+ background-image:
418
+ linear-gradient(45deg, #333 25%, transparent 25%),
419
+ linear-gradient(-45deg, #333 25%, transparent 25%),
420
+ linear-gradient(45deg, transparent 75%, #333 75%),
421
+ linear-gradient(-45deg, transparent 75%, #333 75%);
422
+ background-size: 12px 12px;
423
+ background-position: 0 0, 0 6px, 6px -6px, -6px 0px;
424
+ }
425
+
426
+ .overlay-swatch {
427
+ position: absolute;
428
+ inset: 0;
429
+ }
430
+
431
+ .overlay-item .token-info {
432
+ align-items: center;
433
+ text-align: center;
434
+ }
435
+
436
+ .overlay-global-editor {
437
+ display: flex;
438
+ flex-direction: column;
439
+ gap: var(--ui-space-8);
440
+ padding: var(--ui-space-12);
441
+ background: var(--ui-surface-lowest);
442
+ border: 1px solid var(--ui-border-subtle);
443
+ border-radius: var(--ui-radius-md);
444
+ margin-top: var(--ui-space-8);
445
+ }
446
+
447
+ .overlay-global-columns {
448
+ display: grid;
449
+ grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
450
+ gap: var(--ui-space-16);
451
+ align-items: start;
452
+ }
453
+
454
+ .overlay-global-col {
455
+ display: flex;
456
+ flex-direction: column;
457
+ gap: var(--ui-space-6);
458
+ min-width: 0;
459
+ }
460
+
461
+ .global-shadow-title {
462
+ font-size: var(--ui-font-size-xs);
463
+ font-weight: var(--ui-font-weight-semibold);
464
+ color: var(--ui-text-secondary);
465
+ margin: 0;
466
+ }
467
+
468
+ .global-shadow-row {
469
+ display: flex;
470
+ align-items: center;
471
+ gap: var(--ui-space-8);
472
+ }
473
+
474
+ .global-shadow-row .shadow-slider-label {
475
+ width: 3rem;
476
+ }
477
+
478
+ .global-shadow-row input[type="range"] {
479
+ flex: 1;
480
+ min-width: 4rem;
481
+ accent-color: var(--ui-text-accent);
482
+ height: 4px;
483
+ cursor: pointer;
484
+ }
485
+
486
+ .shadow-slider-row {
487
+ display: flex;
488
+ align-items: center;
489
+ gap: var(--ui-space-8);
490
+ }
491
+
492
+ .shadow-slider-row input[type="range"] {
493
+ flex: 1;
494
+ min-width: 5rem;
495
+ accent-color: var(--ui-text-accent);
496
+ height: 4px;
497
+ cursor: pointer;
498
+ }
499
+
500
+ .shadow-slider-label {
501
+ font-size: var(--ui-font-size-xs);
502
+ font-weight: var(--ui-font-weight-semibold);
503
+ color: var(--ui-text-tertiary);
504
+ width: 4rem;
505
+ text-align: right;
506
+ flex-shrink: 0;
507
+ }
508
+
509
+ .shadow-slider-input {
510
+ font-size: var(--ui-font-size-xs);
511
+ color: var(--ui-text-primary);
512
+ font-family: var(--ui-font-mono);
513
+ width: 2.5rem;
514
+ text-align: right;
515
+ flex-shrink: 0;
516
+ background: var(--ui-surface-lowest);
517
+ border: 1px solid var(--ui-border-subtle);
518
+ border-radius: var(--ui-radius-sm);
519
+ padding: var(--ui-space-2) var(--ui-space-4);
520
+ -moz-appearance: textfield;
521
+ appearance: textfield;
522
+ }
523
+
524
+ .shadow-slider-input::-webkit-inner-spin-button,
525
+ .shadow-slider-input::-webkit-outer-spin-button {
526
+ -webkit-appearance: none;
527
+ margin: 0;
528
+ }
529
+
530
+ .shadow-slider-input:focus {
531
+ outline: none;
532
+ border-color: var(--ui-border-medium);
533
+ }
534
+
535
+ .shadow-slider-unit {
536
+ font-size: var(--ui-font-size-xs);
537
+ color: var(--ui-text-muted);
538
+ font-family: var(--ui-font-mono);
539
+ width: 1rem;
540
+ flex-shrink: 0;
541
+ }
542
+
543
+ .shadow-edit-btn {
544
+ all: unset;
545
+ font-size: var(--ui-font-size-xs);
546
+ color: var(--ui-text-muted);
547
+ cursor: pointer;
548
+ padding: var(--ui-space-2) var(--ui-space-8);
549
+ border-radius: var(--ui-radius-sm);
550
+ transition: color var(--ui-transition-fast), background var(--ui-transition-fast);
551
+ }
552
+
553
+ .shadow-edit-btn:hover {
554
+ color: var(--ui-text-accent);
555
+ background: var(--ui-surface-low);
556
+ }
557
+
558
+ .shadow-editor {
559
+ display: flex;
560
+ flex-direction: column;
561
+ gap: var(--ui-space-8);
562
+ padding: var(--ui-space-12);
563
+ background: var(--ui-surface-lowest);
564
+ border: 1px solid var(--ui-border-subtle);
565
+ border-radius: var(--ui-radius-md);
566
+ width: 100%;
567
+ min-width: 14rem;
568
+ }
569
+
570
+ .shadow-css-output {
571
+ display: flex;
572
+ flex-direction: column;
573
+ gap: var(--ui-space-4);
574
+ margin-top: var(--ui-space-4);
575
+ padding-top: var(--ui-space-8);
576
+ border-top: 1px solid var(--ui-border-faint);
577
+ }
578
+
579
+ .shadow-css-output code {
580
+ font-size: var(--ui-font-size-xs);
581
+ color: var(--ui-text-accent);
582
+ font-family: var(--ui-font-mono);
583
+ word-break: break-all;
584
+ }
585
+
586
+ .shadow-copy-btn {
587
+ all: unset;
588
+ font-size: var(--ui-font-size-xs);
589
+ color: var(--ui-text-muted);
590
+ cursor: pointer;
591
+ padding: var(--ui-space-2) var(--ui-space-6);
592
+ border: 1px solid var(--ui-border-subtle);
593
+ border-radius: var(--ui-radius-sm);
594
+ text-align: center;
595
+ transition: color var(--ui-transition-fast), border-color var(--ui-transition-fast);
596
+ }
597
+
598
+ .shadow-copy-btn:hover {
599
+ color: var(--ui-text-primary);
600
+ border-color: var(--ui-border-medium);
601
+ }
602
+
603
+ .slider-track {
604
+ flex: 1;
605
+ min-width: 4rem;
606
+ position: relative;
607
+ height: 12px;
608
+ border-radius: var(--ui-radius-sm);
609
+ border: 1px solid var(--ui-border-subtle);
610
+ }
611
+
612
+ .slider-track input[type="range"] {
613
+ position: absolute;
614
+ top: 0;
615
+ left: 0;
616
+ width: 100%;
617
+ height: 100%;
618
+ cursor: pointer;
619
+ margin: 0;
620
+ -webkit-appearance: none;
621
+ appearance: none;
622
+ background: transparent;
623
+ }
624
+
625
+ .slider-track input[type="range"]::-webkit-slider-thumb {
626
+ -webkit-appearance: none;
627
+ width: 10px;
628
+ height: 14px;
629
+ border-radius: 2px;
630
+ background: white;
631
+ border: 1px solid var(--ui-border-subtle);
632
+ box-shadow: 0 1px 3px rgba(0,0,0,0.4);
633
+ cursor: pointer;
634
+ }
635
+
636
+ .slider-track input[type="range"]::-moz-range-thumb {
637
+ width: 10px;
638
+ height: 14px;
639
+ border-radius: 2px;
640
+ background: white;
641
+ border: 1px solid var(--ui-border-subtle);
642
+ box-shadow: 0 1px 3px rgba(0,0,0,0.4);
643
+ cursor: pointer;
644
+ }
645
+
646
+ .slider-track input[type="range"]::-moz-range-track {
647
+ background: transparent;
648
+ border: none;
649
+ }
650
+
651
+ .global-color-group {
652
+ display: flex;
653
+ gap: var(--ui-space-8);
654
+ align-items: stretch;
655
+ }
656
+
657
+ .global-color-swatch {
658
+ width: 2rem;
659
+ flex-shrink: 0;
660
+ border-radius: var(--ui-radius-sm);
661
+ border: 1px solid var(--ui-border-subtle);
662
+ }
663
+
664
+ .global-color-sliders {
665
+ flex: 1;
666
+ display: flex;
667
+ flex-direction: column;
668
+ gap: var(--ui-space-6);
669
+ }
670
+ </style>