@motion-proto/live-tokens 0.40.0 → 0.41.0
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 +115 -0
- package/dist-plugin/{chunk-D77VD4Z6.js → chunk-H4TRUINI.js} +102 -13
- package/dist-plugin/index.cjs +288 -67
- package/dist-plugin/index.js +171 -55
- package/dist-plugin/tokensCssMigrations/index.cjs +102 -13
- package/dist-plugin/tokensCssMigrations/index.js +1 -1
- package/package.json +3 -2
- package/src/app/site.css +97 -20
- package/src/editor/core/palettes/colorHarmony.ts +228 -0
- package/src/editor/core/palettes/contrast.ts +128 -0
- package/src/editor/core/palettes/oklch.ts +8 -0
- package/src/editor/core/palettes/paletteDerivation.ts +267 -72
- package/src/editor/core/palettes/recommendText.ts +216 -0
- package/src/editor/core/palettes/solveTextContrast.ts +251 -0
- package/src/editor/core/store/editorCore.ts +2 -1
- package/src/editor/core/store/editorPersistence.ts +18 -1
- package/src/editor/core/store/editorStore.ts +20 -1
- package/src/editor/core/store/editorTypes.ts +4 -0
- package/src/editor/core/store/editorViewStore.ts +7 -3
- package/src/editor/core/themes/migrations/2026-06-05-palette-unification.ts +11 -9
- package/src/editor/core/themes/migrations/2026-07-21-palette-oklch-basis.ts +51 -0
- package/src/editor/core/themes/migrations/2026-07-24-base-anchor-placement.ts +37 -0
- package/src/editor/core/themes/migrations/2026-07-25-background-spot-to-base.ts +40 -0
- package/src/editor/core/themes/themeTypes.ts +23 -3
- package/src/editor/docs/CodeBlock.svelte +1 -1
- package/src/editor/docs/Docs.svelte +30 -13
- package/src/editor/pages/EditorShell.svelte +31 -3
- package/src/editor/ui/ColorEditPanel.svelte +22 -16
- package/src/editor/ui/EditorViewSwitcher.svelte +43 -9
- package/src/editor/ui/FontStackEditor.svelte +0 -1
- package/src/editor/ui/PaletteEditor.svelte +117 -155
- package/src/editor/ui/ProjectFontsSection.svelte +0 -3
- package/src/editor/ui/TextStylesSection.svelte +173 -0
- package/src/editor/ui/UILineHeightSelector.svelte +6 -5
- package/src/editor/ui/UIPaletteSelector.svelte +6 -1
- package/src/editor/ui/UITextTransformSelector.svelte +106 -0
- package/src/editor/ui/VariablesTab.svelte +23 -10
- package/src/editor/ui/colors/ColorReadouts.svelte +123 -0
- package/src/editor/ui/colors/ColorStory.svelte +375 -0
- package/src/editor/ui/colors/ColorWheel.svelte +740 -0
- package/src/editor/ui/colors/ColorsTab.svelte +508 -0
- package/src/editor/ui/colors/HarmonyAxesList.svelte +320 -0
- package/src/editor/ui/colors/LightnessBar.svelte +261 -0
- package/src/editor/ui/colors/PaletteStepStrip.svelte +142 -0
- package/src/editor/ui/colors/colorWheelMath.ts +9 -0
- package/src/editor/ui/colors/harmony-icons/analogous.svg +13 -0
- package/src/editor/ui/colors/harmony-icons/complementary.svg +10 -0
- package/src/editor/ui/colors/harmony-icons/compound.svg +15 -0
- package/src/editor/ui/colors/harmony-icons/custom.svg +10 -0
- package/src/editor/ui/colors/harmony-icons/monochromatic.svg +11 -0
- package/src/editor/ui/colors/harmony-icons/shades.svg +9 -0
- package/src/editor/ui/colors/harmony-icons/split-complementary.svg +13 -0
- package/src/editor/ui/colors/harmony-icons/square.svg +13 -0
- package/src/editor/ui/colors/harmony-icons/tetradic.svg +15 -0
- package/src/editor/ui/colors/harmony-icons/triadic.svg +13 -0
- package/src/editor/ui/colors/harmonyModeIcons.ts +23 -0
- package/src/editor/ui/colors/paletteBaseColor.ts +214 -0
- package/src/editor/ui/index.ts +0 -1
- package/src/editor/ui/palette/OverridesPanel.svelte +6 -3
- package/src/editor/ui/palette/PaletteBase.svelte +16 -8
- package/src/editor/ui/palette/dockMagnify.ts +22 -0
- package/src/editor/ui/palette/paletteEditorState.ts +5 -4
- package/src/editor/ui/palette/paletteMath.ts +93 -181
- package/src/editor/ui/sections/textStyles.ts +82 -0
- package/src/editor/ui/sections/tokenScales.ts +3 -3
- package/src/live-tokens/data/themes/default.json +3 -3
- package/src/live-tokens/data/tokens.generated.css +96 -95
- package/src/system/components/Badge.svelte +11 -11
- package/src/system/components/Button.svelte +7 -7
- package/src/system/components/Callout.svelte +8 -8
- package/src/system/components/Card.svelte +2 -2
- package/src/system/components/CodeSnippet.svelte +1 -1
- package/src/system/components/CollapsibleSection.svelte +7 -7
- package/src/system/components/CornerBadge.svelte +1 -1
- package/src/system/components/Dialog.svelte +2 -2
- package/src/system/components/FloatingTokenTags.css +6 -5
- package/src/system/components/Input.svelte +6 -6
- package/src/system/components/MenuSelect.svelte +4 -4
- package/src/system/components/Notification.svelte +8 -8
- package/src/system/components/ProgressBar.svelte +2 -2
- package/src/system/components/RadioButton.svelte +4 -4
- package/src/system/components/SectionDivider.svelte +6 -6
- package/src/system/components/SegmentedControl.svelte +5 -5
- package/src/system/components/SideNavigation.svelte +12 -12
- package/src/system/components/TabBar.svelte +4 -4
- package/src/system/components/Table.svelte +2 -2
- package/src/system/components/Tooltip.svelte +1 -1
- package/src/system/styles/tokens.css +59 -6
- package/src/editor/ui/TextTab.svelte +0 -203
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
*/
|
|
7
|
+
|
|
8
|
+
const ANCHORED_GROW = 1.7;
|
|
9
|
+
const NEIGHBOUR_GROW = 1.2;
|
|
10
|
+
|
|
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. */
|
|
13
|
+
export function dockGrow(index: number, anchored: number | null): number {
|
|
14
|
+
if (anchored === null) return 1;
|
|
15
|
+
const d = Math.abs(index - anchored);
|
|
16
|
+
return d === 0 ? ANCHORED_GROW : d === 1 ? NEIGHBOUR_GROW : 1;
|
|
17
|
+
}
|
|
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
|
+
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import type { Scope } from '../../core/store/editorStore';
|
|
26
|
+
import type { Oklch } from '../../core/palettes/oklch';
|
|
26
27
|
|
|
27
28
|
/** Sentinel key for the base swatch (used as `editingKey === BASE_KEY`). */
|
|
28
29
|
export const BASE_KEY = '__base__';
|
|
@@ -33,8 +34,8 @@ export type EditingState =
|
|
|
33
34
|
| {
|
|
34
35
|
kind: 'editingStep';
|
|
35
36
|
stepKey: string;
|
|
36
|
-
snapshot:
|
|
37
|
-
draft:
|
|
37
|
+
snapshot: Oklch;
|
|
38
|
+
draft: Oklch;
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
export const idleState: EditingState = { kind: 'idle' };
|
|
@@ -62,8 +63,8 @@ export function activeKey(s: EditingState): string | null {
|
|
|
62
63
|
return s.stepKey;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
/** The current draft
|
|
66
|
-
export function activeDraft(s: EditingState):
|
|
66
|
+
/** The current draft OKLCH (for editingStep) or null. editingBase draws from baseColor directly. */
|
|
67
|
+
export function activeDraft(s: EditingState): Oklch | null {
|
|
67
68
|
return s.kind === 'editingStep' ? s.draft : null;
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -1,21 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type CurveAnchor, makeAnchor
|
|
1
|
+
import type { Oklch } from '../../core/palettes/oklch';
|
|
2
|
+
import { type CurveAnchor, makeAnchor } from '../curveEngine';
|
|
3
3
|
import type { PaletteConfig } from '../../core/themes/themeTypes';
|
|
4
|
+
import {
|
|
5
|
+
type Step,
|
|
6
|
+
type Scale,
|
|
7
|
+
type SchemeDirection,
|
|
8
|
+
SCALES,
|
|
9
|
+
PALETTE_STEPS,
|
|
10
|
+
DEFAULT_PALETTE_LIGHTNESS,
|
|
11
|
+
DEFAULT_PALETTE_SATURATION,
|
|
12
|
+
defaultScaleCurves,
|
|
13
|
+
scaleCurveDefaults,
|
|
14
|
+
computePaletteOklch,
|
|
15
|
+
computeDerivedOklch,
|
|
16
|
+
stepIndexToX,
|
|
17
|
+
scaleStepToX,
|
|
18
|
+
paletteStepKey,
|
|
19
|
+
stepKey,
|
|
20
|
+
nearestPaletteStep,
|
|
21
|
+
syncBaseAnchor,
|
|
22
|
+
clearBaseAnchor,
|
|
23
|
+
} from '../../core/palettes/paletteDerivation';
|
|
24
|
+
|
|
25
|
+
// Single source of truth is `core/palettes/paletteDerivation`; these are the
|
|
26
|
+
// derivation symbols re-exported so existing UI import sites keep resolving.
|
|
27
|
+
export {
|
|
28
|
+
type Step,
|
|
29
|
+
type Scale,
|
|
30
|
+
type SchemeDirection,
|
|
31
|
+
SCALES,
|
|
32
|
+
SCALES as scales,
|
|
33
|
+
PALETTE_STEPS,
|
|
34
|
+
DEFAULT_PALETTE_LIGHTNESS,
|
|
35
|
+
DEFAULT_PALETTE_SATURATION,
|
|
36
|
+
defaultScaleCurves,
|
|
37
|
+
scaleCurveDefaults,
|
|
38
|
+
computePaletteOklch,
|
|
39
|
+
computeDerivedOklch,
|
|
40
|
+
stepIndexToX,
|
|
41
|
+
scaleStepToX,
|
|
42
|
+
paletteStepKey,
|
|
43
|
+
stepKey,
|
|
44
|
+
nearestPaletteStep,
|
|
45
|
+
syncBaseAnchor,
|
|
46
|
+
clearBaseAnchor,
|
|
47
|
+
};
|
|
4
48
|
|
|
5
|
-
export const GRAY_FALLBACK =
|
|
6
|
-
|
|
7
|
-
export interface Step {
|
|
8
|
-
name: string;
|
|
9
|
-
position: number;
|
|
10
|
-
lightness?: number;
|
|
11
|
-
saturation?: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface Scale {
|
|
15
|
-
title: string;
|
|
16
|
-
isText: boolean;
|
|
17
|
-
steps: Step[];
|
|
18
|
-
}
|
|
49
|
+
export const GRAY_FALLBACK: Oklch = { l: 0.5999, c: 0, h: 89.88 };
|
|
19
50
|
|
|
20
51
|
export interface PaletteStepDef {
|
|
21
52
|
label: string;
|
|
@@ -25,8 +56,6 @@ export interface PaletteStepDef {
|
|
|
25
56
|
export type CurveOffset = Record<string, number>;
|
|
26
57
|
export type ScaleCurves = Record<string, { lightness: CurveAnchor[]; saturation: CurveAnchor[] }>;
|
|
27
58
|
|
|
28
|
-
export const DEFAULT_PALETTE_LIGHTNESS = (): CurveAnchor[] => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
|
|
29
|
-
export const DEFAULT_PALETTE_SATURATION = (): CurveAnchor[] => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
|
|
30
59
|
// Neutrals seed a calmer, wider lightness ramp than accents; the saturation
|
|
31
60
|
// curve is the same flat 100 (chroma comes from a low-chroma base, not a cap).
|
|
32
61
|
export const DEFAULT_NEUTRAL_LIGHTNESS = (): CurveAnchor[] => [makeAnchor(0, 92, 5), makeAnchor(100, 3, 5)];
|
|
@@ -36,39 +65,27 @@ export const DEFAULT_NEUTRAL_LIGHTNESS = (): CurveAnchor[] => [makeAnchor(0, 92,
|
|
|
36
65
|
* per-role difference is the seed: neutrals get the wider neutral lightness
|
|
37
66
|
* ramp, accents the standard one. Everything is editable afterward.
|
|
38
67
|
*/
|
|
39
|
-
export function defaultPaletteConfig(opts: { baseColor:
|
|
40
|
-
|
|
68
|
+
export function defaultPaletteConfig(opts: { baseColor: Oklch; neutral?: boolean; scheme?: SchemeDirection }): PaletteConfig {
|
|
69
|
+
const cfg: PaletteConfig = {
|
|
41
70
|
baseColor: opts.baseColor,
|
|
42
71
|
lightnessCurve: opts.neutral ? DEFAULT_NEUTRAL_LIGHTNESS() : DEFAULT_PALETTE_LIGHTNESS(),
|
|
43
72
|
saturationCurve: DEFAULT_PALETTE_SATURATION(),
|
|
44
|
-
scaleCurves: defaultScaleCurvesObject(),
|
|
73
|
+
scaleCurves: defaultScaleCurvesObject(opts.scheme ?? 'dark'),
|
|
45
74
|
curveOffset: { lightness: 0, saturation: 0 },
|
|
46
75
|
overrides: {},
|
|
47
76
|
snappedScales: [],
|
|
48
77
|
anchorToBase: true,
|
|
49
78
|
};
|
|
79
|
+
syncBaseAnchor(cfg);
|
|
80
|
+
return cfg;
|
|
50
81
|
}
|
|
51
82
|
|
|
52
|
-
export
|
|
53
|
-
|
|
54
|
-
lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
|
|
55
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)],
|
|
56
|
-
},
|
|
57
|
-
Borders: {
|
|
58
|
-
lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
|
|
59
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)],
|
|
60
|
-
},
|
|
61
|
-
Text: {
|
|
62
|
-
lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
|
|
63
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)],
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export function defaultScaleCurvesObject(): ScaleCurves {
|
|
83
|
+
export function defaultScaleCurvesObject(scheme: SchemeDirection = 'dark'): ScaleCurves {
|
|
84
|
+
const defs = scaleCurveDefaults(scheme);
|
|
68
85
|
return {
|
|
69
|
-
Surfaces: { lightness:
|
|
70
|
-
Borders:
|
|
71
|
-
Text:
|
|
86
|
+
Surfaces: { lightness: defs.Surfaces.lightness(), saturation: defs.Surfaces.saturation() },
|
|
87
|
+
Borders: { lightness: defs.Borders.lightness(), saturation: defs.Borders.saturation() },
|
|
88
|
+
Text: { lightness: defs.Text.lightness(), saturation: defs.Text.saturation() },
|
|
72
89
|
};
|
|
73
90
|
}
|
|
74
91
|
|
|
@@ -86,163 +103,58 @@ export const paletteStepLightness: PaletteStepDef[] = [
|
|
|
86
103
|
{ label: '950', lightness: 8 },
|
|
87
104
|
];
|
|
88
105
|
|
|
89
|
-
export const scales: Scale[] = [
|
|
90
|
-
{
|
|
91
|
-
title: 'Surfaces',
|
|
92
|
-
isText: false,
|
|
93
|
-
steps: [
|
|
94
|
-
{ name: 'lowest', position: -1 },
|
|
95
|
-
{ name: 'lower', position: -2/3 },
|
|
96
|
-
{ name: 'low', position: -1/3 },
|
|
97
|
-
{ name: 'default', position: 0 },
|
|
98
|
-
{ name: 'high', position: 1/3 },
|
|
99
|
-
{ name: 'higher', position: 2/3 },
|
|
100
|
-
{ name: 'highest', position: 1 },
|
|
101
|
-
]
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
title: 'Borders',
|
|
105
|
-
isText: false,
|
|
106
|
-
steps: [
|
|
107
|
-
{ name: 'faint', position: -1 },
|
|
108
|
-
{ name: 'subtle', position: -0.5 },
|
|
109
|
-
{ name: 'default', position: 0 },
|
|
110
|
-
{ name: 'medium', position: 0.5 },
|
|
111
|
-
{ name: 'strong', position: 1 },
|
|
112
|
-
]
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
title: 'Text',
|
|
116
|
-
isText: true,
|
|
117
|
-
steps: [
|
|
118
|
-
{ name: 'primary', position: 0 },
|
|
119
|
-
{ name: 'secondary', position: 0 },
|
|
120
|
-
{ name: 'tertiary', position: 0 },
|
|
121
|
-
{ name: 'muted', position: 0 },
|
|
122
|
-
{ name: 'disabled', position: 0 },
|
|
123
|
-
]
|
|
124
|
-
}
|
|
125
|
-
];
|
|
126
|
-
|
|
127
|
-
export const paletteStepKey = (label: string) => `Palette-${label}`;
|
|
128
|
-
export const stepKey = (scaleTitle: string, stepName: string) => `${scaleTitle}-${stepName}`;
|
|
129
106
|
export const scaleCurveKey = (scaleTitle: string, channel: 'lightness' | 'saturation') => `${scaleTitle}-${channel}`;
|
|
130
107
|
|
|
131
|
-
export function stepIndexToX(index: number): number {
|
|
132
|
-
return (index / (paletteStepLightness.length - 1)) * 100;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function scaleStepToX(step: Step, scale: Scale): number {
|
|
136
|
-
const idx = scale.steps.indexOf(step);
|
|
137
|
-
return scale.steps.length > 1 ? (idx / (scale.steps.length - 1)) * 100 : 50;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function injectLockedAnchor(curve: CurveAnchor[], x: number, y: number): { curve: CurveAnchor[]; idx: number; injected: boolean } {
|
|
141
|
-
const existing = curve.findIndex(a => Math.abs(a.x - x) < 0.5);
|
|
142
|
-
if (existing >= 0) {
|
|
143
|
-
if (curve[existing].x === x && Math.abs(curve[existing].y - y) < 0.01) return { curve, idx: existing, injected: false };
|
|
144
|
-
return { curve: curve.map((a, i) => i === existing ? { ...a, x, y } : a), idx: existing, injected: false };
|
|
145
|
-
}
|
|
146
|
-
let insertAt = curve.findIndex(a => a.x > x);
|
|
147
|
-
if (insertAt < 0) insertAt = curve.length;
|
|
148
|
-
return { curve: [...curve.slice(0, insertAt), makeAnchor(x, y, 15), ...curve.slice(insertAt)], idx: insertAt, injected: true };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function removeLockedAnchor(curve: CurveAnchor[], idx: number | null): CurveAnchor[] {
|
|
152
|
-
if (idx === null || idx === 0 || idx === curve.length - 1) return curve;
|
|
153
|
-
return curve.filter((_, i) => i !== idx);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function computePaletteColor(
|
|
157
|
-
index: number,
|
|
158
|
-
base: string,
|
|
159
|
-
lightnessCurve: CurveAnchor[],
|
|
160
|
-
saturationCurve: CurveAnchor[],
|
|
161
|
-
curveOffset: CurveOffset
|
|
162
|
-
): string {
|
|
163
|
-
const { c: baseC, h } = hexToOklch(base);
|
|
164
|
-
const xPos = stepIndexToX(index);
|
|
165
|
-
|
|
166
|
-
const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset['lightness'] ?? 0))) / 100;
|
|
167
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset['saturation'] ?? 0)) / 100));
|
|
168
|
-
const targetC = baseC * satMul;
|
|
169
|
-
|
|
170
|
-
const clamped = gamutClamp(targetL, targetC, h);
|
|
171
|
-
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function computeDerivedColor(
|
|
175
|
-
step: Step,
|
|
176
|
-
base: string,
|
|
177
|
-
scaleTitle: string,
|
|
178
|
-
scaleCurves: ScaleCurves,
|
|
179
|
-
curveOffset: CurveOffset
|
|
180
|
-
): string {
|
|
181
|
-
const { l: baseL, c: baseC, h: baseH } = hexToOklch(base);
|
|
182
|
-
const scale = scales.find(s => s.title === scaleTitle)!;
|
|
183
|
-
const xPos = scaleStepToX(step, scale);
|
|
184
|
-
|
|
185
|
-
const lCurve = scaleCurves[scaleTitle]?.lightness ?? [];
|
|
186
|
-
const sCurve = scaleCurves[scaleTitle]?.saturation ?? [];
|
|
187
|
-
const lOff = curveOffset[scaleCurveKey(scaleTitle, 'lightness')] ?? 0;
|
|
188
|
-
const sOff = curveOffset[scaleCurveKey(scaleTitle, 'saturation')] ?? 0;
|
|
189
|
-
|
|
190
|
-
let targetL: number;
|
|
191
|
-
if (scale.isText) {
|
|
192
|
-
// Text: lightness curve is a multiplier (100 = 1x base lightness)
|
|
193
|
-
const lMul = Math.max(0, Math.min(2, (sampleCurve(lCurve, xPos) + lOff) / 100));
|
|
194
|
-
targetL = Math.max(0, Math.min(1, baseL * lMul));
|
|
195
|
-
} else {
|
|
196
|
-
targetL = Math.max(0, Math.min(100, sampleCurve(lCurve, xPos) + lOff)) / 100;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
|
|
200
|
-
const targetC = baseC * satMul;
|
|
201
|
-
|
|
202
|
-
const clamped = gamutClamp(targetL, targetC, baseH);
|
|
203
|
-
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
108
|
interface PaletteComputed {
|
|
207
|
-
|
|
109
|
+
oklch: Oklch;
|
|
208
110
|
}
|
|
209
111
|
|
|
210
|
-
// Pick the contiguous window of palette steps
|
|
211
|
-
//
|
|
112
|
+
// Pick the contiguous window of palette steps whose lightness curve best
|
|
113
|
+
// matches this scale's derived lightness curve. Scores both orderings —
|
|
114
|
+
// dark-first (ascending L, historical) and its reverse (light-first,
|
|
115
|
+
// descending L) — so a light-scheme scale (surfaces descend) snaps as
|
|
116
|
+
// naturally as a dark one. Ties prefer dark-first (byte-stable for existing
|
|
117
|
+
// dark themes).
|
|
212
118
|
export function snapScaleToPalette(
|
|
213
119
|
scale: Scale,
|
|
214
|
-
baseColor:
|
|
120
|
+
baseColor: Oklch,
|
|
215
121
|
scaleCurves: ScaleCurves,
|
|
216
122
|
curveOffset: CurveOffset,
|
|
217
123
|
paletteComputed: ReadonlyArray<PaletteComputed>
|
|
218
|
-
): Record<string,
|
|
124
|
+
): Record<string, Oklch> {
|
|
219
125
|
const n = scale.steps.length;
|
|
220
126
|
|
|
221
|
-
const stepL = scale.steps.map(step =>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
127
|
+
const stepL = scale.steps.map(step =>
|
|
128
|
+
computeDerivedOklch(step, baseColor, scale.title, scaleCurves, curveOffset).l,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const bestWindow = (pal: ReadonlyArray<PaletteComputed>): { cost: number; start: number } => {
|
|
132
|
+
const palL = pal.map(ps => ps.oklch.l);
|
|
133
|
+
let start = 0;
|
|
134
|
+
let cost = Infinity;
|
|
135
|
+
for (let s = 0; s <= pal.length - n; s++) {
|
|
136
|
+
let c = 0;
|
|
137
|
+
for (let i = 0; i < n; i++) {
|
|
138
|
+
const d = stepL[i] - palL[s + i];
|
|
139
|
+
c += d * d;
|
|
140
|
+
}
|
|
141
|
+
if (c < cost) { cost = c; start = s; }
|
|
142
|
+
}
|
|
143
|
+
return { cost, start };
|
|
144
|
+
};
|
|
225
145
|
|
|
226
146
|
const palDarkFirst = [...paletteComputed].reverse();
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const d = stepL[i] - palLDarkFirst[start + i];
|
|
235
|
-
cost += d * d;
|
|
236
|
-
}
|
|
237
|
-
if (cost < bestCost) {
|
|
238
|
-
bestCost = cost;
|
|
239
|
-
bestStart = start;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
147
|
+
const palLightFirst = [...palDarkFirst].reverse();
|
|
148
|
+
const dark = bestWindow(palDarkFirst);
|
|
149
|
+
const light = bestWindow(palLightFirst);
|
|
150
|
+
|
|
151
|
+
const useLight = light.cost < dark.cost;
|
|
152
|
+
const pal = useLight ? palLightFirst : palDarkFirst;
|
|
153
|
+
const start = useLight ? light.start : dark.start;
|
|
242
154
|
|
|
243
|
-
const assigned: Record<string,
|
|
155
|
+
const assigned: Record<string, Oklch> = {};
|
|
244
156
|
for (let i = 0; i < n; i++) {
|
|
245
|
-
assigned[stepKey(scale.title, scale.steps[i].name)] =
|
|
157
|
+
assigned[stepKey(scale.title, scale.steps[i].name)] = pal[start + i].oklch;
|
|
246
158
|
}
|
|
247
159
|
return assigned;
|
|
248
160
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry of the eight v1 semantic text styles rendered by TextStylesSection.
|
|
3
|
+
* Each style is a bundle of alias tokens declared in tokens.css; the prefix
|
|
4
|
+
* joins with a kind suffix (`-font-family`, `-font-size`, …) to form each
|
|
5
|
+
* axis variable the editor's pickers target.
|
|
6
|
+
*
|
|
7
|
+
* Family/size/weight/letter-spacing/line-height are editable on every style.
|
|
8
|
+
* `hasTextTransform` marks the one style (eyebrow) that also exposes a
|
|
9
|
+
* text-transform picker — the only axis that exists on just one style.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface TextStyle {
|
|
13
|
+
name: string;
|
|
14
|
+
label: string;
|
|
15
|
+
/** Variable prefix, e.g. `--heading-xl`; suffixes form each axis var. */
|
|
16
|
+
prefix: string;
|
|
17
|
+
/** Element (or class) this style is the default for. */
|
|
18
|
+
defaultElement: string;
|
|
19
|
+
preview: string;
|
|
20
|
+
/** Whether this style exposes an editable `-text-transform` axis. */
|
|
21
|
+
hasTextTransform?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const TEXT_STYLES: TextStyle[] = [
|
|
25
|
+
{
|
|
26
|
+
name: 'heading-xl',
|
|
27
|
+
label: 'Heading XL',
|
|
28
|
+
prefix: '--heading-xl',
|
|
29
|
+
defaultElement: 'h1',
|
|
30
|
+
preview: 'Page title',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'heading-lg',
|
|
34
|
+
label: 'Heading LG',
|
|
35
|
+
prefix: '--heading-lg',
|
|
36
|
+
defaultElement: 'h2',
|
|
37
|
+
preview: 'Section heading',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'heading-md',
|
|
41
|
+
label: 'Heading MD',
|
|
42
|
+
prefix: '--heading-md',
|
|
43
|
+
defaultElement: 'h3',
|
|
44
|
+
preview: 'Subsection heading',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'heading-sm',
|
|
48
|
+
label: 'Heading SM',
|
|
49
|
+
prefix: '--heading-sm',
|
|
50
|
+
defaultElement: 'h4',
|
|
51
|
+
preview: 'Minor heading',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'body-md',
|
|
55
|
+
label: 'Body MD',
|
|
56
|
+
prefix: '--body-md',
|
|
57
|
+
defaultElement: 'p',
|
|
58
|
+
preview: 'Body copy for comfortable reading.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'body-sm',
|
|
62
|
+
label: 'Body SM',
|
|
63
|
+
prefix: '--body-sm',
|
|
64
|
+
defaultElement: 'small',
|
|
65
|
+
preview: 'Captions and secondary text.',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'code',
|
|
69
|
+
label: 'Code',
|
|
70
|
+
prefix: '--code',
|
|
71
|
+
defaultElement: 'code',
|
|
72
|
+
preview: 'const total = sum(items);',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'eyebrow',
|
|
76
|
+
label: 'Eyebrow',
|
|
77
|
+
prefix: '--eyebrow',
|
|
78
|
+
defaultElement: 'eyebrow',
|
|
79
|
+
preview: 'Overline label',
|
|
80
|
+
hasTextTransform: true,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
@@ -26,7 +26,7 @@ export const RADIUS_VARS = [
|
|
|
26
26
|
export const FONT_SIZE_VARS = [
|
|
27
27
|
'--font-size-xs', '--font-size-sm', '--font-size-md', '--font-size-lg',
|
|
28
28
|
'--font-size-xl', '--font-size-2xl', '--font-size-3xl', '--font-size-4xl',
|
|
29
|
-
'--font-size-5xl', '--font-size-6xl',
|
|
29
|
+
'--font-size-5xl', '--font-size-6xl', '--font-size-7xl',
|
|
30
30
|
] as const;
|
|
31
31
|
|
|
32
32
|
export const ICON_SIZE_VARS = [
|
|
@@ -42,8 +42,8 @@ export const FONT_WEIGHT_VARS = [
|
|
|
42
42
|
] as const;
|
|
43
43
|
|
|
44
44
|
export const LINE_HEIGHT_VARS = [
|
|
45
|
-
'--line-height-
|
|
46
|
-
'--line-height-
|
|
45
|
+
'--line-height-none', '--line-height-tightest', '--line-height-tighter',
|
|
46
|
+
'--line-height-tight', '--line-height-normal', '--line-height-relaxed',
|
|
47
47
|
] as const;
|
|
48
48
|
|
|
49
49
|
export const LETTER_SPACING_VARS = [
|
|
@@ -1721,7 +1721,7 @@
|
|
|
1721
1721
|
"--badge-trait-text-font-family": "var(--font-sans)",
|
|
1722
1722
|
"--badge-trait-text-font-size": "var(--font-size-sm)",
|
|
1723
1723
|
"--badge-trait-text-font-weight": "var(--font-weight-light)",
|
|
1724
|
-
"--badge-trait-text-line-height": "var(--line-height-
|
|
1724
|
+
"--badge-trait-text-line-height": "var(--line-height-none)",
|
|
1725
1725
|
"--badge-trait-border": "var(--border-brand-medium)",
|
|
1726
1726
|
"--badge-trait-border-width": "var(--border-width-1)",
|
|
1727
1727
|
"--badge-trait-radius": "var(--radius-full)",
|
|
@@ -1732,14 +1732,14 @@
|
|
|
1732
1732
|
"--sectiondivider-title-font-family": "var(--font-display)",
|
|
1733
1733
|
"--sectiondivider-title-font-size": "var(--font-size-5xl)",
|
|
1734
1734
|
"--sectiondivider-title-font-weight": "var(--font-weight-normal)",
|
|
1735
|
-
"--sectiondivider-title-line-height": "var(--line-height-
|
|
1735
|
+
"--sectiondivider-title-line-height": "var(--line-height-none)",
|
|
1736
1736
|
"--sectiondivider-title-border-width": "var(--border-width-4)",
|
|
1737
1737
|
"--sectiondivider-title-stroke-color": "var(--surface-canvas-lowest)",
|
|
1738
1738
|
"--sectiondivider-description": "var(--text-secondary)",
|
|
1739
1739
|
"--sectiondivider-description-font-family": "var(--font-sans)",
|
|
1740
1740
|
"--sectiondivider-description-font-size": "var(--font-size-md)",
|
|
1741
1741
|
"--sectiondivider-description-font-weight": "var(--font-weight-normal)",
|
|
1742
|
-
"--sectiondivider-description-line-height": "var(--line-height-
|
|
1742
|
+
"--sectiondivider-description-line-height": "var(--line-height-normal)",
|
|
1743
1743
|
"--dialog-primary-default-surface": "var(--surface-neutral-high)",
|
|
1744
1744
|
"--dialog-primary-default-text": "var(--text-primary)",
|
|
1745
1745
|
"--dialog-primary-default-border": "var(--border-neutral-medium)",
|