@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.
- package/CHANGELOG.md +45 -0
- package/dist-plugin/adjust/index.d.cts +1 -1
- package/dist-plugin/adjust/index.d.ts +1 -1
- package/dist-plugin/{chunk-76TFDTJO.js → chunk-JIGIIE5P.js} +60 -132
- package/dist-plugin/{chunk-44RSTAII.js → chunk-TKZBVIW5.js} +33 -5
- package/dist-plugin/chunk-YR6GPXW2.js +129 -0
- package/dist-plugin/generateColorsAndType/index.cjs +87 -31
- package/dist-plugin/generateColorsAndType/index.d.cts +1 -1
- package/dist-plugin/generateColorsAndType/index.d.ts +1 -1
- package/dist-plugin/generateColorsAndType/index.js +22 -12
- package/dist-plugin/index.cjs +95 -55
- package/dist-plugin/index.js +5 -3
- package/dist-plugin/{themeTypes-DSwKq-bj.d.cts → themeTypes-Dky03HL9.d.cts} +18 -5
- package/dist-plugin/{themeTypes-DSwKq-bj.d.ts → themeTypes-Dky03HL9.d.ts} +18 -5
- package/dist-plugin/tokensCssMigrations/index.cjs +71 -5
- package/dist-plugin/tokensCssMigrations/index.d.cts +20 -9
- package/dist-plugin/tokensCssMigrations/index.d.ts +20 -9
- package/dist-plugin/tokensCssMigrations/index.js +4 -1
- package/package.json +4 -2
- package/src/editor/component-editor/NotificationEditor.svelte +6 -0
- package/src/editor/core/palettes/oklch.ts +3 -137
- package/src/editor/core/palettes/paletteDerivation.ts +79 -37
- package/src/editor/core/themes/generateColorsAndType.ts +18 -8
- package/src/editor/core/themes/themeTypes.ts +18 -6
- package/src/editor/docs/content/editing-tokens.md +6 -2
- package/src/editor/docs/content.generated.ts +1 -1
- package/src/editor/ui/BezierCurveEditor.svelte +90 -67
- package/src/editor/ui/PaletteEditor.svelte +150 -44
- package/src/editor/ui/Toggle.svelte +14 -2
- package/src/editor/ui/colors/ColorStory.svelte +12 -8
- package/src/editor/ui/curveEngine.ts +101 -21
- package/src/editor/ui/palette/OverridesPanel.svelte +19 -5
- package/src/editor/ui/palette/PaletteBase.svelte +17 -78
- package/src/editor/ui/palette/ScaleCurveEditor.svelte +109 -20
- package/src/editor/ui/palette/curveSummary.ts +20 -0
- package/src/editor/ui/palette/dockMagnify.ts +5 -13
- package/src/editor/ui/palette/paletteMath.ts +7 -4
- package/src/live-tokens/data/colors-and-type/autumn.json +528 -528
- package/src/live-tokens/data/colors-and-type/default.json +247 -247
- package/src/live-tokens/data/colors-and-type/halloween.json +532 -532
- package/src/live-tokens/data/colors-and-type/midnight-study.json +538 -538
- package/src/live-tokens/data/colors-and-type/ocean.json +521 -521
- package/src/live-tokens/data/colors-and-type/royal-velvet.json +532 -532
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +504 -504
- package/src/live-tokens/data/colors-and-type/sunset.json +531 -531
- package/src/live-tokens/data/themes/autumn.json +527 -527
- package/src/live-tokens/data/themes/halloween.json +531 -531
- package/src/live-tokens/data/themes/midnight-study.json +537 -537
- package/src/live-tokens/data/themes/ocean.json +520 -520
- package/src/live-tokens/data/themes/royal-velvet.json +531 -531
- package/src/live-tokens/data/themes/spring-meadow.json +504 -504
- package/src/live-tokens/data/themes/sunset.json +531 -531
- package/src/live-tokens/data/tokens.generated.css +282 -282
- package/src/system/components/FloatingTokenTags.css +0 -1
- package/src/system/internal/backgroundContrast.ts +7 -0
- package/src/system/internal/oklch.ts +189 -0
- 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, '
|
|
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
|
|
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
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
|
107
|
-
<div class="
|
|
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
|
|
138
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
*
|
|
3
|
-
*
|
|
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
|
-
/**
|
|
12
|
-
*
|
|
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[] =>
|
|
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;
|