@motion-proto/live-tokens 0.50.0 → 0.52.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 (57) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist-plugin/adjust/index.d.cts +1 -1
  3. package/dist-plugin/adjust/index.d.ts +1 -1
  4. package/dist-plugin/{chunk-76TFDTJO.js → chunk-JIGIIE5P.js} +60 -132
  5. package/dist-plugin/{chunk-44RSTAII.js → chunk-TKZBVIW5.js} +33 -5
  6. package/dist-plugin/chunk-YR6GPXW2.js +129 -0
  7. package/dist-plugin/generateColorsAndType/index.cjs +87 -31
  8. package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
  9. package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
  10. package/dist-plugin/generateColorsAndType/index.js +22 -12
  11. package/dist-plugin/index.cjs +95 -55
  12. package/dist-plugin/index.js +5 -3
  13. package/dist-plugin/{themeTypes-DSwKq-bj.d.cts → themeTypes-Dky03HL9.d.cts} +18 -5
  14. package/dist-plugin/{themeTypes-DSwKq-bj.d.ts → themeTypes-Dky03HL9.d.ts} +18 -5
  15. package/dist-plugin/tokensCssMigrations/index.cjs +71 -5
  16. package/dist-plugin/tokensCssMigrations/index.d.cts +20 -9
  17. package/dist-plugin/tokensCssMigrations/index.d.ts +20 -9
  18. package/dist-plugin/tokensCssMigrations/index.js +4 -1
  19. package/package.json +4 -2
  20. package/src/editor/component-editor/NotificationEditor.svelte +6 -0
  21. package/src/editor/core/palettes/oklch.ts +3 -137
  22. package/src/editor/core/palettes/paletteDerivation.ts +79 -37
  23. package/src/editor/core/themes/generateColorsAndType.ts +18 -8
  24. package/src/editor/core/themes/themeTypes.ts +18 -6
  25. package/src/editor/docs/content/editing-tokens.md +6 -2
  26. package/src/editor/docs/content.generated.ts +1 -1
  27. package/src/editor/ui/BezierCurveEditor.svelte +90 -67
  28. package/src/editor/ui/PaletteEditor.svelte +150 -44
  29. package/src/editor/ui/Toggle.svelte +14 -2
  30. package/src/editor/ui/colors/ColorStory.svelte +12 -8
  31. package/src/editor/ui/curveEngine.ts +101 -21
  32. package/src/editor/ui/palette/OverridesPanel.svelte +19 -5
  33. package/src/editor/ui/palette/PaletteBase.svelte +17 -78
  34. package/src/editor/ui/palette/ScaleCurveEditor.svelte +109 -20
  35. package/src/editor/ui/palette/curveSummary.ts +20 -0
  36. package/src/editor/ui/palette/dockMagnify.ts +5 -13
  37. package/src/editor/ui/palette/paletteMath.ts +7 -4
  38. package/src/live-tokens/data/colors-and-type/autumn.json +528 -528
  39. package/src/live-tokens/data/colors-and-type/default.json +247 -247
  40. package/src/live-tokens/data/colors-and-type/halloween.json +532 -532
  41. package/src/live-tokens/data/colors-and-type/midnight-study.json +538 -538
  42. package/src/live-tokens/data/colors-and-type/ocean.json +521 -521
  43. package/src/live-tokens/data/colors-and-type/royal-velvet.json +532 -532
  44. package/src/live-tokens/data/colors-and-type/spring-meadow.json +504 -504
  45. package/src/live-tokens/data/colors-and-type/sunset.json +531 -531
  46. package/src/live-tokens/data/themes/autumn.json +527 -527
  47. package/src/live-tokens/data/themes/halloween.json +531 -531
  48. package/src/live-tokens/data/themes/midnight-study.json +537 -537
  49. package/src/live-tokens/data/themes/ocean.json +520 -520
  50. package/src/live-tokens/data/themes/royal-velvet.json +531 -531
  51. package/src/live-tokens/data/themes/spring-meadow.json +504 -504
  52. package/src/live-tokens/data/themes/sunset.json +531 -531
  53. package/src/live-tokens/data/tokens.generated.css +282 -282
  54. package/src/system/components/FloatingTokenTags.css +0 -1
  55. package/src/system/internal/backgroundContrast.ts +7 -0
  56. package/src/system/internal/oklch.ts +189 -0
  57. package/src/system/styles/tokens.css +283 -283
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">
2
2
  import ScaleCurveEditor from './ScaleCurveEditor.svelte';
3
3
  import UIPillButton from '../UIPillButton.svelte';
4
- import { type CurveAnchor, lightnessCurveConfig, saturationCurveConfig, textLightnessCurveConfig } from '../curveEngine';
4
+ import { type CurveAnchor, lightnessCurveConfig, saturationCurveConfig, textLightnessCurveConfig, hueCurveConfig } from '../curveEngine';
5
5
  import { scaleToCssVar } from '../../core/palettes/paletteDerivation';
6
+ import { DEFAULT_PALETTE_HUE } from './paletteMath';
6
7
  import type { Oklch } from '../../core/palettes/oklch';
7
8
 
8
9
  /**
@@ -29,7 +30,7 @@
29
30
  steps: Step[];
30
31
  }
31
32
 
32
- type Channel = 'lightness' | 'saturation';
33
+ type Channel = 'lightness' | 'saturation' | 'hue';
33
34
 
34
35
  interface ScaleCurveDef {
35
36
  lightness: () => CurveAnchor[];
@@ -48,8 +49,9 @@
48
49
  snapped: boolean;
49
50
  supportsSnap: boolean;
50
51
  cssNamespace: string | null;
51
- scaleCurves: Record<string, { lightness: CurveAnchor[]; saturation: CurveAnchor[] }>;
52
+ scaleCurves: Record<string, { lightness: CurveAnchor[]; saturation: CurveAnchor[]; hue?: CurveAnchor[] }>;
52
53
  curveOffset: Record<string, number>;
54
+ curveAutoSmooth: Record<string, boolean | undefined>;
53
55
  defaultScaleCurves: Record<string, ScaleCurveDef>;
54
56
  overrides: Record<string, string>;
55
57
  editingKey: string | null;
@@ -72,6 +74,9 @@
72
74
  onCopyVarName: (key: string, varName: string, event?: MouseEvent) => void;
73
75
  onSetScaleCurve: (scaleTitle: string, channel: Channel, anchors: CurveAnchor[]) => void;
74
76
  onOffsetChange: (key: string, value: number) => void;
77
+ onAutoSmoothChange: (key: string, value: boolean) => void;
78
+ sectionOpen: (key: string) => boolean;
79
+ onToggleCurveSection: (key: string) => void;
75
80
  }
76
81
 
77
82
  let {
@@ -82,6 +87,7 @@
82
87
  cssNamespace,
83
88
  scaleCurves,
84
89
  curveOffset,
90
+ curveAutoSmooth,
85
91
  defaultScaleCurves,
86
92
  overrides,
87
93
  editingKey,
@@ -103,7 +109,10 @@
103
109
  onCopyHex,
104
110
  onCopyVarName,
105
111
  onSetScaleCurve,
106
- onOffsetChange
112
+ onOffsetChange,
113
+ onAutoSmoothChange,
114
+ sectionOpen,
115
+ onToggleCurveSection
107
116
  }: Props = $props();
108
117
 
109
118
  interface CurveDescriptor {
@@ -120,8 +129,9 @@
120
129
  const defs = defaultScaleCurves[scale.title];
121
130
  if (!sc || !defs) return [];
122
131
  return [
123
- { key: scaleCurveKeyFor(scale.title, 'lightness'), anchors: sc.lightness, cfg: lightnessCfg, defaults: defs.lightness(), channel: 'lightness' },
132
+ { key: scaleCurveKeyFor(scale.title, 'hue'), anchors: sc.hue ?? DEFAULT_PALETTE_HUE(), cfg: hueCurveConfig, defaults: DEFAULT_PALETTE_HUE(), channel: 'hue' },
124
133
  { key: scaleCurveKeyFor(scale.title, 'saturation'), anchors: sc.saturation, cfg: saturationCurveConfig, defaults: defs.saturation(), channel: 'saturation' },
134
+ { key: scaleCurveKeyFor(scale.title, 'lightness'), anchors: sc.lightness, cfg: lightnessCfg, defaults: defs.lightness(), channel: 'lightness' },
125
135
  ];
126
136
  })());
127
137
  </script>
@@ -255,8 +265,12 @@
255
265
  stepCount={scale.steps.length}
256
266
  defaults={curve.defaults}
257
267
  offset={curveOffset[curve.key] ?? 0}
268
+ autoSmooth={curveAutoSmooth[curve.key]}
269
+ open={sectionOpen(curve.key)}
270
+ onToggleOpen={() => onToggleCurveSection(curve.key)}
258
271
  onAnchorsChange={(a) => onSetScaleCurve(scale.title, curve.channel, a)}
259
272
  onOffsetChange={onOffsetChange}
273
+ onAutoSmoothChange={onAutoSmoothChange}
260
274
  />
261
275
  {/each}
262
276
  </div>
@@ -1,74 +1,38 @@
1
1
  <script lang="ts">
2
- import ColorEditPanel from '../ColorEditPanel.svelte';
3
- import Toggle from '../Toggle.svelte';
4
- import { beginSliderGesture } from '../../core/store/editorStore';
2
+ import type { Snippet } from 'svelte';
5
3
  import { oklchToHexClamped, type Oklch } from '../../core/palettes/oklch';
6
4
 
7
- // Full sRGB chroma range (gamutClamp trims per hue/lightness). Neutrals default
8
- // low but are not capped; their calm character comes from defaults, not a ceiling.
9
- const CHROMA_MAX = 0.4;
10
- // Where a typical neutral's chroma sits, marked on the slider as a soft nudge.
11
- const NEUTRAL_CALM_CHROMA = 0.05;
12
-
13
-
14
-
15
-
16
5
  interface Props {
17
6
  /**
18
- * The header swatch + label + base-hex + (when active) the OKLCH ColorEditPanel
19
- * for editing the palette's base colour. The picker edits hue/chroma/lightness
20
- * and maps them straight to the base hex. `neutral` only nudges the picker
21
- * (a calm-chroma hint on the slider); it does not change behaviour.
22
- *
23
- * State (`editing`, scope handle) is owned by the parent — this component
24
- * fires callbacks (`onStartEdit`, `onConfirm`, `onCancel`, etc.).
7
+ * The header band: swatch, label, base hex, and the palette's actions on the
8
+ * right. The base-colour picker itself is rendered by the parent below this
9
+ * band so it can line up with the curve editors.
25
10
  */
26
11
  label: string;
27
12
  displayLabel?: string | null;
28
- neutral?: boolean;
29
13
  baseColor: Oklch;
30
- anchorToBase: boolean;
31
- /** Palette step label ('500', '850', …) the base color is placed at; null while off or unplaced. */
32
- anchorStepLabel?: string | null;
33
14
  isEditingBase: boolean;
34
- panelOpen: boolean;
35
- /** Keep the panel open in live-apply mode (no confirm/cancel session) while the curve editors are visible. */
15
+ /** Keep the swatch reading as active while the curve editors are visible. */
36
16
  pinnedOpen?: boolean;
37
- editingColor: Oklch | null;
38
- editPanelTitle: string | null;
39
17
  copiedKey: string | null;
40
18
  onStartEdit: () => void;
41
- onConfirm: () => void;
42
- onCancel: () => void;
43
- onBaseChange: (hue: number, chroma: number, lightness: number) => void;
44
- onAnchorToBaseChange: (next: boolean) => void;
45
19
  onCopyBaseHex: (key: string, hex: string, event?: MouseEvent) => void;
20
+ actions?: Snippet;
46
21
  }
47
22
 
48
23
  let {
49
24
  label,
50
25
  displayLabel = null,
51
- neutral = false,
52
26
  baseColor,
53
- anchorToBase,
54
- anchorStepLabel = null,
55
27
  isEditingBase,
56
- panelOpen,
57
28
  pinnedOpen = false,
58
- editingColor,
59
- editPanelTitle,
60
29
  copiedKey,
61
30
  onStartEdit,
62
- onConfirm,
63
- onCancel,
64
- onBaseChange,
65
- onAnchorToBaseChange,
66
- onCopyBaseHex
31
+ onCopyBaseHex,
32
+ actions
67
33
  }: Props = $props();
68
34
 
69
- let baseOklch = $derived(baseColor);
70
35
  let baseHex = $derived(oklchToHexClamped(baseColor.l, baseColor.c, baseColor.h));
71
- let pickerChromaHint = $derived(neutral ? NEUTRAL_CALM_CHROMA : undefined);
72
36
  </script>
73
37
 
74
38
  <div class="editor-top">
@@ -103,42 +67,18 @@
103
67
  </div>
104
68
  </div>
105
69
 
106
- {#if pinnedOpen || (isEditingBase && panelOpen && editingColor)}
107
- <div class="editor-controls">
108
- <ColorEditPanel
109
- title={isEditingBase ? editPanelTitle : 'Base Color'}
110
- showRemoveOverride={false}
111
- hideActions={!isEditingBase}
112
- hidePreview
113
- hue={baseOklch.h}
114
- chroma={baseOklch.c}
115
- lightness={baseOklch.l * 100}
116
- chromaMax={CHROMA_MAX}
117
- chromaHint={pickerChromaHint}
118
- onHueChromaChange={onBaseChange}
119
- onConfirm={onConfirm}
120
- onCancel={onCancel}
121
- onRemoveOverride={() => {}}
122
- onSliderStart={() => beginSliderGesture(`edit ${label} base`)}
123
- >
124
- {#snippet actions()}
125
- <Toggle
126
- checked={anchorToBase}
127
- onchange={(v) => onAnchorToBaseChange(v ?? !anchorToBase)}
128
- label={anchorStepLabel ? `Base color placed at ${anchorStepLabel}` : 'Place base color in palette'}
129
- />
130
- {/snippet}
131
- </ColorEditPanel>
132
- </div>
70
+ {#if actions}
71
+ <div class="header-actions">{@render actions()}</div>
133
72
  {/if}
134
73
  </div>
135
74
 
136
75
  <style>
137
- /* Identity and controls read as one band: the sliders sit beside the swatch
138
- rather than under it, which is what leaves the palette below its height. */
76
+ /* Identity on the left, actions on the right: close and reset sit where a
77
+ panel's controls are expected, at the top-right of the band they close. */
139
78
  .editor-top {
140
79
  display: flex;
141
80
  align-items: flex-start;
81
+ justify-content: space-between;
142
82
  gap: var(--ui-space-16);
143
83
  flex-wrap: wrap;
144
84
  }
@@ -157,11 +97,10 @@
157
97
  gap: var(--ui-space-2);
158
98
  }
159
99
 
160
- /* Basis, not width: the panel takes the rest of the row and drops below only
161
- when it can't hold that much. */
162
- .editor-controls {
163
- flex: 1 1 26rem;
164
- min-width: 0;
100
+ .header-actions {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: var(--ui-space-8);
165
104
  }
166
105
 
167
106
  .header-swatch {
@@ -1,31 +1,39 @@
1
1
  <script lang="ts">
2
2
  import BezierCurveEditor from '../BezierCurveEditor.svelte';
3
- import type { CurveAnchor, CurveConfig } from '../curveEngine';
3
+ import { isAutoSmoothCurve, type CurveAnchor, type CurveConfig } from '../curveEngine';
4
+ import { curveSummary } from './curveSummary';
4
5
 
5
-
6
-
7
- interface Props {
8
- /**
6
+ /**
9
7
  * Single-channel curve editor — one Bezier curve for one channel
10
- * (lightness or saturation) of one scale (palette / gray-palette /
8
+ * (lightness, saturation, or hue) of one scale (palette / gray-palette /
11
9
  * Surfaces / Borders / Text). Instantiated 6+ times across PaletteEditor:
12
10
  * the parent's chromatic-palette + gray-palette curves and the four
13
11
  * derived-scale curve groups inside OverridesPanel.
14
12
  *
15
- * Pure presentational wrapper: it forwards props to BezierCurveEditor
16
- * and adapts the offset key into a single `onOffsetChange(value)` so the
13
+ * Pure presentational wrapper: it forwards props to BezierCurveEditor and
14
+ * adapts the offset key into a single `onOffsetChange(value)` so the
17
15
  * parent only needs to know how to wire `(key, value)` once (via a
18
- * closure passed in).
16
+ * closure passed in). It also owns the collapsible disclosure chrome
17
+ * around that editor; the open/closed flag itself is parent state
18
+ * (session-scoped, never written to theme JSON).
19
19
  */
20
+ interface Props {
20
21
  curveKey: string;
21
22
  anchors: CurveAnchor[];
22
23
  cfg: CurveConfig;
23
24
  stepCount: number;
24
25
  defaults: CurveAnchor[];
25
26
  offset?: number;
27
+ /** Undefined for a curve saved before the flag existed, or one never toggled;
28
+ * its shape answers for it. Only an explicit switch-off needs recording, since
29
+ * switching on is self-evident from the shape it produces. */
30
+ autoSmooth?: boolean;
26
31
  lockedAnchorIndex?: number | null;
32
+ open: boolean;
33
+ onToggleOpen: () => void;
27
34
  onAnchorsChange: (anchors: CurveAnchor[]) => void;
28
35
  onOffsetChange: (key: string, value: number) => void;
36
+ onAutoSmoothChange: (key: string, value: boolean) => void;
29
37
  onLockedAnchorUnlock?: (() => void) | null;
30
38
  }
31
39
 
@@ -36,21 +44,102 @@
36
44
  stepCount,
37
45
  defaults,
38
46
  offset = 0,
47
+ autoSmooth = undefined,
39
48
  lockedAnchorIndex = null,
49
+ open,
50
+ onToggleOpen,
40
51
  onAnchorsChange,
41
52
  onOffsetChange,
53
+ onAutoSmoothChange,
42
54
  onLockedAnchorUnlock = null
43
55
  }: Props = $props();
56
+
57
+ let summary = $derived(curveSummary(anchors, defaults, offset, cfg.unit ?? ''));
58
+ let autoSmoothResolved = $derived(autoSmooth ?? isAutoSmoothCurve(anchors));
44
59
  </script>
45
60
 
46
- <BezierCurveEditor
47
- {anchors}
48
- {cfg}
49
- {stepCount}
50
- defaultAnchors={defaults}
51
- {offset}
52
- {lockedAnchorIndex}
53
- {onLockedAnchorUnlock}
54
- {onAnchorsChange}
55
- onOffsetChange={(v) => onOffsetChange(curveKey, v)}
56
- />
61
+ <div class="curve-section">
62
+ <button
63
+ class="curve-section-toggle"
64
+ type="button"
65
+ aria-expanded={open}
66
+ onclick={onToggleOpen}
67
+ >
68
+ <i class="fas" class:fa-chevron-right={!open} class:fa-chevron-down={open} aria-hidden="true"></i>
69
+ <span class="curve-section-label">{cfg.label}</span>
70
+ <span class="curve-section-summary">
71
+ {#if summary !== 'default'}<span class="curve-section-dot" aria-hidden="true"></span>{/if}
72
+ {summary}
73
+ </span>
74
+ </button>
75
+ {#if open}
76
+ <BezierCurveEditor
77
+ {anchors}
78
+ {cfg}
79
+ {stepCount}
80
+ defaultAnchors={defaults}
81
+ {offset}
82
+ autoSmooth={autoSmoothResolved}
83
+ {lockedAnchorIndex}
84
+ {onLockedAnchorUnlock}
85
+ {onAnchorsChange}
86
+ onOffsetChange={(v) => onOffsetChange(curveKey, v)}
87
+ onAutoSmoothChange={(v) => onAutoSmoothChange(curveKey, v)}
88
+ />
89
+ {/if}
90
+ </div>
91
+
92
+ <style>
93
+ .curve-section {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: var(--ui-space-4);
97
+ }
98
+
99
+ .curve-section-toggle {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: var(--ui-space-8);
103
+ width: 100%;
104
+ padding: var(--ui-space-4) var(--ui-space-2);
105
+ background: none;
106
+ border: none;
107
+ color: var(--ui-text-tertiary);
108
+ cursor: pointer;
109
+ text-align: left;
110
+ transition: color var(--ui-transition-fast);
111
+ }
112
+
113
+ .curve-section-toggle:hover {
114
+ color: var(--ui-text-primary);
115
+ }
116
+
117
+ .curve-section-toggle i {
118
+ font-size: var(--ui-font-size-xs);
119
+ width: 0.75rem;
120
+ text-align: center;
121
+ }
122
+
123
+ .curve-section-label {
124
+ font-size: var(--ui-font-size-md);
125
+ font-weight: var(--ui-font-weight-semibold);
126
+ }
127
+
128
+ .curve-section-summary {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: var(--ui-space-4);
132
+ margin-left: auto;
133
+ font-size: var(--ui-font-size-sm);
134
+ font-weight: var(--ui-font-weight-normal);
135
+ color: var(--ui-text-muted);
136
+ }
137
+
138
+ .curve-section-dot {
139
+ width: 0.375rem;
140
+ height: 0.375rem;
141
+ border-radius: var(--ui-radius-full);
142
+ background: var(--ui-text-primary);
143
+ flex-shrink: 0;
144
+ }
145
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { CurveAnchor } from '../curveEngine';
2
+
3
+ const ANCHOR_KEYS = ['x', 'y', 'inDx', 'inDy', 'outDx', 'outDy'] as const;
4
+
5
+ function anchorsEqual(a: CurveAnchor[], b: CurveAnchor[]): boolean {
6
+ return a.length === b.length && a.every((anchor, i) => ANCHOR_KEYS.every((k) => anchor[k] === b[i][k]));
7
+ }
8
+
9
+ /**
10
+ * Collapsed-row readout for a curve section. Range rather than first-to-last:
11
+ * a peak's endpoints can match the defaults' while the middle diverges, and
12
+ * first-to-last would misstate the peak's range as flat.
13
+ */
14
+ export function curveSummary(anchors: CurveAnchor[], defaults: CurveAnchor[], offset: number, unit = ''): string {
15
+ if (offset === 0 && anchorsEqual(anchors, defaults)) return 'default';
16
+ const ys = anchors.map((a) => a.y);
17
+ const range = `${Math.round(Math.min(...ys))} to ${Math.round(Math.max(...ys))}${unit}`;
18
+ const offsetPart = offset !== 0 ? ` offset ${offset > 0 ? '+' : ''}${offset}${unit}` : '';
19
+ return `${range}${offsetPart}`;
20
+ }
@@ -1,22 +1,14 @@
1
- /**
2
- * macOS-dock magnification policy for palette ramps, shared by the palette
3
- * editor's swatch grid and the Colors view's step strips so the falloff tunes
4
- * in one place. Layout stays native to each surface (grid tracks vs
5
- * flex-grow); only the size policy is shared.
6
- */
1
+ /** macOS-dock magnification for the Colors view's step strips. The palette
2
+ * editor's own grid stays even, because its curve editors plot one step per
3
+ * column. */
7
4
 
8
5
  const ANCHORED_GROW = 1.7;
9
6
  const NEIGHBOUR_GROW = 1.2;
10
7
 
11
- /** Size share for a ramp cell: anchored opens widest, immediate neighbours
12
- * get a smaller boost, everything else stays 1. Doubles as a flex-grow. */
8
+ /** Flex-grow for a ramp cell: anchored opens widest, immediate neighbours get a
9
+ * smaller boost, everything else stays 1. */
13
10
  export function dockGrow(index: number, anchored: number | null): number {
14
11
  if (anchored === null) return 1;
15
12
  const d = Math.abs(index - anchored);
16
13
  return d === 0 ? ANCHORED_GROW : d === 1 ? NEIGHBOUR_GROW : 1;
17
14
  }
18
-
19
- /** Grid track list (`minmax(0, Nfr)` per column) for a `cols`-column ramp. */
20
- export function dockTrackTemplate(cols: number, anchored: number | null): string {
21
- return Array.from({ length: cols }, (_, i) => `minmax(0, ${dockGrow(i, anchored)}fr)`).join(' ');
22
- }
@@ -1,5 +1,5 @@
1
1
  import type { Oklch } from '../../core/palettes/oklch';
2
- import { type CurveAnchor, makeAnchor } from '../curveEngine';
2
+ import { type CurveAnchor, makeAnchor, resmoothAutoCurve } from '../curveEngine';
3
3
  import type { PaletteConfig } from '../../core/themes/themeTypes';
4
4
  import {
5
5
  type Step,
@@ -9,6 +9,7 @@ import {
9
9
  PALETTE_STEPS,
10
10
  DEFAULT_PALETTE_LIGHTNESS,
11
11
  DEFAULT_PALETTE_SATURATION,
12
+ DEFAULT_PALETTE_HUE,
12
13
  defaultScaleCurves,
13
14
  scaleCurveDefaults,
14
15
  computePaletteOklch,
@@ -33,6 +34,7 @@ export {
33
34
  PALETTE_STEPS,
34
35
  DEFAULT_PALETTE_LIGHTNESS,
35
36
  DEFAULT_PALETTE_SATURATION,
37
+ DEFAULT_PALETTE_HUE,
36
38
  defaultScaleCurves,
37
39
  scaleCurveDefaults,
38
40
  computePaletteOklch,
@@ -54,11 +56,12 @@ export interface PaletteStepDef {
54
56
  }
55
57
 
56
58
  export type CurveOffset = Record<string, number>;
57
- export type ScaleCurves = Record<string, { lightness: CurveAnchor[]; saturation: CurveAnchor[] }>;
59
+ export type ScaleCurves = Record<string, { lightness: CurveAnchor[]; saturation: CurveAnchor[]; hue?: CurveAnchor[] }>;
58
60
 
59
61
  // Neutrals seed a calmer, wider lightness ramp than accents; the saturation
60
62
  // curve is the same flat 100 (chroma comes from a low-chroma base, not a cap).
61
- export const DEFAULT_NEUTRAL_LIGHTNESS = (): CurveAnchor[] => [makeAnchor(0, 92, 5), makeAnchor(100, 3, 5)];
63
+ export const DEFAULT_NEUTRAL_LIGHTNESS = (): CurveAnchor[] =>
64
+ resmoothAutoCurve([makeAnchor(0, 92), makeAnchor(100, 3)]);
62
65
 
63
66
  /**
64
67
  * Seed config for a palette. The derivation path is unified; the only
@@ -103,7 +106,7 @@ export const paletteStepLightness: PaletteStepDef[] = [
103
106
  { label: '950', lightness: 8 },
104
107
  ];
105
108
 
106
- export const scaleCurveKey = (scaleTitle: string, channel: 'lightness' | 'saturation') => `${scaleTitle}-${channel}`;
109
+ export const scaleCurveKey = (scaleTitle: string, channel: 'lightness' | 'saturation' | 'hue') => `${scaleTitle}-${channel}`;
107
110
 
108
111
  interface PaletteComputed {
109
112
  oklch: Oklch;