@motion-proto/live-tokens 0.1.0 → 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 (225) 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 +51 -23
  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 +265 -82
  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 -31
  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 +49 -0
  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 -41
  158. package/src/{showcase → ui}/TextTab.svelte +27 -29
  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/pageSource.ts +0 -6
  195. package/src/lib/tokenInit.ts +0 -29
  196. package/src/lib/tokenService.ts +0 -144
  197. package/src/lib/tokenTypes.ts +0 -45
  198. package/src/pages/Admin.svelte +0 -100
  199. package/src/pages/ShowcasePage.svelte +0 -146
  200. package/src/showcase/BackupBrowser.svelte +0 -617
  201. package/src/showcase/ComponentsTab.svelte +0 -107
  202. package/src/showcase/PaletteEditor.svelte +0 -2579
  203. package/src/showcase/PaletteSelector.svelte +0 -627
  204. package/src/showcase/TokenMap.svelte +0 -54
  205. package/src/showcase/VariablesTab.svelte +0 -2657
  206. package/src/showcase/VisualsTab.svelte +0 -233
  207. package/src/showcase/demos/BadgeDemo.svelte +0 -58
  208. package/src/showcase/demos/CardDemo.svelte +0 -52
  209. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -194
  210. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -56
  211. package/src/showcase/demos/DialogDemo.svelte +0 -42
  212. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -27
  213. package/src/showcase/demos/NotificationDemo.svelte +0 -149
  214. package/src/showcase/demos/ProgressBarDemo.svelte +0 -56
  215. package/src/showcase/demos/RadioButtonDemo.svelte +0 -58
  216. package/src/showcase/demos/SectionDividerDemo.svelte +0 -79
  217. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -457
  218. package/src/showcase/demos/TabBarDemo.svelte +0 -60
  219. package/src/showcase/demos/TooltipDemo.svelte +0 -54
  220. package/src/showcase/editor.css +0 -93
  221. package/src/showcase/index.ts +0 -17
  222. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  223. package/src/styles/fonts/Domine/OFL.txt +0 -97
  224. package/src/styles/fonts/Domine/README.txt +0 -66
  225. /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>