@motion-proto/live-tokens 0.1.1 → 0.3.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.
Files changed (224) hide show
  1. package/README.md +168 -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,482 @@
1
+ <script lang="ts">
2
+ import type { GradientStyle, GradientStop } from '../../lib/themeTypes';
3
+ import { beginSliderGesture } from '../../lib/editorStore';
4
+
5
+ /**
6
+ * Gradient style + stop editor (the bar, draggable handles, and selected-
7
+ * stop controls). All persistent gradient state lives in the parent's
8
+ * palette config; this component receives values + dispatches edits via
9
+ * callback props. Drag state (`draggingStopIndex`) and `selectedStopIndex`
10
+ * are owned here because they are pure interaction state — never persisted.
11
+ *
12
+ * The drag handlers wrap edits in `beginSliderGesture` so the editor's
13
+ * undo coalescer treats one drag as one history entry, matching the
14
+ * behaviour of the inline implementation it replaces.
15
+ */
16
+
17
+ interface PaletteStep { label: string; hex: string }
18
+
19
+ export let gradientStyle: GradientStyle;
20
+ export let gradientAngle: number;
21
+ export let gradientSize: 'page' | 'window';
22
+ export let gradientReverse: boolean;
23
+ export let gradientStops: GradientStop[];
24
+ export let gradientBarPreview: string;
25
+ export let paletteComputed: PaletteStep[];
26
+
27
+ export let onSetGradientStyle: (style: GradientStyle) => void;
28
+ export let onSetGradientSize: (size: 'page' | 'window') => void;
29
+ export let onSetGradientAngle: (angle: number) => void;
30
+ export let onSetGradientReverse: (reverse: boolean) => void;
31
+ export let onSetGradientStops: (stops: GradientStop[]) => void;
32
+
33
+ const gradientStyleOptions: { value: GradientStyle; icon: string; title: string }[] = [
34
+ { value: 'linear', icon: '/', title: 'Linear' },
35
+ { value: 'radial', icon: '○', title: 'Radial' },
36
+ { value: 'conic', icon: '◔', title: 'Conic' },
37
+ ];
38
+
39
+ const gradientSizeOptions: { value: 'page' | 'window'; label: string; title: string }[] = [
40
+ { value: 'page', label: 'Page', title: 'Gradient stretches over the full scrollable page' },
41
+ { value: 'window', label: 'Window', title: 'Gradient stays fixed to the viewport' },
42
+ ];
43
+
44
+ let selectedStopIndex = 0;
45
+ let draggingStopIndex: number | null = null;
46
+
47
+ function stopColor(stop: GradientStop): string {
48
+ const ps = paletteComputed.find(p => p.label === stop.paletteLabel);
49
+ return ps ? ps.hex : '#000000';
50
+ }
51
+
52
+ function onAngleInput(e: Event) {
53
+ const v = parseInt((e.currentTarget as HTMLInputElement).value) || 0;
54
+ onSetGradientAngle(v);
55
+ }
56
+
57
+ function onReverseChange(e: Event) {
58
+ onSetGradientReverse((e.currentTarget as HTMLInputElement).checked);
59
+ }
60
+
61
+ function onStopColorChange(e: Event) {
62
+ const v = (e.currentTarget as HTMLSelectElement).value;
63
+ const next = gradientStops.map((s, idx) => idx === selectedStopIndex ? { ...s, paletteLabel: v } : s);
64
+ onSetGradientStops(next);
65
+ }
66
+
67
+ function onStopPositionChange(e: Event) {
68
+ const raw = parseInt((e.currentTarget as HTMLInputElement).value) || 0;
69
+ const v = Math.max(0, Math.min(100, raw));
70
+ const next = gradientStops.map((s, idx) => idx === selectedStopIndex ? { ...s, position: v } : s);
71
+ onSetGradientStops(next);
72
+ }
73
+
74
+ function addGradientStop(position: number) {
75
+ const nearest = paletteComputed.reduce((prev, curr) => {
76
+ const prevDist = Math.abs(parseInt(prev.label) - 500);
77
+ const currDist = Math.abs(parseInt(curr.label) - 500);
78
+ return currDist < prevDist ? curr : prev;
79
+ });
80
+ const next = [...gradientStops, { position, paletteLabel: nearest.label }];
81
+ onSetGradientStops(next);
82
+ selectedStopIndex = next.length - 1;
83
+ }
84
+
85
+ function removeGradientStop(index: number) {
86
+ if (gradientStops.length <= 2) return;
87
+ const next = gradientStops.filter((_, i) => i !== index);
88
+ onSetGradientStops(next);
89
+ if (selectedStopIndex >= next.length) selectedStopIndex = next.length - 1;
90
+ }
91
+
92
+ function handleStopHandleMouseDown(e: MouseEvent, i: number) {
93
+ selectedStopIndex = i;
94
+ draggingStopIndex = i;
95
+ const bar = (e.currentTarget as HTMLElement).parentElement!;
96
+ const rect = bar.getBoundingClientRect();
97
+ beginSliderGesture('drag gradient stop');
98
+ function onMove(me: MouseEvent) {
99
+ if (draggingStopIndex === null) return;
100
+ const newPos = Math.round(Math.max(0, Math.min(100, ((me.clientX - rect.left) / rect.width) * 100)));
101
+ const next = gradientStops.map((s, idx) => idx === draggingStopIndex ? { ...s, position: newPos } : s);
102
+ onSetGradientStops(next);
103
+ }
104
+ function onUp() {
105
+ draggingStopIndex = null;
106
+ window.removeEventListener('mousemove', onMove);
107
+ window.removeEventListener('mouseup', onUp);
108
+ }
109
+ window.addEventListener('mousemove', onMove);
110
+ window.addEventListener('mouseup', onUp);
111
+ }
112
+
113
+ function handleStopBarMouseDown(e: MouseEvent) {
114
+ const bar = (e.currentTarget as HTMLElement);
115
+ const rect = bar.getBoundingClientRect();
116
+ const pos = Math.round(((e.clientX - rect.left) / rect.width) * 100);
117
+
118
+ const nearIdx = gradientStops.findIndex(s => Math.abs(s.position - pos) < 4);
119
+ if (nearIdx >= 0) {
120
+ selectedStopIndex = nearIdx;
121
+ draggingStopIndex = nearIdx;
122
+ beginSliderGesture('drag gradient stop');
123
+ } else {
124
+ addGradientStop(Math.max(0, Math.min(100, pos)));
125
+ draggingStopIndex = gradientStops.length - 1;
126
+ beginSliderGesture('drag gradient stop');
127
+ }
128
+
129
+ function onMove(me: MouseEvent) {
130
+ if (draggingStopIndex === null) return;
131
+ const newPos = Math.round(Math.max(0, Math.min(100, ((me.clientX - rect.left) / rect.width) * 100)));
132
+ const next = gradientStops.map((s, idx) => idx === draggingStopIndex ? { ...s, position: newPos } : s);
133
+ onSetGradientStops(next);
134
+ }
135
+ function onUp() {
136
+ draggingStopIndex = null;
137
+ window.removeEventListener('mousemove', onMove);
138
+ window.removeEventListener('mouseup', onUp);
139
+ }
140
+ window.addEventListener('mousemove', onMove);
141
+ window.addEventListener('mouseup', onUp);
142
+ }
143
+ </script>
144
+
145
+ <div class="gradient-controls">
146
+ <div class="gradient-row">
147
+ <span class="gradient-label">Style:</span>
148
+ <div class="gradient-style-buttons">
149
+ {#each gradientStyleOptions as opt}
150
+ <button
151
+ class="style-btn"
152
+ class:active={gradientStyle === opt.value}
153
+ type="button"
154
+ title={opt.title}
155
+ on:click={() => onSetGradientStyle(opt.value)}
156
+ >{opt.icon}</button>
157
+ {/each}
158
+ </div>
159
+ </div>
160
+
161
+ <div class="gradient-row">
162
+ <span class="gradient-label">Angle:</span>
163
+ <input
164
+ class="gradient-angle-input"
165
+ type="number"
166
+ min="0"
167
+ max="360"
168
+ value={gradientAngle}
169
+ on:input={onAngleInput}
170
+ />
171
+ <span class="gradient-unit">deg</span>
172
+ <input
173
+ class="gradient-angle-slider"
174
+ type="range"
175
+ min="0"
176
+ max="360"
177
+ value={gradientAngle}
178
+ on:input={onAngleInput}
179
+ />
180
+ </div>
181
+
182
+ <div class="gradient-row">
183
+ <span class="gradient-label">Size:</span>
184
+ <div class="gradient-style-buttons">
185
+ {#each gradientSizeOptions as opt}
186
+ <button
187
+ class="style-btn size-btn"
188
+ class:active={gradientSize === opt.value}
189
+ type="button"
190
+ title={opt.title}
191
+ on:click={() => onSetGradientSize(opt.value)}
192
+ >{opt.label}</button>
193
+ {/each}
194
+ </div>
195
+ </div>
196
+
197
+ <div class="gradient-row">
198
+ <label class="gradient-checkbox-label">
199
+ <input type="checkbox" checked={gradientReverse} on:change={onReverseChange} />
200
+ Reverse
201
+ </label>
202
+ </div>
203
+
204
+ <!-- Gradient stop bar -->
205
+ <div class="gradient-stop-bar-wrapper">
206
+ <div class="gradient-stop-handles">
207
+ {#each gradientStops as stop, i}
208
+ <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
209
+ <div
210
+ class="gradient-stop-handle"
211
+ class:selected={selectedStopIndex === i}
212
+ style="left: {stop.position}%; --stop-color: {stopColor(stop)}"
213
+ on:mousedown|stopPropagation={(e) => handleStopHandleMouseDown(e, i)}
214
+ role="button"
215
+ tabindex="0"
216
+ on:keydown={(e) => {
217
+ if (e.key === 'Delete' || e.key === 'Backspace') removeGradientStop(i);
218
+ }}
219
+ >
220
+ <div class="stop-swatch" style="background: {stopColor(stop)}"></div>
221
+ <div class="stop-arrow"></div>
222
+ </div>
223
+ {/each}
224
+ </div>
225
+ <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
226
+ <div
227
+ class="gradient-stop-bar"
228
+ style="background: {gradientBarPreview}"
229
+ on:mousedown={handleStopBarMouseDown}
230
+ role="slider"
231
+ tabindex="0"
232
+ aria-label="Gradient stops"
233
+ aria-valuenow={gradientStops[selectedStopIndex]?.position ?? 0}
234
+ aria-valuemin="0"
235
+ aria-valuemax="100"
236
+ ></div>
237
+ </div>
238
+
239
+ <!-- Selected stop controls -->
240
+ {#if gradientStops[selectedStopIndex]}
241
+ <div class="gradient-row stop-controls">
242
+ <span class="gradient-label">Color:</span>
243
+ <select
244
+ class="gradient-select"
245
+ value={gradientStops[selectedStopIndex].paletteLabel}
246
+ on:change={onStopColorChange}
247
+ >
248
+ {#each paletteComputed as ps}
249
+ <option value={ps.label}>{ps.label}</option>
250
+ {/each}
251
+ </select>
252
+ <div class="stop-color-preview" style="background: {stopColor(gradientStops[selectedStopIndex])}"></div>
253
+ <span class="gradient-label">Pos:</span>
254
+ <input
255
+ class="gradient-pos-input"
256
+ type="number"
257
+ min="0"
258
+ max="100"
259
+ value={gradientStops[selectedStopIndex].position}
260
+ on:change={onStopPositionChange}
261
+ />
262
+ <span class="gradient-unit">%</span>
263
+ {#if gradientStops.length > 2}
264
+ <button
265
+ class="stop-remove-btn"
266
+ type="button"
267
+ title="Remove stop"
268
+ on:click={() => removeGradientStop(selectedStopIndex)}
269
+ >&times;</button>
270
+ {/if}
271
+ </div>
272
+ {/if}
273
+ </div>
274
+
275
+ <style>
276
+ .gradient-controls {
277
+ margin-top: var(--ui-space-8);
278
+ padding: var(--ui-space-12);
279
+ background: var(--ui-surface-low);
280
+ border: 1px solid var(--ui-border-faint);
281
+ border-radius: var(--ui-radius-lg);
282
+ display: flex;
283
+ flex-direction: column;
284
+ gap: var(--ui-space-8);
285
+ }
286
+
287
+ .gradient-row {
288
+ display: flex;
289
+ align-items: center;
290
+ gap: var(--ui-space-8);
291
+ }
292
+
293
+ .gradient-label {
294
+ font-size: var(--ui-font-size-md);
295
+ color: var(--ui-text-secondary);
296
+ min-width: 36px;
297
+ flex-shrink: 0;
298
+ }
299
+
300
+ .gradient-style-buttons {
301
+ display: flex;
302
+ gap: var(--ui-space-2);
303
+ }
304
+
305
+ .style-btn {
306
+ width: 28px;
307
+ height: 28px;
308
+ border: 1px solid var(--ui-border-subtle);
309
+ border-radius: var(--ui-radius-md);
310
+ background: var(--ui-surface-lowest);
311
+ color: var(--ui-text-secondary);
312
+ cursor: pointer;
313
+ font-size: var(--ui-font-size-md);
314
+ display: flex;
315
+ align-items: center;
316
+ justify-content: center;
317
+ padding: 0;
318
+ }
319
+
320
+ .style-btn.active {
321
+ border-color: var(--ui-text-secondary);
322
+ background: var(--ui-surface-high);
323
+ color: var(--ui-text-primary);
324
+ }
325
+
326
+ .style-btn:hover {
327
+ border-color: var(--ui-border-medium);
328
+ }
329
+
330
+ .size-btn {
331
+ width: auto;
332
+ padding: 0 8px;
333
+ }
334
+
335
+ .gradient-angle-input,
336
+ .gradient-pos-input {
337
+ width: 52px;
338
+ padding: 2px 6px;
339
+ font-size: var(--ui-font-size-md);
340
+ background: var(--ui-surface-lowest);
341
+ border: 1px solid var(--ui-border-subtle);
342
+ border-radius: var(--ui-radius-md);
343
+ color: var(--ui-text-primary);
344
+ text-align: center;
345
+ }
346
+
347
+ .gradient-angle-slider {
348
+ flex: 1;
349
+ min-width: 60px;
350
+ height: 4px;
351
+ accent-color: var(--ui-text-secondary);
352
+ }
353
+
354
+ .gradient-unit {
355
+ font-size: var(--ui-font-size-md);
356
+ color: var(--ui-text-tertiary);
357
+ }
358
+
359
+ .gradient-checkbox-label {
360
+ display: flex;
361
+ align-items: center;
362
+ gap: var(--ui-space-6);
363
+ font-size: var(--ui-font-size-md);
364
+ color: var(--ui-text-secondary);
365
+ cursor: pointer;
366
+ user-select: none;
367
+ }
368
+
369
+ .gradient-checkbox-label input {
370
+ margin: 0;
371
+ cursor: pointer;
372
+ }
373
+
374
+ .gradient-select {
375
+ padding: 2px 6px;
376
+ font-size: var(--ui-font-size-md);
377
+ background: var(--ui-surface-lowest);
378
+ border: 1px solid var(--ui-border-subtle);
379
+ border-radius: var(--ui-radius-md);
380
+ color: var(--ui-text-primary);
381
+ }
382
+
383
+ .stop-color-preview {
384
+ width: 20px;
385
+ height: 20px;
386
+ border-radius: var(--ui-radius-sm);
387
+ border: 1px solid var(--ui-border-subtle);
388
+ flex-shrink: 0;
389
+ }
390
+
391
+ .gradient-stop-bar-wrapper {
392
+ padding: 0;
393
+ display: flex;
394
+ flex-direction: column;
395
+ gap: 0;
396
+ }
397
+
398
+ .gradient-stop-handles {
399
+ position: relative;
400
+ height: 28px;
401
+ }
402
+
403
+ .gradient-stop-bar {
404
+ position: relative;
405
+ height: 24px;
406
+ border-radius: var(--ui-radius-md);
407
+ border: 1px solid var(--ui-border-subtle);
408
+ cursor: crosshair;
409
+ }
410
+
411
+ .gradient-stop-handle {
412
+ position: absolute;
413
+ top: 0;
414
+ transform: translateX(-50%);
415
+ display: flex;
416
+ flex-direction: column;
417
+ align-items: center;
418
+ cursor: grab;
419
+ z-index: 1;
420
+ }
421
+
422
+ .gradient-stop-handle.selected {
423
+ z-index: 2;
424
+ }
425
+
426
+ .stop-swatch {
427
+ width: 16px;
428
+ height: 16px;
429
+ border-radius: var(--ui-radius-sm);
430
+ border: 2px solid var(--ui-border-medium);
431
+ flex-shrink: 0;
432
+ }
433
+
434
+ .gradient-stop-handle.selected .stop-swatch {
435
+ border-color: var(--ui-text-primary);
436
+ }
437
+
438
+ .gradient-stop-handle:hover .stop-swatch {
439
+ border-color: var(--ui-text-secondary);
440
+ }
441
+
442
+ .stop-arrow {
443
+ width: 0;
444
+ height: 0;
445
+ border-left: 4px solid transparent;
446
+ border-right: 4px solid transparent;
447
+ border-top: 6px solid var(--ui-border-medium);
448
+ }
449
+
450
+ .gradient-stop-handle.selected .stop-arrow {
451
+ border-top-color: var(--ui-text-primary);
452
+ }
453
+
454
+ .gradient-stop-handle:hover .stop-arrow {
455
+ border-top-color: var(--ui-text-secondary);
456
+ }
457
+
458
+ .stop-controls {
459
+ flex-wrap: wrap;
460
+ }
461
+
462
+ .stop-remove-btn {
463
+ width: 20px;
464
+ height: 20px;
465
+ border: 1px solid var(--ui-border-subtle);
466
+ border-radius: var(--ui-radius-md);
467
+ background: var(--ui-surface-lowest);
468
+ color: var(--ui-text-tertiary);
469
+ cursor: pointer;
470
+ font-size: var(--ui-font-size-md);
471
+ display: flex;
472
+ align-items: center;
473
+ justify-content: center;
474
+ padding: 0;
475
+ margin-left: auto;
476
+ }
477
+
478
+ .stop-remove-btn:hover {
479
+ border-color: var(--ui-border-strong);
480
+ color: var(--ui-text-primary);
481
+ }
482
+ </style>