@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,526 @@
1
+ <script lang="ts">
2
+ import ScaleCurveEditor from './ScaleCurveEditor.svelte';
3
+ import { type CurveAnchor, lightnessCurveConfig, saturationCurveConfig, textLightnessCurveConfig } from '../curveEngine';
4
+ import { scaleToCssVar } from '../../lib/paletteDerivation';
5
+
6
+ /**
7
+ * Per-scale derived swatch section used for Text, Surfaces, and Borders
8
+ * (in both chromatic and gray modes). Renders the scale header (snap/clear/edit
9
+ * buttons), the swatch grid (derived swatch + override slot per step), and
10
+ * the inline 2-curve editor when `editorOpen` is true.
11
+ *
12
+ * Linked/unlinked semantics live in the parent — this component receives the
13
+ * pre-computed `overrides`, `derivedHexFor`, and `effectiveHexFor` and renders
14
+ * them unchanged. No state mutation here; all interactions dispatch up.
15
+ */
16
+
17
+ interface Step {
18
+ name: string;
19
+ position: number;
20
+ lightness?: number;
21
+ saturation?: number;
22
+ }
23
+
24
+ interface Scale {
25
+ title: string;
26
+ isText: boolean;
27
+ steps: Step[];
28
+ }
29
+
30
+ type Channel = 'lightness' | 'saturation';
31
+
32
+ interface ScaleCurveDef {
33
+ lightness: () => CurveAnchor[];
34
+ saturation: () => CurveAnchor[];
35
+ }
36
+
37
+ interface PaletteStep { label: string; hex: string }
38
+
39
+ export let scale: Scale;
40
+ export let editorOpen: boolean;
41
+ export let snapped: boolean;
42
+ export let supportsSnap: boolean;
43
+ export let cssNamespace: string | null;
44
+ export let scaleCurves: Record<string, { lightness: CurveAnchor[]; saturation: CurveAnchor[] }>;
45
+ export let curveOffset: Record<string, number>;
46
+ export let defaultScaleCurves: Record<string, ScaleCurveDef>;
47
+ export let overrides: Record<string, string>;
48
+ export let editingKey: string | null;
49
+ export let snapPickerKey: string | null;
50
+ export let copiedKey: string | null;
51
+ export let copiedLabelKey: string | null;
52
+ export let paletteComputed: PaletteStep[];
53
+
54
+ export let derivedHexFor: (step: Step, scaleTitle: string) => string;
55
+ export let effectiveHexFor: (key: string, step: Step, scaleTitle: string) => string;
56
+ export let stepKeyFor: (scaleTitle: string, stepName: string) => string;
57
+ export let scaleCurveKeyFor: (scaleTitle: string, channel: Channel) => string;
58
+
59
+ export let onToggleSnap: (scale: Scale) => void;
60
+ export let onClearScaleOverrides: (scale: Scale) => void;
61
+ export let onToggleEditor: (scaleTitle: string) => void;
62
+ export let onResetOverride: (key: string) => void;
63
+ export let onOverrideClick: (key: string, step: Step, scaleTitle: string) => void;
64
+ export let onSnappedClick: (key: string) => void;
65
+ export let onSelectSnapValue: (key: string, paletteHex: string, scaleTitle: string) => void;
66
+ export let onCopyHex: (key: string, hex: string, event?: MouseEvent) => void;
67
+ export let onCopyVarName: (key: string, varName: string, event?: MouseEvent) => void;
68
+ export let onSetScaleCurve: (scaleTitle: string, channel: Channel, anchors: CurveAnchor[]) => void;
69
+ export let onOffsetChange: (key: string, value: number) => void;
70
+
71
+ interface CurveDescriptor {
72
+ key: string;
73
+ anchors: CurveAnchor[];
74
+ cfg: typeof lightnessCurveConfig;
75
+ defaults: CurveAnchor[];
76
+ channel: Channel;
77
+ }
78
+
79
+ $: curveDescriptors = ((): CurveDescriptor[] => {
80
+ const lightnessCfg = scale.isText ? textLightnessCurveConfig : lightnessCurveConfig;
81
+ const sc = scaleCurves[scale.title];
82
+ const defs = defaultScaleCurves[scale.title];
83
+ if (!sc || !defs) return [];
84
+ return [
85
+ { key: scaleCurveKeyFor(scale.title, 'lightness'), anchors: sc.lightness, cfg: lightnessCfg, defaults: defs.lightness(), channel: 'lightness' },
86
+ { key: scaleCurveKeyFor(scale.title, 'saturation'), anchors: sc.saturation, cfg: saturationCurveConfig, defaults: defs.saturation(), channel: 'saturation' },
87
+ ];
88
+ })();
89
+ </script>
90
+
91
+ <div class="scale-section">
92
+ <div class="scale-header">
93
+ <h4 class="scale-title">{scale.title}</h4>
94
+ {#if supportsSnap}
95
+ <button
96
+ class="edit-toggle"
97
+ class:active={snapped}
98
+ type="button"
99
+ on:click={() => onToggleSnap(scale)}
100
+ >{snapped ? 'Unsnap' : 'Snap All'}</button>
101
+ {/if}
102
+ <button class="edit-toggle" type="button" on:click={() => onClearScaleOverrides(scale)}>Clear Overrides</button>
103
+ <button
104
+ class="edit-toggle"
105
+ type="button"
106
+ on:click={() => onToggleEditor(scale.title)}
107
+ >{editorOpen ? 'Close' : 'Edit'}</button>
108
+ </div>
109
+ <div class="swatch-grid" style="--swatch-cols: {scale.steps.length}; --swatch-gap: var(--ui-space-8)">
110
+ {#each scale.steps as step}
111
+ {@const k = stepKeyFor(scale.title, step.name)}
112
+ {@const hex = effectiveHexFor(k, step, scale.title)}
113
+ {@const dHex = derivedHexFor(step, scale.title)}
114
+ <div class="step-column">
115
+ <button class="step-label copyable-label" class:copied={copiedLabelKey === k} type="button" on:click={(e) => { const v = scaleToCssVar(scale.title, step.name, cssNamespace); if (v) onCopyVarName(k, v, e); }}>
116
+ {copiedLabelKey === k ? 'copied!' : step.name}
117
+ </button>
118
+ {#if scale.isText}
119
+ <div
120
+ class="swatch derived text-swatch"
121
+ class:dimmed={k in overrides}
122
+ class:clickable={k in overrides}
123
+ on:click={() => onResetOverride(k)}
124
+ role={k in overrides ? 'button' : undefined}
125
+ tabindex={k in overrides ? 0 : undefined}
126
+ on:keydown={(e) => k in overrides && e.key === 'Enter' && onResetOverride(k)}
127
+ >
128
+ <span style="color: {dHex}">Ag</span>
129
+ </div>
130
+ <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
131
+ <div
132
+ class="swatch override-slot text-swatch"
133
+ class:active={editingKey === k}
134
+ class:populated={k in overrides}
135
+ class:matching={k in overrides && overrides[k] === dHex}
136
+ on:click={() => onOverrideClick(k, step, scale.title)}
137
+ role="button"
138
+ tabindex="0"
139
+ on:keydown={(e) => e.key === 'Enter' && onOverrideClick(k, step, scale.title)}
140
+ >
141
+ {#if k in overrides}
142
+ <span style="color: {overrides[k]}">Ag</span>
143
+ {/if}
144
+ </div>
145
+ {:else}
146
+ <div
147
+ class="swatch derived"
148
+ class:border-preview={scale.title === 'Borders'}
149
+ class:dimmed={k in overrides}
150
+ class:clickable={k in overrides}
151
+ style={scale.title === 'Borders'
152
+ ? `border: 3px solid ${dHex}`
153
+ : `background: ${dHex}`}
154
+ on:click={() => onResetOverride(k)}
155
+ role={k in overrides ? 'button' : undefined}
156
+ tabindex={k in overrides ? 0 : undefined}
157
+ on:keydown={(e) => k in overrides && e.key === 'Enter' && onResetOverride(k)}
158
+ ></div>
159
+ <div class="override-slot-wrapper">
160
+ <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
161
+ <div
162
+ class="swatch override-slot"
163
+ class:border-preview={scale.title === 'Borders'}
164
+ class:active={editingKey === k || snapPickerKey === k}
165
+ class:populated={k in overrides}
166
+ class:matching={k in overrides && overrides[k] === dHex}
167
+ on:click={() => snapped ? onSnappedClick(k) : onOverrideClick(k, step, scale.title)}
168
+ role="button"
169
+ tabindex="0"
170
+ on:keydown={(e) => e.key === 'Enter' && (snapped ? onSnappedClick(k) : onOverrideClick(k, step, scale.title))}
171
+ >
172
+ {#if k in overrides}
173
+ {#if scale.title === 'Borders'}
174
+ <div class="override-fill border-fill" style="border: 3px solid {overrides[k]}"></div>
175
+ {:else}
176
+ <div class="override-fill" style="background: {overrides[k]}"></div>
177
+ {/if}
178
+ {#if snapped}
179
+ {@const plabel = paletteComputed.find(ps => ps.hex === overrides[k])?.label ?? null}
180
+ {#if plabel}
181
+ <span class="palette-step-label">{plabel}</span>
182
+ {/if}
183
+ {/if}
184
+ {/if}
185
+ </div>
186
+ {#if snapPickerKey === k}
187
+ <div class="snap-picker">
188
+ {#each paletteComputed as ps}
189
+ <button
190
+ class="snap-picker-item"
191
+ class:selected={overrides[k] === ps.hex}
192
+ type="button"
193
+ on:click={() => onSelectSnapValue(k, ps.hex, scale.title)}
194
+ >
195
+ <span class="snap-picker-swatch" style="background: {ps.hex}"></span>
196
+ <span class="snap-picker-label">{ps.label}</span>
197
+ </button>
198
+ {/each}
199
+ </div>
200
+ {/if}
201
+ </div>
202
+ {/if}
203
+ <button
204
+ class="step-hex"
205
+ class:copied={copiedKey === k}
206
+ type="button"
207
+ on:click={(e) => onCopyHex(k, hex, e)}
208
+ >{copiedKey === k ? 'copied!' : hex}</button>
209
+ </div>
210
+ {/each}
211
+ {#if editorOpen}
212
+ <div class="curve-grid-span" style="grid-column: 1 / -1">
213
+ {#each curveDescriptors as curve (curve.key)}
214
+ <ScaleCurveEditor
215
+ curveKey={curve.key}
216
+ anchors={curve.anchors}
217
+ cfg={curve.cfg}
218
+ stepCount={scale.steps.length}
219
+ defaults={curve.defaults}
220
+ offset={curveOffset[curve.key] ?? 0}
221
+ onAnchorsChange={(a) => onSetScaleCurve(scale.title, curve.channel, a)}
222
+ onOffsetChange={onOffsetChange}
223
+ />
224
+ {/each}
225
+ </div>
226
+ {/if}
227
+ </div>
228
+ </div>
229
+
230
+ <style>
231
+ .scale-section {
232
+ display: flex;
233
+ flex-direction: column;
234
+ gap: var(--ui-space-6);
235
+ min-width: 0;
236
+ max-width: 100%;
237
+ }
238
+
239
+ .scale-header {
240
+ display: flex;
241
+ align-items: center;
242
+ gap: var(--ui-space-8);
243
+ }
244
+
245
+ .scale-title {
246
+ font-size: var(--ui-font-size-md);
247
+ font-weight: var(--ui-font-weight-semibold);
248
+ color: var(--ui-text-tertiary);
249
+ margin: 0;
250
+ text-transform: uppercase;
251
+ letter-spacing: 0.05em;
252
+ }
253
+
254
+ .edit-toggle {
255
+ font-size: var(--ui-font-size-md);
256
+ color: var(--ui-text-tertiary);
257
+ background: none;
258
+ border: 1px solid var(--ui-border-subtle);
259
+ border-radius: var(--ui-radius-sm);
260
+ padding: var(--ui-space-2) var(--ui-space-6);
261
+ cursor: pointer;
262
+ }
263
+
264
+ .edit-toggle:hover {
265
+ color: var(--ui-text-primary);
266
+ border-color: var(--ui-border-medium);
267
+ }
268
+
269
+ .edit-toggle.active {
270
+ color: var(--ui-text-primary);
271
+ border-color: var(--ui-border-medium);
272
+ background: var(--ui-surface-high);
273
+ }
274
+
275
+ .swatch-grid {
276
+ display: grid;
277
+ grid-template-columns: repeat(var(--swatch-cols), minmax(0, 1fr));
278
+ gap: var(--ui-space-4) var(--swatch-gap, var(--ui-space-4));
279
+ align-items: start;
280
+ justify-content: start;
281
+ min-width: 0;
282
+ max-width: calc(var(--swatch-cols) * 4rem + (var(--swatch-cols) - 1) * var(--swatch-gap, var(--ui-space-4)));
283
+ }
284
+
285
+ .curve-grid-span {
286
+ display: flex;
287
+ flex-direction: column;
288
+ gap: var(--ui-space-8);
289
+ }
290
+
291
+ .step-column {
292
+ display: flex;
293
+ flex-direction: column;
294
+ align-items: stretch;
295
+ justify-self: stretch;
296
+ gap: var(--ui-space-2);
297
+ width: auto;
298
+ min-width: 0;
299
+ overflow: visible;
300
+ }
301
+
302
+ .step-label {
303
+ font-size: var(--ui-font-size-sm);
304
+ color: var(--ui-text-secondary);
305
+ text-align: center;
306
+ line-height: 1;
307
+ height: var(--ui-font-size-xs);
308
+ display: flex;
309
+ align-items: flex-end;
310
+ }
311
+
312
+ .step-label.copyable-label {
313
+ background: none;
314
+ border: none;
315
+ padding: 0;
316
+ cursor: pointer;
317
+ font: inherit;
318
+ font-size: var(--ui-font-size-sm);
319
+ justify-content: center;
320
+ transition: color var(--ui-transition-fast);
321
+ }
322
+
323
+ .step-label.copyable-label:hover {
324
+ color: var(--ui-text-primary);
325
+ }
326
+
327
+ .step-label.copyable-label.copied {
328
+ color: var(--ui-text-accent, var(--ui-text-primary));
329
+ }
330
+
331
+ .swatch {
332
+ width: 100%;
333
+ height: 2rem;
334
+ border-radius: var(--ui-radius-sm);
335
+ border: 1px solid var(--ui-border-faint);
336
+ }
337
+
338
+ .swatch.text-swatch {
339
+ display: flex;
340
+ align-items: center;
341
+ justify-content: center;
342
+ background: none;
343
+ font-size: var(--ui-font-size-md);
344
+ font-weight: var(--ui-font-weight-bold);
345
+ }
346
+
347
+ .swatch.border-preview {
348
+ background: none;
349
+ border-radius: var(--ui-radius-md);
350
+ }
351
+
352
+ .override-fill.border-fill {
353
+ background: none;
354
+ border-radius: var(--ui-radius-sm);
355
+ }
356
+
357
+ .swatch.derived.clickable {
358
+ cursor: pointer;
359
+ }
360
+
361
+ .swatch.derived.clickable:hover {
362
+ outline: 2px solid var(--ui-border-medium);
363
+ outline-offset: 1px;
364
+ }
365
+
366
+ .swatch.derived.dimmed {
367
+ position: relative;
368
+ }
369
+
370
+ .swatch.derived.dimmed::after {
371
+ content: '';
372
+ position: absolute;
373
+ inset: 0;
374
+ background: linear-gradient(
375
+ to bottom left,
376
+ transparent calc(50% - 0.5px),
377
+ white calc(50% - 0.5px),
378
+ white calc(50% + 0.5px),
379
+ transparent calc(50% + 0.5px)
380
+ );
381
+ border-radius: inherit;
382
+ }
383
+
384
+ .override-slot {
385
+ border-style: dashed;
386
+ border-color: var(--ui-border-subtle);
387
+ cursor: pointer;
388
+ position: relative;
389
+ overflow: hidden;
390
+ }
391
+
392
+ .override-slot:hover {
393
+ border-color: var(--ui-border-medium);
394
+ }
395
+
396
+ .override-slot.active {
397
+ border-color: var(--ui-border-strong);
398
+ outline: 1px solid var(--ui-border-medium);
399
+ outline-offset: 1px;
400
+ }
401
+
402
+ .override-slot.populated {
403
+ border-color: var(--ui-border-medium);
404
+ }
405
+
406
+ .override-slot.matching::after {
407
+ content: '';
408
+ position: absolute;
409
+ inset: 0;
410
+ background: linear-gradient(
411
+ to bottom left,
412
+ transparent calc(50% - 0.5px),
413
+ white calc(50% - 0.5px),
414
+ white calc(50% + 0.5px),
415
+ transparent calc(50% + 0.5px)
416
+ );
417
+ border-radius: inherit;
418
+ pointer-events: none;
419
+ }
420
+
421
+ .override-fill {
422
+ position: absolute;
423
+ inset: 0;
424
+ border-radius: inherit;
425
+ }
426
+
427
+ .palette-step-label {
428
+ position: absolute;
429
+ inset: 0;
430
+ display: flex;
431
+ align-items: center;
432
+ justify-content: center;
433
+ font-size: var(--ui-font-size-md);
434
+ font-weight: var(--ui-font-weight-semibold);
435
+ color: white;
436
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
437
+ pointer-events: none;
438
+ z-index: 1;
439
+ }
440
+
441
+ .override-slot-wrapper {
442
+ position: relative;
443
+ }
444
+
445
+ .snap-picker {
446
+ position: absolute;
447
+ top: 100%;
448
+ left: 50%;
449
+ transform: translateX(-50%);
450
+ z-index: 10;
451
+ margin-top: var(--ui-space-4);
452
+ background: var(--ui-surface-lowest);
453
+ border: 1px solid var(--ui-border-medium);
454
+ border-radius: var(--ui-radius-md);
455
+ padding: var(--ui-space-4);
456
+ display: flex;
457
+ flex-direction: column;
458
+ gap: 1px;
459
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
460
+ min-width: 5.5rem;
461
+ }
462
+
463
+ .snap-picker-item {
464
+ display: flex;
465
+ align-items: center;
466
+ gap: var(--ui-space-6);
467
+ padding: var(--ui-space-2) var(--ui-space-6);
468
+ border: none;
469
+ background: none;
470
+ border-radius: var(--ui-radius-sm);
471
+ cursor: pointer;
472
+ color: var(--ui-text-secondary);
473
+ font-size: var(--ui-font-size-md);
474
+ font-family: var(--ui-font-mono);
475
+ white-space: nowrap;
476
+ }
477
+
478
+ .snap-picker-item:hover {
479
+ background: var(--ui-surface-high);
480
+ color: var(--ui-text-primary);
481
+ }
482
+
483
+ .snap-picker-item.selected {
484
+ background: var(--ui-surface-highest);
485
+ color: var(--ui-text-primary);
486
+ font-weight: var(--ui-font-weight-semibold);
487
+ }
488
+
489
+ .snap-picker-swatch {
490
+ display: inline-block;
491
+ width: 1rem;
492
+ height: 1rem;
493
+ border-radius: var(--ui-radius-sm);
494
+ border: 1px solid var(--ui-border-faint);
495
+ flex-shrink: 0;
496
+ }
497
+
498
+ .snap-picker-label {
499
+ line-height: 1;
500
+ }
501
+
502
+ .step-hex {
503
+ font-size: var(--ui-font-size-xs);
504
+ color: var(--ui-text-secondary);
505
+ font-family: var(--ui-font-mono);
506
+ cursor: pointer;
507
+ padding: 1px var(--ui-space-2);
508
+ border-radius: var(--ui-radius-sm);
509
+ white-space: nowrap;
510
+ background: none;
511
+ border: none;
512
+ text-align: center;
513
+ min-width: 0;
514
+ overflow: hidden;
515
+ text-overflow: ellipsis;
516
+ }
517
+
518
+ .step-hex:hover {
519
+ background: var(--ui-surface-highest);
520
+ color: var(--ui-text-primary);
521
+ }
522
+
523
+ .step-hex.copied {
524
+ color: var(--ui-text-accent);
525
+ }
526
+ </style>
@@ -0,0 +1,165 @@
1
+ <script lang="ts">
2
+ import ColorEditPanel from '../ColorEditPanel.svelte';
3
+ import Toggle from '../Toggle.svelte';
4
+ import { beginSliderGesture } from '../../lib/editorStore';
5
+
6
+ /**
7
+ * The header swatch + label + base-hex + (when active) the ColorEditPanel
8
+ * for editing the palette's base colour. In chromatic mode the user picks
9
+ * an arbitrary hex; in gray mode the user picks tint hue + chroma and the
10
+ * hex is derived.
11
+ *
12
+ * State (`editing`, scope handle) is owned by the parent — this component
13
+ * fires callbacks (`onStartEdit`, `onConfirm`, `onCancel`, etc.). The
14
+ * parent decides whether to apply the chromatic-vs-gray snapshot dance.
15
+ */
16
+
17
+ export let label: string;
18
+ export let displayLabel: string | null = null;
19
+ export let mode: 'chromatic' | 'gray';
20
+ export let baseColor: string;
21
+ export let gray500Hex: string;
22
+ export let tintHue: number;
23
+ export let tintChroma: number;
24
+ export let anchorToBase: boolean;
25
+ export let isEditingBase: boolean;
26
+ export let panelOpen: boolean;
27
+ export let editingColor: string | null;
28
+ export let editPanelTitle: string | null;
29
+ export let copiedKey: string | null;
30
+
31
+ export let onStartEdit: () => void;
32
+ export let onConfirm: () => void;
33
+ export let onCancel: () => void;
34
+ export let onColorChange: (hex: string) => void;
35
+ export let onTintChange: (hue: number, chroma: number) => void;
36
+ export let onAnchorToBaseChange: (next: boolean) => void;
37
+ export let onCopyBaseHex: (key: string, hex: string, event?: MouseEvent) => void;
38
+
39
+ $: displayHex = mode === 'gray' ? gray500Hex : baseColor;
40
+ $: copyKey = mode === 'gray' ? 'gray-500' : '__base__';
41
+ </script>
42
+
43
+ <div class="editor-top">
44
+ <div class="editor-primary">
45
+ <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
46
+ <div
47
+ class="header-swatch"
48
+ class:active={isEditingBase}
49
+ style="background: {displayHex}"
50
+ on:click={onStartEdit}
51
+ role="button"
52
+ tabindex="0"
53
+ on:keydown={(e) => e.key === 'Enter' && onStartEdit()}
54
+ ></div>
55
+ <div class="primary-info">
56
+ <span class="editor-label">{displayLabel ?? label}</span>
57
+ <button
58
+ class="base-hex clickable-hex"
59
+ type="button"
60
+ on:click={(e) => onCopyBaseHex(copyKey, displayHex, e)}
61
+ >{copiedKey === copyKey ? 'copied!' : displayHex}</button>
62
+ </div>
63
+ </div>
64
+ </div>
65
+
66
+ {#if isEditingBase && panelOpen && editingColor}
67
+ <ColorEditPanel
68
+ color={editingColor}
69
+ title={editPanelTitle}
70
+ showRemoveOverride={false}
71
+ mode={mode === 'gray' ? 'hue-chroma' : 'hsl'}
72
+ hue={tintHue}
73
+ chroma={tintChroma}
74
+ onHueChromaChange={onTintChange}
75
+ onColorChange={onColorChange}
76
+ onConfirm={onConfirm}
77
+ onCancel={onCancel}
78
+ onRemoveOverride={() => {}}
79
+ onSliderStart={() => beginSliderGesture(`edit ${label} base`)}
80
+ >
81
+ <span slot="actions" class:hidden={mode !== 'chromatic'}>
82
+ <Toggle checked={anchorToBase} on:change={(e) => onAnchorToBaseChange(e.detail ?? !anchorToBase)} label="Lock base color to position 500" />
83
+ </span>
84
+ </ColorEditPanel>
85
+ {/if}
86
+
87
+ <style>
88
+ .editor-top {
89
+ display: flex;
90
+ align-items: flex-start;
91
+ gap: var(--ui-space-16);
92
+ flex-wrap: wrap;
93
+ }
94
+
95
+ .editor-primary {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: var(--ui-space-8);
99
+ flex-shrink: 0;
100
+ }
101
+
102
+ .primary-info {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: var(--ui-space-2);
106
+ }
107
+
108
+ .header-swatch {
109
+ width: 4rem;
110
+ height: 4rem;
111
+ border-radius: var(--ui-radius-md);
112
+ border: 2px solid var(--ui-border-default);
113
+ flex-shrink: 0;
114
+ cursor: pointer;
115
+ }
116
+
117
+ .header-swatch:hover {
118
+ border-color: var(--ui-border-strong);
119
+ }
120
+
121
+ .header-swatch.active {
122
+ border-color: var(--ui-border-strong);
123
+ outline: 2px solid var(--ui-border-medium);
124
+ outline-offset: 1px;
125
+ }
126
+
127
+ .editor-label {
128
+ font-size: var(--ui-font-size-lg);
129
+ font-weight: var(--ui-font-weight-semibold);
130
+ color: var(--ui-text-primary);
131
+ }
132
+
133
+ .base-hex {
134
+ font-size: var(--ui-font-size-xs);
135
+ color: var(--ui-text-secondary);
136
+ font-family: var(--ui-font-mono);
137
+ }
138
+
139
+ .clickable-hex {
140
+ background: none;
141
+ border: none;
142
+ cursor: pointer;
143
+ padding: var(--ui-space-2) var(--ui-space-4);
144
+ border-radius: var(--ui-radius-sm);
145
+ font-size: var(--ui-font-size-xs);
146
+ color: var(--ui-text-secondary);
147
+ font-family: var(--ui-font-mono);
148
+ }
149
+
150
+ .clickable-hex:hover {
151
+ background: var(--ui-surface-highest);
152
+ color: var(--ui-text-primary);
153
+ }
154
+
155
+ .hidden {
156
+ display: none;
157
+ }
158
+
159
+ @media (max-width: 1280px) {
160
+ .header-swatch {
161
+ width: 3rem;
162
+ height: 3rem;
163
+ }
164
+ }
165
+ </style>