@motion-proto/live-tokens 0.49.1 → 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 (111) hide show
  1. package/CHANGELOG.md +72 -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 +100 -33
  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 +35 -14
  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 +8 -1
  20. package/src/editor/bootstrap.ts +2 -0
  21. package/src/editor/component-editor/ButtonEditor.svelte +1 -1
  22. package/src/editor/component-editor/CardEditor.svelte +5 -2
  23. package/src/editor/component-editor/IconButtonEditor.svelte +1 -1
  24. package/src/editor/component-editor/ImageEditor.svelte +1 -1
  25. package/src/editor/component-editor/NotificationEditor.svelte +6 -0
  26. package/src/editor/component-editor/SectionDividerEditor.svelte +4 -1
  27. package/src/editor/component-editor/SideNavigationEditor.svelte +6 -0
  28. package/src/editor/component-editor/TabBarEditor.svelte +1 -5
  29. package/src/editor/component-editor/scaffolding/ComponentEditorBase.svelte +8 -1
  30. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +10 -32
  31. package/src/editor/component-editor/scaffolding/StateBlock.svelte +2 -0
  32. package/src/editor/component-editor/scaffolding/TypeEditor.svelte +3 -1
  33. package/src/editor/component-editor/scaffolding/types.ts +3 -0
  34. package/src/editor/core/components/componentConfigService.ts +25 -0
  35. package/src/editor/core/cssVarSync.ts +65 -7
  36. package/src/editor/core/fonts/fontLoader.ts +12 -3
  37. package/src/editor/core/fonts/fontWeightAvailability.ts +97 -0
  38. package/src/editor/core/palettes/oklch.ts +3 -137
  39. package/src/editor/core/palettes/paletteDerivation.ts +79 -37
  40. package/src/editor/core/preview/lookPreview.ts +23 -18
  41. package/src/editor/core/store/editorRenderer.ts +22 -7
  42. package/src/editor/core/store/editorStore.ts +31 -0
  43. package/src/editor/core/store/gradientSource.ts +5 -0
  44. package/src/editor/core/themes/colorsAndTypeService.ts +2 -12
  45. package/src/editor/core/themes/generateColorsAndType.ts +18 -8
  46. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +4 -1
  47. package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +4 -1
  48. package/src/editor/core/themes/migrations/2026-05-25-cornerbadge-flatten-variants.ts +8 -0
  49. package/src/editor/core/themes/slices/fonts.ts +4 -5
  50. package/src/editor/core/themes/themeDocumentSync.ts +66 -0
  51. package/src/editor/core/themes/themeInit.ts +7 -10
  52. package/src/editor/core/themes/themeService.ts +10 -3
  53. package/src/editor/core/themes/themeTypes.ts +18 -6
  54. package/src/editor/docs/content/editing-tokens.md +6 -2
  55. package/src/editor/docs/content/themes-workflow.md +14 -11
  56. package/src/editor/docs/content/where-themes-live.md +2 -2
  57. package/src/editor/docs/content.generated.ts +3 -3
  58. package/src/editor/ui/BezierCurveEditor.svelte +90 -67
  59. package/src/editor/ui/FileLoadList.svelte +1 -0
  60. package/src/editor/ui/FontStackEditor.svelte +1 -2
  61. package/src/editor/ui/GradientEditor.svelte +5 -1
  62. package/src/editor/ui/PaletteEditor.svelte +150 -44
  63. package/src/editor/ui/ProjectFontsSection.svelte +0 -5
  64. package/src/editor/ui/ThemePanel.svelte +206 -42
  65. package/src/editor/ui/Toggle.svelte +14 -2
  66. package/src/editor/ui/UIDialog.svelte +7 -1
  67. package/src/editor/ui/UIFontFamilySelector.svelte +13 -13
  68. package/src/editor/ui/UIFontSizeSelector.svelte +5 -5
  69. package/src/editor/ui/UIFontWeightSelector.svelte +52 -2
  70. package/src/editor/ui/UIOptionItem.svelte +14 -1
  71. package/src/editor/ui/UIPaddingSelector.svelte +4 -3
  72. package/src/editor/ui/UIPaletteSelector.svelte +20 -13
  73. package/src/editor/ui/UIRelinkConfirmDialog.svelte +18 -3
  74. package/src/editor/ui/UITextTransformSelector.svelte +5 -5
  75. package/src/editor/ui/UITokenSelector.svelte +29 -7
  76. package/src/editor/ui/colors/ColorStory.svelte +12 -8
  77. package/src/editor/ui/curveEngine.ts +101 -21
  78. package/src/editor/ui/palette/OverridesPanel.svelte +19 -5
  79. package/src/editor/ui/palette/PaletteBase.svelte +17 -78
  80. package/src/editor/ui/palette/ScaleCurveEditor.svelte +109 -20
  81. package/src/editor/ui/palette/curveSummary.ts +20 -0
  82. package/src/editor/ui/palette/dockMagnify.ts +5 -13
  83. package/src/editor/ui/palette/paletteMath.ts +7 -4
  84. package/src/live-tokens/data/colors-and-type/autumn.json +528 -528
  85. package/src/live-tokens/data/colors-and-type/default.json +247 -247
  86. package/src/live-tokens/data/colors-and-type/halloween.json +532 -532
  87. package/src/live-tokens/data/colors-and-type/midnight-study.json +538 -538
  88. package/src/live-tokens/data/colors-and-type/ocean.json +521 -521
  89. package/src/live-tokens/data/colors-and-type/royal-velvet.json +532 -532
  90. package/src/live-tokens/data/colors-and-type/spring-meadow.json +504 -504
  91. package/src/live-tokens/data/colors-and-type/sunset.json +531 -531
  92. package/src/live-tokens/data/themes/autumn.json +527 -527
  93. package/src/live-tokens/data/themes/halloween.json +531 -531
  94. package/src/live-tokens/data/themes/midnight-study.json +537 -537
  95. package/src/live-tokens/data/themes/ocean.json +520 -520
  96. package/src/live-tokens/data/themes/royal-velvet.json +531 -531
  97. package/src/live-tokens/data/themes/spring-meadow.json +516 -514
  98. package/src/live-tokens/data/themes/sunset.json +531 -531
  99. package/src/live-tokens/data/tokens.generated.css +282 -282
  100. package/src/system/components/Button.svelte +2 -1
  101. package/src/system/components/FloatingTokenTags.css +2 -2
  102. package/src/system/components/FloatingTokenTags.svelte +11 -0
  103. package/src/system/components/IconButton.svelte +2 -1
  104. package/src/system/components/Image.svelte +9 -3
  105. package/src/system/components/ImageLightbox.svelte +2 -0
  106. package/src/system/components/SectionDivider.svelte +20 -2
  107. package/src/system/components/SideNavigation.svelte +15 -1
  108. package/src/system/internal/backgroundContrast.ts +7 -0
  109. package/src/system/internal/oklch.ts +189 -0
  110. package/src/system/styles/fonts.css +6 -6
  111. package/src/system/styles/tokens.css +283 -283
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { onMount, onDestroy, tick } from 'svelte';
3
3
  import { oklchToHexClamped, type Oklch } from '../core/palettes/oklch';
4
- import { type CurveAnchor, lightnessCurveConfig, saturationCurveConfig } from './curveEngine';
4
+ import { type CurveAnchor, lightnessCurveConfig, saturationCurveConfig, hueCurveConfig } from './curveEngine';
5
5
  import ColorEditPanel from './ColorEditPanel.svelte';
6
6
  import OverridesPanel from './palette/OverridesPanel.svelte';
7
7
  import UIPillButton from './UIPillButton.svelte';
@@ -9,12 +9,12 @@
9
9
  import ScaleCurveEditor from './palette/ScaleCurveEditor.svelte';
10
10
  import PaletteBase from './palette/PaletteBase.svelte';
11
11
  import PaletteJumpButton from './palette/PaletteJumpButton.svelte';
12
+ import Toggle from './Toggle.svelte';
12
13
  import { type EditingState, idleState, BASE_KEY, isEditingBase as isBaseEdit } from './palette/paletteEditorState';
13
- import { dockTrackTemplate } from './palette/dockMagnify';
14
14
  import {
15
15
  type Step, type Scale,
16
16
  GRAY_FALLBACK,
17
- DEFAULT_PALETTE_LIGHTNESS, DEFAULT_PALETTE_SATURATION,
17
+ DEFAULT_PALETTE_LIGHTNESS, DEFAULT_PALETTE_SATURATION, DEFAULT_PALETTE_HUE,
18
18
  defaultScaleCurves, defaultScaleCurvesObject, defaultPaletteConfig,
19
19
  paletteStepLightness, scales,
20
20
  paletteStepKey, stepKey, scaleCurveKey as getScaleCurveKey,
@@ -32,6 +32,12 @@
32
32
  import { pendingPaletteFocus } from '../core/store/paletteFocus';
33
33
  import { showCopyPopover } from './copyPopover';
34
34
 
35
+ // Full sRGB chroma range (gamutClamp trims per hue/lightness). Neutrals default
36
+ // low but are not capped; their calm character comes from defaults, not a ceiling.
37
+ const BASE_CHROMA_MAX = 0.4;
38
+ // Where a typical neutral's chroma sits, marked on the slider as a soft nudge.
39
+ const NEUTRAL_CALM_CHROMA = 0.05;
40
+
35
41
  interface Props {
36
42
  label: string;
37
43
  displayLabel?: string | null;
@@ -80,6 +86,14 @@
80
86
  const idx = saturationCurve.findIndex(a => Math.abs(a.x - x) < 0.5);
81
87
  return idx >= 0 ? idx : null;
82
88
  });
89
+ // Unlike lightness/saturation, hue has no always-present curve to search: it stays
90
+ // undefined until the first edit (RJC 5), and there is nothing to lock before then.
91
+ let lockedHueIdx: number | null = $derived.by(() => {
92
+ if (!anchorToBase || anchorPlacement === undefined || hueCurve === undefined) return null;
93
+ const x = stepIndexToX(anchorPlacement.step);
94
+ const idx = hueCurve.findIndex(a => Math.abs(a.x - x) < 0.5);
95
+ return idx >= 0 ? idx : null;
96
+ });
83
97
 
84
98
  // Held at component scope so inline header-swatch handlers and the function
85
99
  // handlers share one handle for commit/cancel.
@@ -114,14 +128,36 @@
114
128
  function setLightnessCurve(a: CurveAnchor[]) { edit('lightnessCurve', a); }
115
129
  function setSaturationCurve(a: CurveAnchor[]) { edit('saturationCurve', a); }
116
130
 
131
+ // First edit materializes the field (RJC 5) with `a` — the curve the UI
132
+ // actually rendered and edited — written in BEFORE syncBaseAnchor runs, not
133
+ // after: syncBaseAnchor pins the anchor by inserting a y=0 point into
134
+ // whatever `cfg.hueCurve` currently holds, so calling it against the
135
+ // about-to-be-discarded flat default and then overwriting with `a` would
136
+ // throw the pin away. Wave 3's idempotency guarantee is what makes
137
+ // re-running syncBaseAnchor here safe without disturbing the
138
+ // lightness/saturation anchors it already placed.
139
+ function setHueCurve(a: CurveAnchor[]) {
140
+ mutate(`${label}: hueCurve`, (s) => {
141
+ if (!s.palettes[label]) s.palettes[label] = seedConfig();
142
+ const cfg = s.palettes[label];
143
+ const materializing = cfg.hueCurve === undefined;
144
+ cfg.hueCurve = a;
145
+ if (materializing) syncBaseAnchor(cfg);
146
+ });
147
+ }
148
+
117
149
  function handleOffset(key: string, value: number) {
118
150
  edit('curveOffset', { ...curveOffset, [key]: value });
119
151
  }
120
152
 
153
+ function handleAutoSmooth(key: string, value: boolean) {
154
+ edit('curveAutoSmooth', { ...curveAutoSmooth, [key]: value });
155
+ }
156
+
121
157
  let editing: EditingState = $state(idleState);
122
158
 
123
159
  function computePaletteOklch(index: number, base: Oklch): Oklch {
124
- return computePaletteOklchPure(index, base, lightnessCurve, saturationCurve, curveOffset);
160
+ return computePaletteOklchPure(index, base, lightnessCurve, saturationCurve, curveOffset, hueCurve);
125
161
  }
126
162
 
127
163
  function computeDerivedOklch(step: Step, base: Oklch, scaleTitle: string): Oklch {
@@ -176,7 +212,17 @@
176
212
  scaleEditorOpen = scaleEditorOpen;
177
213
  }
178
214
 
179
- function setScaleCurve(title: string, channel: 'lightness' | 'saturation', a: CurveAnchor[]) {
215
+ // Session-scoped (RJC 9): viewport state, never written to theme JSON.
216
+ // Keyed by the same curveKey strings that already key curveOffset, so a
217
+ // hue key ("hue" or "<scale>-hue") defaults closed and the rest open.
218
+ let curveSectionOpen: Record<string, boolean> = $state({});
219
+ const sectionOpen = (key: string) => curveSectionOpen[key] ?? !key.endsWith('hue');
220
+ function toggleCurveSection(key: string) {
221
+ curveSectionOpen[key] = !sectionOpen(key);
222
+ curveSectionOpen = curveSectionOpen;
223
+ }
224
+
225
+ function setScaleCurve(title: string, channel: 'lightness' | 'saturation' | 'hue', a: CurveAnchor[]) {
180
226
  const cur = scaleCurves[title] ?? { lightness: defaultScaleCurves[title].lightness(), saturation: defaultScaleCurves[title].saturation() };
181
227
  edit('scaleCurves', { ...scaleCurves, [title]: { ...cur, [channel]: a } });
182
228
  }
@@ -389,22 +435,16 @@
389
435
  let baseColor = $derived($editorState.palettes[label]?.baseColor ?? initialColor);
390
436
  let lightnessCurve = $derived($editorState.palettes[label]?.lightnessCurve ?? DEFAULT_PALETTE_LIGHTNESS());
391
437
  let saturationCurve = $derived($editorState.palettes[label]?.saturationCurve ?? DEFAULT_PALETTE_SATURATION());
438
+ // Left undefined when absent (RJC 5): materializing here would seed every
439
+ // fresh palette with a redundant flat curve it never asked for.
440
+ let hueCurve = $derived($editorState.palettes[label]?.hueCurve);
392
441
  let scaleCurves = $derived($editorState.palettes[label]?.scaleCurves ?? defaultScaleCurvesObject());
393
442
  let curveOffset = $derived($editorState.palettes[label]?.curveOffset ?? { lightness: 0, saturation: 0 });
443
+ let curveAutoSmooth = $derived($editorState.palettes[label]?.curveAutoSmooth ?? {});
394
444
  let overrides = $derived($editorState.palettes[label]?.overrides ?? {});
395
445
  let snappedScales = $derived(new Set($editorState.palettes[label]?.snappedScales ?? []));
396
446
  let anchorToBase = $derived($editorState.palettes[label]?.anchorToBase ?? true);
397
447
  let anchorPlacement = $derived($editorState.palettes[label]?.anchorPlacement);
398
- let anchorStepLabel = $derived(anchorToBase && anchorPlacement !== undefined
399
- ? paletteStepLightness[anchorPlacement.step]?.label ?? null
400
- : null);
401
- // Dock magnification: the grid transitions between track lists, so dialing
402
- // lightness reads as swatches opening/closing while the anchor walks the
403
- // ramp. Column 0 is the white bookend, so step i is column i+1.
404
- let swatchTemplate = $derived(dockTrackTemplate(
405
- paletteStepLightness.length + 2,
406
- anchorToBase && anchorPlacement !== undefined ? anchorPlacement.step + 1 : null,
407
- ));
408
448
  let emptyMode = $derived($editorState.palettes[label]?.emptyMode ?? 'solid');
409
449
  let gradientStyle = $derived($editorState.palettes[label]?.gradientStyle ?? 'linear');
410
450
  let gradientAngle = $derived($editorState.palettes[label]?.gradientAngle ?? 180);
@@ -417,7 +457,7 @@
417
457
  let editingKey = $derived(editing.kind === 'idle' ? null : editing.kind === 'editingBase' ? BASE_KEY : editing.stepKey);
418
458
  let editingDraft = $derived(editing.kind === 'editingStep' ? editing.draft : null);
419
459
  let paletteComputed = $derived((() => {
420
- const _bc = baseColor, _lc = lightnessCurve, _sc = saturationCurve, _co = curveOffset, _ed = editingDraft, _ek = editingKey, _ov = overrides, _ab = anchorToBase, _ap = anchorPlacement;
460
+ const _bc = baseColor, _lc = lightnessCurve, _sc = saturationCurve, _hc = hueCurve, _co = curveOffset, _ed = editingDraft, _ek = editingKey, _ov = overrides, _ab = anchorToBase, _ap = anchorPlacement;
421
461
  return paletteStepLightness.map((ps, index) => {
422
462
  const k = paletteStepKey(ps.label);
423
463
  const oklch = computePaletteOklch(index, baseColor);
@@ -488,23 +528,55 @@
488
528
  <PaletteBase
489
529
  {label}
490
530
  {displayLabel}
491
- {neutral}
492
531
  {baseColor}
493
- {anchorToBase}
494
- {anchorStepLabel}
495
532
  {isEditingBase}
496
- {panelOpen}
497
533
  pinnedOpen={paletteEditorOpen}
498
- {editingColor}
499
- {editPanelTitle}
500
534
  {copiedKey}
501
535
  onStartEdit={startBaseEdit}
502
- onConfirm={confirmEdit}
503
- onCancel={cancelEdit}
504
- onBaseChange={(h, c, l) => setBaseColor(label, { l: l / 100, c, h })}
505
- onAnchorToBaseChange={setAnchorToBase}
506
536
  onCopyBaseHex={copyHex}
507
- />
537
+ >
538
+ {#snippet actions()}
539
+ <PaletteJumpButton family={label} {displayLabel} target="wheel" />
540
+ <UIPillButton size="compact" variant="outline" onclick={clearPaletteOverrides}>Clear Overrides</UIPillButton>
541
+ <UIPillButton size="compact" variant="outline" onclick={() => paletteEditorOpen = !paletteEditorOpen}>
542
+ {paletteEditorOpen ? 'Close' : 'Edit'}
543
+ </UIPillButton>
544
+ {/snippet}
545
+ </PaletteBase>
546
+
547
+ {#if paletteEditorOpen || (isEditingBase && panelOpen && editingColor)}
548
+ <div class="swatch-grid" style="--swatch-cols: {paletteStepLightness.length + 2}">
549
+ <div class="base-panel" style="grid-column: 2 / {paletteStepLightness.length + 2}">
550
+ <ColorEditPanel
551
+ title={isEditingBase ? editPanelTitle : 'Base Color'}
552
+ showRemoveOverride={false}
553
+ hideActions={!isEditingBase}
554
+ hidePreview
555
+ hue={baseColor.h}
556
+ chroma={baseColor.c}
557
+ lightness={baseColor.l * 100}
558
+ chromaMax={BASE_CHROMA_MAX}
559
+ chromaHint={neutral ? NEUTRAL_CALM_CHROMA : undefined}
560
+ onHueChromaChange={(h, c, l) => setBaseColor(label, { l: l / 100, c, h })}
561
+ onConfirm={confirmEdit}
562
+ onCancel={cancelEdit}
563
+ onRemoveOverride={() => {}}
564
+ onSliderStart={() => beginSliderGesture(`edit ${label} base`)}
565
+ >
566
+ {#snippet actions()}
567
+ <div class="base-anchor-toggle">
568
+ <Toggle
569
+ checked={anchorToBase}
570
+ onchange={(v) => setAnchorToBase(v ?? !anchorToBase)}
571
+ label="Base color must appear in palette"
572
+ labelFirst
573
+ />
574
+ </div>
575
+ {/snippet}
576
+ </ColorEditPanel>
577
+ </div>
578
+ </div>
579
+ {/if}
508
580
 
509
581
  <!-- Palette + Text row -->
510
582
  <div class="scales-row">
@@ -521,13 +593,8 @@
521
593
  <span>Gradient</span>
522
594
  </label>
523
595
  {/if}
524
- <PaletteJumpButton family={label} {displayLabel} target="wheel" />
525
- <UIPillButton size="compact" variant="outline" onclick={clearPaletteOverrides}>Clear Overrides</UIPillButton>
526
- <UIPillButton size="compact" variant="outline" onclick={() => paletteEditorOpen = !paletteEditorOpen}>
527
- {paletteEditorOpen ? 'Close' : 'Edit'}
528
- </UIPillButton>
529
596
  </div>
530
- <div class="swatch-grid" style="--swatch-cols: {paletteStepLightness.length + 2}; grid-template-columns: {swatchTemplate}">
597
+ <div class="swatch-grid" style="--swatch-cols: {paletteStepLightness.length + 2}">
531
598
  <div class="step-column">
532
599
  <button class="step-label copyable-label" class:copied={copiedLabelKey === 'palette-white'} type="button" onclick={(e) => copyVarName('palette-white', `--color-${cssNamespace}-white`, e)}>
533
600
  {copiedLabelKey === 'palette-white' ? 'copied!' : 'white'}
@@ -585,16 +652,20 @@
585
652
  </div>
586
653
  {/if}
587
654
  <ScaleCurveEditor
588
- curveKey="lightness"
589
- anchors={lightnessCurve}
590
- cfg={lightnessCurveConfig}
655
+ curveKey="hue"
656
+ anchors={hueCurve ?? DEFAULT_PALETTE_HUE()}
657
+ cfg={hueCurveConfig}
591
658
  stepCount={paletteStepLightness.length}
592
- defaults={DEFAULT_PALETTE_LIGHTNESS()}
593
- offset={curveOffset['lightness'] ?? 0}
594
- lockedAnchorIndex={lockedLightnessIdx}
659
+ defaults={DEFAULT_PALETTE_HUE()}
660
+ offset={curveOffset['hue'] ?? 0}
661
+ autoSmooth={curveAutoSmooth['hue']}
662
+ lockedAnchorIndex={lockedHueIdx}
663
+ open={sectionOpen('hue')}
664
+ onToggleOpen={() => toggleCurveSection('hue')}
595
665
  onLockedAnchorUnlock={() => anchorUnlockPrompt = true}
596
- onAnchorsChange={setLightnessCurve}
666
+ onAnchorsChange={setHueCurve}
597
667
  onOffsetChange={handleOffset}
668
+ onAutoSmoothChange={handleAutoSmooth}
598
669
  />
599
670
  <ScaleCurveEditor
600
671
  curveKey="saturation"
@@ -603,10 +674,30 @@
603
674
  stepCount={paletteStepLightness.length}
604
675
  defaults={DEFAULT_PALETTE_SATURATION()}
605
676
  offset={curveOffset['saturation'] ?? 0}
677
+ autoSmooth={curveAutoSmooth['saturation']}
606
678
  lockedAnchorIndex={lockedSaturationIdx}
679
+ open={sectionOpen('saturation')}
680
+ onToggleOpen={() => toggleCurveSection('saturation')}
607
681
  onLockedAnchorUnlock={() => anchorUnlockPrompt = true}
608
682
  onAnchorsChange={setSaturationCurve}
609
683
  onOffsetChange={handleOffset}
684
+ onAutoSmoothChange={handleAutoSmooth}
685
+ />
686
+ <ScaleCurveEditor
687
+ curveKey="lightness"
688
+ anchors={lightnessCurve}
689
+ cfg={lightnessCurveConfig}
690
+ stepCount={paletteStepLightness.length}
691
+ defaults={DEFAULT_PALETTE_LIGHTNESS()}
692
+ offset={curveOffset['lightness'] ?? 0}
693
+ autoSmooth={curveAutoSmooth['lightness']}
694
+ lockedAnchorIndex={lockedLightnessIdx}
695
+ open={sectionOpen('lightness')}
696
+ onToggleOpen={() => toggleCurveSection('lightness')}
697
+ onLockedAnchorUnlock={() => anchorUnlockPrompt = true}
698
+ onAnchorsChange={setLightnessCurve}
699
+ onOffsetChange={handleOffset}
700
+ onAutoSmoothChange={handleAutoSmooth}
610
701
  />
611
702
  </div>
612
703
  {/if}
@@ -641,6 +732,7 @@
641
732
  {cssNamespace}
642
733
  {scaleCurves}
643
734
  {curveOffset}
735
+ {curveAutoSmooth}
644
736
  {defaultScaleCurves}
645
737
  overrides={overridesHex}
646
738
  {editingKey}
@@ -663,6 +755,9 @@
663
755
  onCopyVarName={copyVarName}
664
756
  onSetScaleCurve={setScaleCurve}
665
757
  onOffsetChange={handleOffset}
758
+ onAutoSmoothChange={handleAutoSmooth}
759
+ {sectionOpen}
760
+ onToggleCurveSection={toggleCurveSection}
666
761
  />
667
762
  {/snippet}
668
763
 
@@ -862,8 +957,9 @@
862
957
 
863
958
  .swatch-grid {
864
959
  display: grid;
960
+ /* Even tracks: the curve editors below plot one step per column, so a wider
961
+ anchored column would slide every swatch out from under its own point. */
865
962
  grid-template-columns: repeat(var(--swatch-cols), minmax(0, 1fr));
866
- transition: grid-template-columns var(--ui-transition-fast);
867
963
  gap: var(--ui-space-4) var(--swatch-gap, var(--ui-space-4));
868
964
  align-items: start;
869
965
  justify-content: start;
@@ -871,6 +967,16 @@
871
967
  max-width: calc(var(--swatch-cols) * 4.5rem + (var(--swatch-cols) - 1) * var(--swatch-gap, var(--ui-space-4)));
872
968
  }
873
969
 
970
+ /* The picker rides the same track geometry as the curve editors below, so
971
+ the two graphs share one left and right edge. */
972
+ .base-panel {
973
+ min-width: 0;
974
+ }
975
+
976
+ .base-anchor-toggle {
977
+ margin-left: auto;
978
+ }
979
+
874
980
  .curve-grid-span {
875
981
  display: flex;
876
982
  flex-direction: column;
@@ -927,9 +1033,9 @@
927
1033
  outline-offset: 1px;
928
1034
  }
929
1035
 
930
- /* The step the base color is placed at: dock-magnified (its grid column is
931
- also widened via the track template) with an emphasized border. No fill
932
- change — the swatch itself already IS the picked color. */
1036
+ /* The step the base color is placed at: taller, with an emphasized border. It
1037
+ keeps its column width so the curve editors stay aligned. No fill change:
1038
+ the swatch itself already IS the picked color. */
933
1039
  .swatch.gray-swatch.anchored {
934
1040
  border-color: var(--ui-border-higher);
935
1041
  height: calc(5.75rem + var(--ui-space-2));
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { FontFamily, FontSource } from '../core/themes/themeTypes';
3
3
  import { editorState, setFontSources, transaction } from '../core/store/editorStore';
4
- import { applyFontSources, applyFontStacks } from '../core/fonts/fontLoader';
5
4
  import {
6
5
  buildSourceFromFontFaceText,
7
6
  buildSourceFromUrl,
@@ -69,8 +68,6 @@
69
68
 
70
69
  function commitSources(next: FontSource[]) {
71
70
  setFontSources(next);
72
- applyFontSources(next);
73
- applyFontStacks(fontStacksList, next);
74
71
  }
75
72
 
76
73
  /** One paste field → sniff whether it's @font-face or a URL/embed and
@@ -220,8 +217,6 @@
220
217
  s.fonts.sources = next;
221
218
  s.fonts.stacks = updatedStacks;
222
219
  });
223
- applyFontSources(next);
224
- applyFontStacks(updatedStacks, next);
225
220
  }
226
221
 
227
222
  /** Resolve a clickable target for the row. We prefer the human-readable