okchroma 0.6.1 → 0.8.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/README.md +82 -17
- package/dist-lib/index.cjs +331 -558
- package/dist-lib/index.mjs +331 -558
- package/dist-lib/types/engine/cssRender.d.ts +0 -37
- package/dist-lib/types/engine/dtcgRender.d.ts +47 -0
- package/dist-lib/types/engine/figmaRender.d.ts +1 -5
- package/dist-lib/types/engine/tokenDescriptions.d.ts +20 -0
- package/dist-lib/types/engine/tokenNames.d.ts +14 -30
- package/dist-lib/types/engine/tokensRender.d.ts +2 -4
- package/dist-lib/types/index.d.ts +6 -7
- package/package.json +6 -5
- package/tokens/semantic.css +0 -202
package/dist-lib/index.cjs
CHANGED
|
@@ -20,27 +20,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ABSOLUTE_GROUP: () => ABSOLUTE_GROUP,
|
|
24
|
+
BAND_STOPS: () => BAND_STOPS,
|
|
25
|
+
COLOR_GROUP: () => COLOR_GROUP,
|
|
23
26
|
CSS_FAMILY: () => CSS_FAMILY,
|
|
24
27
|
DEFAULT_LINK_HEX: () => DEFAULT_LINK_HEX,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
INTERACTION_POLE_FAMILY: () => INTERACTION_POLE_FAMILY,
|
|
30
|
-
INTERACTION_ROWS: () => INTERACTION_ROWS,
|
|
31
|
-
INTERACTION_RUNGS: () => INTERACTION_RUNGS,
|
|
32
|
-
INTERACTION_STATES: () => INTERACTION_STATES,
|
|
28
|
+
GUARANTEE_SCOPE: () => GUARANTEE_SCOPE,
|
|
29
|
+
LINK_GROUP: () => LINK_GROUP,
|
|
30
|
+
LINK_POSTURES: () => LINK_POSTURES,
|
|
31
|
+
LINK_STATES: () => LINK_STATES,
|
|
33
32
|
MODE_SPECS: () => MODE_SPECS,
|
|
34
33
|
OFFSET_ALPHAS: () => OFFSET_ALPHAS,
|
|
35
|
-
OPACITY_RUNGS: () => OPACITY_RUNGS,
|
|
36
34
|
OUTLINE_HOVER_ALPHA: () => OUTLINE_HOVER_ALPHA,
|
|
37
35
|
PAPER_0: () => PAPER_0,
|
|
38
36
|
PEN_100: () => PEN_100,
|
|
39
|
-
RESOLVER_ID: () => RESOLVER_ID,
|
|
40
37
|
SCALE_STOP_COUNT: () => SCALE_STOP_COUNT,
|
|
41
|
-
SCRIM_ALPHA: () => SCRIM_ALPHA,
|
|
42
38
|
SECONDARY_DISTINCT_DELTA_E: () => SECONDARY_DISTINCT_DELTA_E,
|
|
43
|
-
SHADOW_ALPHAS: () => SHADOW_ALPHAS,
|
|
44
39
|
SIGNALS: () => SIGNALS2,
|
|
45
40
|
SIGNAL_EMIT_NAME: () => SIGNAL_EMIT_NAME,
|
|
46
41
|
SIGNAL_SCALES: () => SIGNAL_SCALES,
|
|
@@ -48,38 +43,35 @@ __export(index_exports, {
|
|
|
48
43
|
STAMP_FILL: () => STAMP_FILL,
|
|
49
44
|
STAMP_FILL_HOVER: () => STAMP_FILL_HOVER,
|
|
50
45
|
STAMP_FILL_PRESSED: () => STAMP_FILL_PRESSED,
|
|
46
|
+
STAMP_LEAF: () => STAMP_LEAF,
|
|
51
47
|
STAMP_ON: () => STAMP_ON,
|
|
52
48
|
STAMP_STATE_LEAVES: () => STAMP_STATE_LEAVES,
|
|
53
49
|
SUBTLE_PASTEL_K: () => SUBTLE_PASTEL_K,
|
|
54
50
|
SUBTLE_TINT_MULT: () => SUBTLE_TINT_MULT,
|
|
55
|
-
|
|
56
|
-
SYSTEM_LEAF: () => SYSTEM_LEAF,
|
|
51
|
+
absolutePath: () => absolutePath,
|
|
57
52
|
brandCss: () => brandCss,
|
|
58
53
|
checkAllCollisions: () => checkAllCollisions,
|
|
59
54
|
checkCollision: () => checkCollision,
|
|
60
55
|
checkHueCollision: () => checkHueCollision,
|
|
61
56
|
classifyArchetype: () => classifyArchetype,
|
|
57
|
+
cssVarName: () => cssVarName,
|
|
62
58
|
ctaBorderRung: () => ctaBorderRung,
|
|
63
59
|
ctaNeedsBorder: () => ctaNeedsBorder,
|
|
64
|
-
|
|
60
|
+
describeDocument: () => describeDocument,
|
|
61
|
+
describeToken: () => describeToken,
|
|
62
|
+
descriptionPathOf: () => descriptionPathOf,
|
|
63
|
+
familyPath: () => familyPath,
|
|
64
|
+
figmaPathOf: () => figmaPathOf,
|
|
65
65
|
generateNeutralScale: () => generateNeutralScale,
|
|
66
66
|
generateScale: () => generateScale,
|
|
67
67
|
generateSubtleSecondary: () => generateSubtleSecondary,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
interactionTintName: () => interactionTintName,
|
|
71
|
-
interactionTokens: () => interactionTokens,
|
|
72
|
-
interactionVarName: () => interactionVarName,
|
|
68
|
+
groupEntries: () => groupEntries,
|
|
69
|
+
linkPath: () => linkPath,
|
|
73
70
|
neutralCss: () => neutralCss,
|
|
74
71
|
neutralTintHue: () => neutralTintHue,
|
|
75
|
-
opacityLeafName: () => opacityLeafName,
|
|
76
|
-
opacityTokenPath: () => opacityTokenPath,
|
|
77
|
-
opacityVarName: () => opacityVarName,
|
|
78
72
|
pageStopFor: () => pageStopFor,
|
|
79
|
-
parseToken: () => parseToken,
|
|
80
73
|
readEmission: () => readEmission,
|
|
81
74
|
resolveBrand: () => resolveBrand,
|
|
82
|
-
resolveDtcgRamp: () => resolveDtcgRamp,
|
|
83
75
|
resolveLinkInverseTrio: () => resolveLinkInverseTrio,
|
|
84
76
|
resolveLinkTrio: () => resolveLinkTrio,
|
|
85
77
|
resolveReferences: () => resolveReferences,
|
|
@@ -89,12 +81,13 @@ __export(index_exports, {
|
|
|
89
81
|
stopHex: () => stopHex,
|
|
90
82
|
stopTokenName: () => stopTokenName,
|
|
91
83
|
stopsToVars: () => stopsToVars,
|
|
92
|
-
systemCss: () => systemCss,
|
|
93
84
|
themeToFigma: () => themeToFigma,
|
|
94
85
|
themeTokens: () => themeTokens,
|
|
95
86
|
toHex: () => toHex2,
|
|
96
87
|
tokenOrder: () => tokenOrder,
|
|
97
|
-
|
|
88
|
+
tokenPathOf: () => tokenPathOf,
|
|
89
|
+
tokensFromEmission: () => tokensFromEmission,
|
|
90
|
+
tokensToDtcg: () => tokensToDtcg
|
|
98
91
|
});
|
|
99
92
|
module.exports = __toCommonJS(index_exports);
|
|
100
93
|
|
|
@@ -825,29 +818,14 @@ function perceptualDarkC(L, H2, nativeC, keep = KEEP_DARK, gamut = MASTER_GAMUT)
|
|
|
825
818
|
var SHARED_NAMES = {
|
|
826
819
|
1: "paper-1",
|
|
827
820
|
2: "paper-3",
|
|
828
|
-
// stop 3 renamed wash-3 → paper-3 (owner 2026-07-24, elevation round): it is a
|
|
829
|
-
// PLANE in both themes (light sink / dark pop), so it belongs to the surface band.
|
|
830
|
-
// NAME ONLY — generation is index-keyed and unchanged; the chalk-collision machinery
|
|
831
|
-
// (collision.ts CHALK_STOPS) deliberately still spans stops 3–7. (The requirement
|
|
832
|
-
// spec's `group` label followed the band to 'paper' on 2026-08-31 — labels only,
|
|
833
|
-
// the spans never moved; see spec.ts groupOf.)
|
|
834
|
-
// Stage B (owner 2026-08-07, names only): relabeled to the ROOT_L-derived digit,
|
|
835
|
-
// paper-5 — same band, same index.
|
|
836
821
|
3: "paper-5",
|
|
837
822
|
4: "chalk-8",
|
|
838
823
|
5: "chalk-11",
|
|
839
824
|
6: "chalk-15",
|
|
840
825
|
7: "chalk-20",
|
|
841
826
|
8: "highlighter-26",
|
|
842
|
-
//
|
|
843
|
-
//
|
|
844
|
-
// deleted; this stop carries both jobs, its on-color a paper token), the between
|
|
845
|
-
// text stop (a value the retired text-cta hover state used to generate bespokely,
|
|
846
|
-
// promoted to a normal stop), and the strong text stop. The three read as states
|
|
847
|
-
// ARE the text-style cta (owner 2026-08-12). C49 restored the top
|
|
848
|
-
// two pre-collapse stop INDICES (11, and the off-scale anchor's 12 — emitted as a
|
|
849
|
-
// literal in cssRender, not through this table) with the between stop taking the
|
|
850
|
-
// vacated index 10; the indices (9/10/11) are unchanged since.
|
|
827
|
+
// the pen band: the first text stop doubles as the emphasis fill, the between stop and
|
|
828
|
+
// the strong stop follow; the three read as states are the text-style CTA (C49)
|
|
851
829
|
9: "pencil-47",
|
|
852
830
|
10: "pen-58",
|
|
853
831
|
11: "pen-70"
|
|
@@ -879,63 +857,22 @@ var STAMP_LEAF = {
|
|
|
879
857
|
EDGE: STAMP_STATE_LEAVES[STAMP_EDGE],
|
|
880
858
|
ON: STAMP_STATE_LEAVES[STAMP_ON]
|
|
881
859
|
};
|
|
882
|
-
var
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
TRANSPARENT: "system/alpha/transparent",
|
|
893
|
-
INK: "system/alpha/ink",
|
|
894
|
-
// the DIRECTIONAL ladders (owner 2026-08-31, renaming the 2026-08-18 bare rungs
|
|
895
|
-
// and the 2026-08-29 inverse ladder): away-from-bg is the page-polarity pole
|
|
896
|
-
// (black in light, white in dark), toward-bg the flipped pole — STATE LAYERS for
|
|
897
|
-
// INVERTED grounds, whose polarity is opposite the page's. (Never call toward-bg
|
|
898
|
-
// a "chalk": that word belongs to the tinted band stops.) The reversal lives in
|
|
899
|
-
// the engine: a consumer on an inverted ground names one token and never picks a
|
|
900
|
-
// pole. Colored fills never ride these; their states stay engine-solved stops.
|
|
901
|
-
// Rungs are two-digit percent, harmonizing with shadow-04/08/12.
|
|
902
|
-
AWAY_FROM_BG_06: "system/alpha/away-from-bg/06",
|
|
903
|
-
AWAY_FROM_BG_08: "system/alpha/away-from-bg/08",
|
|
904
|
-
AWAY_FROM_BG_16: "system/alpha/away-from-bg/16",
|
|
905
|
-
TOWARD_BG_06: "system/alpha/toward-bg/06",
|
|
906
|
-
TOWARD_BG_08: "system/alpha/toward-bg/08",
|
|
907
|
-
TOWARD_BG_16: "system/alpha/toward-bg/16",
|
|
908
|
-
SHADOW_04: "system/alpha/shadow-04",
|
|
909
|
-
SHADOW_08: "system/alpha/shadow-08",
|
|
910
|
-
SHADOW_12: "system/alpha/shadow-12"
|
|
911
|
-
},
|
|
912
|
-
// the OPACITY LADDER's rows: bare numbers, never colors. `alpha` above is the group
|
|
913
|
-
// of colors that carry transparency; `opacity` is the numbers they compose with
|
|
914
|
-
// (values in cssRender OPACITY_RUNGS; the ext plugin zones these under utility/).
|
|
915
|
-
OPACITY: {
|
|
916
|
-
"004": "system/opacity/004",
|
|
917
|
-
"008": "system/opacity/008",
|
|
918
|
-
"012": "system/opacity/012",
|
|
919
|
-
"016": "system/opacity/016",
|
|
920
|
-
"024": "system/opacity/024",
|
|
921
|
-
"032": "system/opacity/032",
|
|
922
|
-
"048": "system/opacity/048",
|
|
923
|
-
"064": "system/opacity/064"
|
|
924
|
-
}
|
|
925
|
-
};
|
|
926
|
-
var SURFACE_PLANE_LAW = {
|
|
927
|
-
[SYSTEM_LEAF.SURFACE.DIM]: { light: SHARED_NAMES[3], dark: PAPER_0 },
|
|
928
|
-
[SYSTEM_LEAF.SURFACE.LOW]: { light: SHARED_NAMES[2], dark: SHARED_NAMES[1] },
|
|
929
|
-
[SYSTEM_LEAF.SURFACE.MID]: { light: SHARED_NAMES[1], dark: SHARED_NAMES[2] },
|
|
930
|
-
[SYSTEM_LEAF.SURFACE.HIGH]: { light: PAPER_0, dark: SHARED_NAMES[3] }
|
|
931
|
-
};
|
|
860
|
+
var cssVarName = (path) => `--${path.join("-")}`;
|
|
861
|
+
var figmaPathOf = (path) => path.join("/");
|
|
862
|
+
var familyPath = (family, leaf) => [family, ...(STAMP_STATE_LEAVES[leaf] ?? leaf).split("/")];
|
|
863
|
+
var LINK_GROUP = "link";
|
|
864
|
+
var LINK_POSTURES = ["default", "inverse"];
|
|
865
|
+
var LINK_STATES = ["enabled", "hover", "pressed"];
|
|
866
|
+
var linkPath = (posture, state) => [LINK_GROUP, posture, state];
|
|
867
|
+
var ABSOLUTE_GROUP = "absolute";
|
|
868
|
+
var absolutePath = (family) => [ABSOLUTE_GROUP, family];
|
|
869
|
+
var COLOR_GROUP = "color";
|
|
932
870
|
var TOKEN_ORDER = [
|
|
933
871
|
...Object.values(SHARED_NAMES),
|
|
934
872
|
STAMP_FILL,
|
|
935
873
|
STAMP_FILL_HOVER,
|
|
936
874
|
STAMP_FILL_PRESSED,
|
|
937
|
-
STAMP_ON
|
|
938
|
-
"identity"
|
|
875
|
+
STAMP_ON
|
|
939
876
|
];
|
|
940
877
|
function tokenOrder(name) {
|
|
941
878
|
const i = TOKEN_ORDER.indexOf(name);
|
|
@@ -2864,8 +2801,34 @@ var COLOR_WORD = {
|
|
|
2864
2801
|
[FAMILY.positive]: " (green)",
|
|
2865
2802
|
[FAMILY.info]: " (blue)"
|
|
2866
2803
|
};
|
|
2867
|
-
var
|
|
2868
|
-
var
|
|
2804
|
+
var COLLIDES = "shifts to avoid similar colors";
|
|
2805
|
+
var GUARANTEE_SCOPE = {
|
|
2806
|
+
highlighter: { level: AA_LARGE, grounds: ["paper"] },
|
|
2807
|
+
pencil: { level: AA_BODY, grounds: ["paper"] },
|
|
2808
|
+
pen: { level: AA_BODY, grounds: ["paper", "chalk"] }
|
|
2809
|
+
};
|
|
2810
|
+
var BAND_STOPS = {
|
|
2811
|
+
highlighter: ["highlighter-26"],
|
|
2812
|
+
pencil: ["pencil-47"],
|
|
2813
|
+
pen: ["pen-58", "pen-70"]
|
|
2814
|
+
};
|
|
2815
|
+
var list = (words) => words.length > 1 ? `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}` : words[0];
|
|
2816
|
+
var ofFamilies = (fam) => fam === FAMILY.neutral ? "of every family" : "of this family and of the neutral";
|
|
2817
|
+
var fromFamilies = (fam) => fam === FAMILY.neutral ? "from every family" : "from this family and from the neutral";
|
|
2818
|
+
var claimOf = (band) => (fam) => `${GUARANTEE_SCOPE[band].level} on every ${GUARANTEE_SCOPE[band].grounds.join(" and ")} ${ofFamilies(fam)}`;
|
|
2819
|
+
var groundLine = (ground) => (fam) => {
|
|
2820
|
+
const bands = Object.keys(GUARANTEE_SCOPE).filter((b) => GUARANTEE_SCOPE[b].grounds.includes(ground));
|
|
2821
|
+
const byLevel = /* @__PURE__ */ new Map();
|
|
2822
|
+
for (const b of bands) {
|
|
2823
|
+
const stops = [...BAND_STOPS[b], ...b === "pen" && fam === FAMILY.neutral ? ["pen-100"] : []];
|
|
2824
|
+
byLevel.set(GUARANTEE_SCOPE[b].level, [...byLevel.get(GUARANTEE_SCOPE[b].level) ?? [], ...stops]);
|
|
2825
|
+
}
|
|
2826
|
+
const clears = [...byLevel.entries()].map(([level, stops]) => `${list(stops)} clear${stops.length === 1 ? "s" : ""} it as ${level}`).join("; ");
|
|
2827
|
+
const notHeld = Object.keys(GUARANTEE_SCOPE).filter((b) => !bands.includes(b));
|
|
2828
|
+
return `${clears}, ${fromFamilies(fam)}${notHeld.length ? `; ${list(notHeld.map((b) => `the ${b}`))} ${notHeld.length === 1 ? "is" : "are"} not held against it` : ""}`;
|
|
2829
|
+
};
|
|
2830
|
+
var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true, ground: groundLine("paper") };
|
|
2831
|
+
var CHALK = { req: "decorative borders, inverted text, illos, signal hierarchy", theming: (f) => TINT[f], collides: true, ground: groundLine("chalk") };
|
|
2869
2832
|
var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
|
|
2870
2833
|
var OVERLAY = {
|
|
2871
2834
|
req: "translucent backgrounds that hold their reading on any paper",
|
|
@@ -2877,106 +2840,123 @@ var SCALE = {
|
|
|
2877
2840
|
"paper-99-overlay": OVERLAY,
|
|
2878
2841
|
"paper-3": PAPER,
|
|
2879
2842
|
"paper-97-overlay": OVERLAY,
|
|
2880
|
-
"paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
|
|
2843
|
+
"paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true, ground: (f) => `${groundLine("paper")(f)}; the ground the text stops are solved against` },
|
|
2881
2844
|
"paper-95-overlay": OVERLAY,
|
|
2882
2845
|
"chalk-8": CHALK,
|
|
2883
2846
|
"chalk-11": CHALK,
|
|
2884
2847
|
"chalk-15": CHALK,
|
|
2885
2848
|
"chalk-20": CHALK,
|
|
2886
|
-
// ("icons"
|
|
2887
|
-
//
|
|
2888
|
-
"highlighter-26": { req: "focus rings, icons, large text, translucent state layers over any ground", contrast: AA_LARGE, theming: solved, collides: true },
|
|
2889
|
-
"pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2890
|
-
"pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2891
|
-
// ("
|
|
2892
|
-
//
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
//
|
|
2896
|
-
//
|
|
2897
|
-
//
|
|
2898
|
-
"stamp/fill": {
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
"stamp/
|
|
2849
|
+
// ("icons" stays although it carries "on": an "icon" query landing on the highlighter
|
|
2850
|
+
// rows is worth that noise)
|
|
2851
|
+
"highlighter-26": { req: "focus rings, icons, large text, translucent state layers over any ground", contrast: AA_LARGE, claim: claimOf("highlighter"), theming: solved, collides: true },
|
|
2852
|
+
"pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, claim: claimOf("pencil"), theming: solved, collides: true },
|
|
2853
|
+
"pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, claim: claimOf("pen"), theming: solved, collides: true },
|
|
2854
|
+
// ("heavy-emphasis": "high" and "low" are plane words in the community plugin and
|
|
2855
|
+
// "strong" carries "on")
|
|
2856
|
+
"pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, claim: claimOf("pen"), theming: solved, collides: true },
|
|
2857
|
+
// the stamp family. "CTA" stays in these bodies on purpose: it is not a token label, so
|
|
2858
|
+
// it floods nothing, and a designer's "cta" query lands on these rows.
|
|
2859
|
+
// the usage lines are the owner's guidance, for the document rendering: the fill is
|
|
2860
|
+
// misused as a standalone color in the wild
|
|
2861
|
+
"stamp/fill": {
|
|
2862
|
+
req: "CTAs",
|
|
2863
|
+
theming: (f) => `fully re-solved per theme and family${COLOR_WORD[f]}`,
|
|
2864
|
+
use: "primary button fills; optional for avatars, badges and other elements that carry text. Not a standalone color: it has no contrast guarantee of its own, only its on-text has one. Use it alone or with its hover and pressed states. Always bordered with its edge"
|
|
2865
|
+
},
|
|
2866
|
+
"stamp/fill-hover": { req: "CTA pointer-over state", theming: (f) => `follows its rest fill${COLOR_WORD[f]}`, use: "the pointer-over state of the fill, with the fill; for nothing else. Always bordered with the edge" },
|
|
2867
|
+
"stamp/fill-pressed": { req: "CTA pressed state", theming: (f) => `follows its rest fill${COLOR_WORD[f]}`, use: "the pressed state of the fill, with the fill; for nothing else. Always bordered with the edge" },
|
|
2868
|
+
"stamp/edge": { req: "min APCA visibility", theming: (f) => `draws for CTAs that sit close to the page; strength per family tier${COLOR_WORD[f]}`, use: "the border of the fill and its states, always rendered with them and never without them (it resolves to transparent unless the fill sits close to the page); for nothing else" },
|
|
2869
|
+
// ("fill" is a label word and foreign here)
|
|
2870
|
+
"stamp/on": { req: "text over the CTA color", contrast: `${AA_BODY} over its CTA`, theming: (f) => `whichever pole passes; quiet CTAs take the soft pole${COLOR_WORD[f]}`, use: "the only text color over the fill; never elsewhere" }
|
|
2871
|
+
};
|
|
2872
|
+
var NEUTRAL_ONLY = {
|
|
2873
|
+
// the pole is measured against the neutral's own stops only; as text, it is the on-color
|
|
2874
|
+
// of every family's emphasis fill (the band audit holds that)
|
|
2875
|
+
"paper-0": { req: "backgrounds, inverted text", theming: (f) => TINT[f], ground: () => `the neutral's highlighter-26 clears it as ${AA_LARGE}; its pencil-47, pen-58, pen-70 and pen-100 clear it as ${AA_BODY}; as text, ${AA_BODY} on every family's pencil-47` },
|
|
2876
|
+
// the literal pole carries no tint, so no theming line
|
|
2877
|
+
"pen-100": { req: "max-emphasis text", contrast: AA_BODY, claim: () => `${AA_BODY} on every paper and chalk of every family` }
|
|
2904
2878
|
};
|
|
2905
|
-
var ABS = { req: "extreme poles for CTAs, aliased global endpoints" };
|
|
2906
|
-
var OFFSET = { req: "min APCA visibility", theming: "offsets CTAs in themes where they sit close to the page" };
|
|
2907
|
-
var OFFSET_INVERSE = { req: "state layers over inverted backgrounds", theming: "the offset ladder with its pole flipped, so inverted grounds keep the same rungs" };
|
|
2908
|
-
var SHADOW = { req: "drop shadows" };
|
|
2909
|
-
var OPACITY_RUNG = { req: "translucency weight for any color", theming: "a bare number; a translucent row composes its color with one rung" };
|
|
2910
|
-
var OPACITY_STATE = { req: "translucency weight for any color; the state layer weight of the non-text stop over any ground", theming: "a bare number; a translucent row composes its color with one rung" };
|
|
2911
|
-
var PLANE = (req) => ({ req, theming: "aliased to the gray ramp" });
|
|
2912
2879
|
var LINK = (state, contrast) => ({
|
|
2913
2880
|
req: "links" + state,
|
|
2914
2881
|
contrast,
|
|
2915
|
-
|
|
2882
|
+
claim: () => `${contrast} on every paper of the neutral`,
|
|
2883
|
+
theming: "rides the theme\u2019s link color; custom seed re-solves; overridable per theme",
|
|
2884
|
+
use: "hyperlinks; not a text-style CTA, which is the text stops read as states"
|
|
2916
2885
|
});
|
|
2917
2886
|
var LINK_INVERSE = (state, contrast) => ({
|
|
2918
2887
|
req: "links over inverted backgrounds" + state,
|
|
2919
2888
|
contrast,
|
|
2920
|
-
|
|
2889
|
+
claim: () => `${contrast} on any family's pen-70`,
|
|
2890
|
+
theming: "same seed as the link, re-solved for inverted backgrounds; overridable per theme",
|
|
2891
|
+
use: "hyperlinks on a pen-70 ground; not a text-style CTA"
|
|
2921
2892
|
});
|
|
2922
2893
|
var SYSTEM = {
|
|
2923
|
-
"
|
|
2924
|
-
"
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
"
|
|
2929
|
-
|
|
2930
|
-
"
|
|
2931
|
-
"system/alpha/transparent": { req: "aliased off-states" },
|
|
2932
|
-
// the OPACITY LADDER: bare numbers a translucent color row composes with (the
|
|
2933
|
-
// scrim is the absolute black at the top rung). No conformance line of its own:
|
|
2934
|
-
// the state-layer claim rides the non-text stop these weights apply to
|
|
2935
|
-
"system/opacity/004": OPACITY_RUNG,
|
|
2936
|
-
"system/opacity/008": OPACITY_STATE,
|
|
2937
|
-
"system/opacity/012": OPACITY_STATE,
|
|
2938
|
-
"system/opacity/016": OPACITY_STATE,
|
|
2939
|
-
"system/opacity/024": OPACITY_STATE,
|
|
2940
|
-
"system/opacity/032": OPACITY_STATE,
|
|
2941
|
-
"system/opacity/048": OPACITY_RUNG,
|
|
2942
|
-
"system/opacity/064": OPACITY_RUNG,
|
|
2943
|
-
// ("fills" reworded 2026-08-18: fill became a label word and is foreign here;
|
|
2944
|
-
// "on-color" reworded 2026-08-28: the bare on fed the "on" flood)
|
|
2945
|
-
"system/alpha/ink": { req: "soft text pole for quiet CTAs" },
|
|
2946
|
-
"system/alpha/away-from-bg/06": OFFSET,
|
|
2947
|
-
"system/alpha/away-from-bg/08": OFFSET,
|
|
2948
|
-
"system/alpha/away-from-bg/16": OFFSET,
|
|
2949
|
-
// the inverse ladder: the offset rungs with the pole flipped per mode, for
|
|
2950
|
-
// chalks and edges over inverted backgrounds ("interaction" avoided in the
|
|
2951
|
-
// bodies: acti-ON would feed the "on" flood)
|
|
2952
|
-
"system/alpha/toward-bg/06": OFFSET_INVERSE,
|
|
2953
|
-
"system/alpha/toward-bg/08": OFFSET_INVERSE,
|
|
2954
|
-
"system/alpha/toward-bg/16": OFFSET_INVERSE,
|
|
2955
|
-
"system/alpha/shadow-04": SHADOW,
|
|
2956
|
-
"system/alpha/shadow-08": SHADOW,
|
|
2957
|
-
"system/alpha/shadow-12": SHADOW,
|
|
2958
|
-
// ("elevation" dropped 2026-08-28: elevati-ON fed the "on" flood; the sibling
|
|
2959
|
-
// planes never carried the word)
|
|
2960
|
-
"system/surface/dim": PLANE("recessed plane"),
|
|
2961
|
-
"system/surface/low": PLANE("resting page plane"),
|
|
2962
|
-
"system/surface/mid": PLANE("raised plane \u2014 cards, menus"),
|
|
2963
|
-
// ("overlays" reworded 2026-08-13: paper-overlay took the word as a label — the C50 law)
|
|
2964
|
-
"system/surface/high": PLANE("topmost plane \u2014 modals, dialogs"),
|
|
2965
|
-
"system/link/default/enabled": LINK("", AA_BODY),
|
|
2966
|
-
"system/link/default/hover": LINK(" pointer-over", AA_BODY),
|
|
2967
|
-
"system/link/default/pressed": LINK(" pressed", AA_BODY),
|
|
2968
|
-
"system/link/inverse/enabled": LINK_INVERSE("", AA_BODY),
|
|
2969
|
-
"system/link/inverse/hover": LINK_INVERSE(" pointer-over", AA_BODY),
|
|
2970
|
-
"system/link/inverse/pressed": LINK_INVERSE(" pressed", AA_BODY)
|
|
2894
|
+
"absolute/brand": { req: "identity seed reference", theming: "the theme\u2019s own input, as given", use: "a reference value (logos, swatches of the input); never a UI color, it carries no contrast guarantee" },
|
|
2895
|
+
"absolute/brand-alt": { req: "identity seed reference", theming: "the theme\u2019s paired input, as given", use: "a reference value (logos, swatches of the input); never a UI color, it carries no contrast guarantee" },
|
|
2896
|
+
"link/default/enabled": LINK("", AA_BODY),
|
|
2897
|
+
"link/default/hover": LINK(" pointer-over", AA_BODY),
|
|
2898
|
+
"link/default/pressed": LINK(" pressed", AA_BODY),
|
|
2899
|
+
"link/inverse/enabled": LINK_INVERSE("", AA_BODY),
|
|
2900
|
+
"link/inverse/hover": LINK_INVERSE(" pointer-over", AA_BODY),
|
|
2901
|
+
"link/inverse/pressed": LINK_INVERSE(" pressed", AA_BODY)
|
|
2971
2902
|
};
|
|
2972
2903
|
var PREFIXES = [
|
|
2973
|
-
// community spellings
|
|
2974
|
-
//
|
|
2975
|
-
// 'brand/alt/'), so most-specific must be tried before the family words
|
|
2904
|
+
// community spellings first: the family word is a prefix of the community shapes
|
|
2905
|
+
// ('brand/' would shadow 'brand/primary/' and 'brand/alt/'), so most-specific wins
|
|
2976
2906
|
["brand/primary/", FAMILY.brandPrimary],
|
|
2977
2907
|
["brand/alt/", FAMILY.brandSecondary],
|
|
2978
2908
|
...FAMILIES.map((f) => [f + "/", f])
|
|
2979
2909
|
];
|
|
2910
|
+
var ZONE_MAP = [
|
|
2911
|
+
// brand-alt before brand: canonicalize is first-match startsWith, and brand is a prefix
|
|
2912
|
+
// of brand-alt
|
|
2913
|
+
["system/abs-alt", "absolute/brand-alt"],
|
|
2914
|
+
["system/abs-primary", "absolute/brand"],
|
|
2915
|
+
["system/link/", "link/"],
|
|
2916
|
+
["color/", ""],
|
|
2917
|
+
["base/", ""],
|
|
2918
|
+
["primitive/", ""]
|
|
2919
|
+
];
|
|
2920
|
+
function canonicalize(path) {
|
|
2921
|
+
for (const [prefix, home] of ZONE_MAP)
|
|
2922
|
+
if (path.startsWith(prefix)) return home + path.slice(prefix.length);
|
|
2923
|
+
return path;
|
|
2924
|
+
}
|
|
2925
|
+
function bodyFor(path) {
|
|
2926
|
+
const canonical = canonicalize(path);
|
|
2927
|
+
if (SYSTEM[canonical]) return { body: SYSTEM[canonical], fam: FAMILY.neutral };
|
|
2928
|
+
const hit = PREFIXES.find(([p]) => canonical.startsWith(p));
|
|
2929
|
+
if (!hit) return void 0;
|
|
2930
|
+
const [prefix, fam] = hit;
|
|
2931
|
+
const leaf = canonical.slice(prefix.length);
|
|
2932
|
+
const body = fam === FAMILY.neutral && NEUTRAL_ONLY[leaf] || SCALE[leaf];
|
|
2933
|
+
return body ? { body, fam } : void 0;
|
|
2934
|
+
}
|
|
2935
|
+
function describeToken(path) {
|
|
2936
|
+
const canonical = canonicalize(path);
|
|
2937
|
+
const title = canonical.replace(/[/-]/g, " ");
|
|
2938
|
+
const hit = bodyFor(canonical);
|
|
2939
|
+
if (!hit) return title;
|
|
2940
|
+
const { body, fam } = hit;
|
|
2941
|
+
const lines = [title, `Req for: ${body.req}`];
|
|
2942
|
+
if (body.contrast) lines.push(body.contrast);
|
|
2943
|
+
if (body.theming) lines.push(`Theming: ${typeof body.theming === "function" ? body.theming(fam) : body.theming}`);
|
|
2944
|
+
if (body.collides && SIGNALS.includes(fam)) lines.push(COLLIDES);
|
|
2945
|
+
return lines.join("\n");
|
|
2946
|
+
}
|
|
2947
|
+
function describeDocument(path) {
|
|
2948
|
+
const hit = bodyFor(canonicalize(path));
|
|
2949
|
+
if (!hit) return "";
|
|
2950
|
+
const { body, fam } = hit;
|
|
2951
|
+
const lines = [`Req for: ${body.req}`];
|
|
2952
|
+
if (body.claim) lines.push(body.claim(fam));
|
|
2953
|
+
else if (body.contrast) lines.push(body.contrast);
|
|
2954
|
+
if (body.ground) lines.push(`Ground: ${body.ground(fam)}`);
|
|
2955
|
+
if (body.use) lines.push(`Use: ${body.use}`);
|
|
2956
|
+
if (body.theming) lines.push(`Theming: ${typeof body.theming === "function" ? body.theming(fam) : body.theming}`);
|
|
2957
|
+
if (body.collides && SIGNALS.includes(fam)) lines.push(COLLIDES);
|
|
2958
|
+
return lines.join("\n");
|
|
2959
|
+
}
|
|
2980
2960
|
|
|
2981
2961
|
// src/engine/signals.ts
|
|
2982
2962
|
var SIGNALS2 = [
|
|
@@ -3295,7 +3275,7 @@ var OUTLINE_PRESSED_ALPHA = 0.18;
|
|
|
3295
3275
|
var SOFT_ON_CTA_ALPHA = { light: 0.75, dark: 0.8 };
|
|
3296
3276
|
function softOnCtaPasses(s, mode) {
|
|
3297
3277
|
const white = mode === "light" ? s.onFillTextIsWhite : s.onFillTextIsWhiteDark;
|
|
3298
|
-
const
|
|
3278
|
+
const pole2 = white ? 1 : 0;
|
|
3299
3279
|
const a = SOFT_ON_CTA_ALPHA[mode];
|
|
3300
3280
|
const states = mode === "light" ? [s.cta, s.ctaHover, s.ctaPressed] : [s.ctaDark, s.ctaHoverDark, s.ctaPressedDark];
|
|
3301
3281
|
const lin = (v2) => v2 <= 0.04045 ? v2 / 12.92 : ((v2 + 0.055) / 1.055) ** 2.4;
|
|
@@ -3305,7 +3285,7 @@ function softOnCtaPasses(s, mode) {
|
|
|
3305
3285
|
const e = srgbEmitChannels(st);
|
|
3306
3286
|
const fr = q2(e.r), fg = q2(e.g), fb = q2(e.b);
|
|
3307
3287
|
const fillY = relY(fr, fg, fb);
|
|
3308
|
-
const textY = relY(
|
|
3288
|
+
const textY = relY(pole2 * a + fr * (1 - a), pole2 * a + fg * (1 - a), pole2 * a + fb * (1 - a));
|
|
3309
3289
|
if (contrastRatio(textY, fillY) < 4.5) return false;
|
|
3310
3290
|
}
|
|
3311
3291
|
return true;
|
|
@@ -3551,7 +3531,7 @@ var p3Differs = (s) => s.C > clampChromaToGamut(s.L, s.C, s.H, "srgb") + 1e-4;
|
|
|
3551
3531
|
var P3_SUPPORTS = "@supports (color: color(display-p3 1 1 1))";
|
|
3552
3532
|
var P3_MEDIA = "@media (color-gamut: p3)";
|
|
3553
3533
|
function stopsToVars(stops, prefix) {
|
|
3554
|
-
return [...stops].sort((a, b) => tokenOrder(stopTokenName(a.stop)) - tokenOrder(stopTokenName(b.stop))).map((s) => `
|
|
3534
|
+
return [...stops].sort((a, b) => tokenOrder(stopTokenName(a.stop)) - tokenOrder(stopTokenName(b.stop))).map((s) => ` ${cssVarName(familyPath(prefix, stopTokenName(s.stop)))}: ${stopHex(s)};`).join("\n");
|
|
3555
3535
|
}
|
|
3556
3536
|
var onColor = (white) => white ? "#ffffff" : "#000000";
|
|
3557
3537
|
var CTA_BORDER_LC_FLOOR = 15;
|
|
@@ -3562,34 +3542,7 @@ function pageStopFor(neutral, mode) {
|
|
|
3562
3542
|
}
|
|
3563
3543
|
var OFFSET_ALPHAS = { 6: 0.06, 8: 0.08, 16: 0.16 };
|
|
3564
3544
|
var ctaBorderRung = (prefix) => prefix === CSS_NEUTRAL ? 8 : prefix === CSS_SECONDARY ? 6 : 16;
|
|
3565
|
-
var offsetLeafName = (rung) => String(rung).padStart(2, "0");
|
|
3566
|
-
var offsetVarName = (rung) => `--alpha-away-from-bg-${offsetLeafName(rung)}`;
|
|
3567
|
-
var OPACITY_RUNGS = { 4: 0.04, 8: 0.08, 12: 0.12, 16: 0.16, 24: 0.24, 32: 0.32, 48: 0.48, 64: 0.64 };
|
|
3568
|
-
var INTERACTION_RUNGS = [8, 12, 16, 24, 32];
|
|
3569
|
-
var opacityLeafName = (rung) => String(rung).padStart(3, "0");
|
|
3570
|
-
var opacityVarName = (rung) => `--opacity-${opacityLeafName(rung)}`;
|
|
3571
|
-
var opacityTokenPath = (rung) => SYSTEM_LEAF.OPACITY[opacityLeafName(rung)];
|
|
3572
|
-
var SHADOW_ALPHAS = {
|
|
3573
|
-
4: { light: OPACITY_RUNGS[4], dark: OPACITY_RUNGS[32] },
|
|
3574
|
-
8: { light: OPACITY_RUNGS[8], dark: OPACITY_RUNGS[48] },
|
|
3575
|
-
12: { light: OPACITY_RUNGS[12], dark: OPACITY_RUNGS[64] }
|
|
3576
|
-
};
|
|
3577
|
-
var SCRIM_ALPHA = OPACITY_RUNGS[64];
|
|
3578
|
-
var DISABLED_OPACITY = 0.38;
|
|
3579
|
-
var TRANSPARENT_VAR = "--alpha-transparent";
|
|
3580
3545
|
var offsetRgba = (rung, mode) => mode === "light" ? `rgba(0, 0, 0, ${OFFSET_ALPHAS[rung]})` : `rgba(255, 255, 255, ${OFFSET_ALPHAS[rung]})`;
|
|
3581
|
-
var alphaRootVars = (mode) => [
|
|
3582
|
-
` ${TRANSPARENT_VAR}: transparent;`,
|
|
3583
|
-
// the opacity ladder, mode-invariant like transparent; repeated in the dark block to
|
|
3584
|
-
// keep the set together
|
|
3585
|
-
...Object.keys(OPACITY_RUNGS).map(Number).map((r) => ` ${opacityVarName(r)}: ${OPACITY_RUNGS[r]};`),
|
|
3586
|
-
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`),
|
|
3587
|
-
// the toward-bg ladder (the 2026-08-29 inverse ladder, renamed 2026-08-31): the
|
|
3588
|
-
// same rungs, pole flipped per mode — state layers for INVERTED grounds (never
|
|
3589
|
-
// called a "chalk" — that word is the tinted band's). One spelling with the Figma
|
|
3590
|
-
// row: --alpha-toward-bg-06 ↔ system/alpha/toward-bg/06.
|
|
3591
|
-
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` --alpha-toward-bg-${offsetLeafName(r)}: ${offsetRgba(r, mode === "light" ? "dark" : "light")};`)
|
|
3592
|
-
];
|
|
3593
3546
|
function ctaPageLc(s, mode, page) {
|
|
3594
3547
|
const cta = mode === "light" ? s.cta : s.ctaDark;
|
|
3595
3548
|
return Math.abs(apcaLc(apcaY(cta.r, cta.g, cta.b), apcaY(page.r, page.g, page.b)));
|
|
@@ -3604,23 +3557,20 @@ function brandKindBody(prefix, s, mode, page, on = prefix === CSS_NEUTRAL ? "sof
|
|
|
3604
3557
|
const onCta = mode === "light" ? s.onFillTextIsWhite : s.onFillTextIsWhiteDark;
|
|
3605
3558
|
const border = ctaNeedsBorder(s, mode, page);
|
|
3606
3559
|
const scaleBlock = [
|
|
3607
|
-
...stops.map((x2) => ({ name: stopTokenName(x2.stop), line: `
|
|
3560
|
+
...stops.map((x2) => ({ name: stopTokenName(x2.stop), line: ` ${cssVarName(familyPath(prefix, stopTokenName(x2.stop)))}: ${stopHex(x2)};` }))
|
|
3608
3561
|
].sort((a, b) => tokenOrder(a.name) - tokenOrder(b.name)).map((v2) => v2.line).join("\n");
|
|
3609
3562
|
return [
|
|
3610
3563
|
scaleBlock,
|
|
3611
|
-
`
|
|
3612
|
-
`
|
|
3613
|
-
`
|
|
3614
|
-
`
|
|
3615
|
-
// the
|
|
3616
|
-
//
|
|
3617
|
-
//
|
|
3618
|
-
//
|
|
3619
|
-
//
|
|
3620
|
-
|
|
3621
|
-
// the caller's per-mode verdict. Loud fills (brand, signals, the cta escape) keep the
|
|
3622
|
-
// solid pole.
|
|
3623
|
-
on === "soft" ? ` --${prefix}-stamp-on: rgba(${onCta ? "255, 255, 255" : "0, 0, 0"}, ${SOFT_ON_CTA_ALPHA[mode]});` : ` --${prefix}-stamp-on: ${onColor(onCta)};`
|
|
3564
|
+
` ${cssVarName(familyPath(prefix, STAMP_FILL))}: ${stopHex(f.cta)};`,
|
|
3565
|
+
` ${cssVarName(familyPath(prefix, STAMP_FILL_HOVER))}: ${stopHex(f.ctaHover)};`,
|
|
3566
|
+
` ${cssVarName(familyPath(prefix, STAMP_FILL_PRESSED))}: ${stopHex(f.ctaPressed)};`,
|
|
3567
|
+
` ${cssVarName(familyPath(prefix, STAMP_EDGE))}: ${border ? offsetRgba(ctaBorderRung(prefix), mode) : "transparent"};`,
|
|
3568
|
+
// the soft on-text: a quiet fill's button text is the pole at alpha, composited over
|
|
3569
|
+
// whatever state the fill is in so hover and pressed carry their own legibility (C47).
|
|
3570
|
+
// The neutral's is unconditional (the parameter default); the secondary's is the
|
|
3571
|
+
// caller's per-mode verdict. Loud fills (the brand, the signals, the cta escape) keep
|
|
3572
|
+
// the solid pole.
|
|
3573
|
+
on === "soft" ? ` ${cssVarName(familyPath(prefix, STAMP_ON))}: rgba(${onCta ? "255, 255, 255" : "0, 0, 0"}, ${SOFT_ON_CTA_ALPHA[mode]});` : ` ${cssVarName(familyPath(prefix, STAMP_ON))}: ${onColor(onCta)};`
|
|
3624
3574
|
];
|
|
3625
3575
|
}
|
|
3626
3576
|
function ctaFamilyOf(s, mode) {
|
|
@@ -3630,9 +3580,9 @@ function brandKindP3Body(prefix, s, mode) {
|
|
|
3630
3580
|
const stops = mode === "light" ? s.light : s.dark;
|
|
3631
3581
|
const f = ctaFamilyOf(s, mode);
|
|
3632
3582
|
const out = [];
|
|
3633
|
-
for (const st of stops) if (p3Differs(st)) out.push(`
|
|
3634
|
-
for (const [name, st] of [[
|
|
3635
|
-
if (p3Differs(st)) out.push(`
|
|
3583
|
+
for (const st of stops) if (p3Differs(st)) out.push(` ${cssVarName(familyPath(prefix, stopTokenName(st.stop)))}: ${p3Value(st)};`);
|
|
3584
|
+
for (const [name, st] of [[STAMP_FILL, f.cta], [STAMP_FILL_HOVER, f.ctaHover], [STAMP_FILL_PRESSED, f.ctaPressed]])
|
|
3585
|
+
if (p3Differs(st)) out.push(` ${cssVarName(familyPath(prefix, name))}: ${p3Value(st)};`);
|
|
3636
3586
|
return out;
|
|
3637
3587
|
}
|
|
3638
3588
|
function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBorder = true) {
|
|
@@ -3643,16 +3593,14 @@ function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBor
|
|
|
3643
3593
|
const p3Dark = brandKindP3Body(CSS_NEUTRAL, s, "dark");
|
|
3644
3594
|
return [
|
|
3645
3595
|
`${selector} {`,
|
|
3646
|
-
`
|
|
3647
|
-
`
|
|
3648
|
-
// the neutral
|
|
3649
|
-
// the neutral's overlays never read the visibility bar (the exempt family), so
|
|
3650
|
-
// the brandless chrome context passes 0
|
|
3596
|
+
` ${cssVarName(familyPath(CSS_NEUTRAL, PAPER_0))}: ${p02(s.paper0, "#ffffff")};`,
|
|
3597
|
+
` ${cssVarName(familyPath(CSS_NEUTRAL, PEN_100))}: ${p02(s.pen100, "#000000")};`,
|
|
3598
|
+
// the neutral is the page, so it is judged against its own paper stop
|
|
3651
3599
|
...brandKindBody(CSS_NEUTRAL, s, "light", nPage("light")),
|
|
3652
3600
|
`}`,
|
|
3653
3601
|
`${selector}[data-theme="dark"] {`,
|
|
3654
|
-
`
|
|
3655
|
-
`
|
|
3602
|
+
` ${cssVarName(familyPath(CSS_NEUTRAL, PAPER_0))}: ${p02(s.paper0Dark, "#000000")};`,
|
|
3603
|
+
` ${cssVarName(familyPath(CSS_NEUTRAL, PEN_100))}: ${p02(s.pen100Dark, "#ffffff")};`,
|
|
3656
3604
|
...brandKindBody(CSS_NEUTRAL, s, "dark", nPage("dark")),
|
|
3657
3605
|
`}`,
|
|
3658
3606
|
...p3Light.length || p3Dark.length ? [
|
|
@@ -3684,16 +3632,9 @@ function signalsCss(contrastProfile) {
|
|
|
3684
3632
|
return [
|
|
3685
3633
|
`/* Signal scales \u2014 engine-generated from canonical hexes, shared across brands */`,
|
|
3686
3634
|
`:root {`,
|
|
3687
|
-
// the system alpha variables every family's cta-border aliases (owner 2026-07-29) — this is
|
|
3688
|
-
// the engine's one global :root, the CSS counterpart of the Figma side's system/alpha/* rows.
|
|
3689
|
-
// Emitted in BOTH blocks because the offset rungs are scheme-DIVERGENT (black in light,
|
|
3690
|
-
// white in dark); --alpha-transparent and the opacity ladder (mode-invariant) repeat
|
|
3691
|
-
// harmlessly and keep the set together.
|
|
3692
|
-
...alphaRootVars("light"),
|
|
3693
3635
|
...lightBlocks,
|
|
3694
3636
|
`}`,
|
|
3695
3637
|
`${SIGNALS_DARK_SELECTOR} {`,
|
|
3696
|
-
...alphaRootVars("dark"),
|
|
3697
3638
|
...darkBlocks,
|
|
3698
3639
|
`}`,
|
|
3699
3640
|
...p3LightBlocks.length || p3DarkBlocks.length ? [
|
|
@@ -3727,69 +3668,72 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
|
|
|
3727
3668
|
const page = ctaBorder ? { light: pageStopFor(nScale, "light"), dark: pageStopFor(nScale, "dark") } : { light: void 0, dark: void 0 };
|
|
3728
3669
|
const mirrorBody = (prefix, mode) => {
|
|
3729
3670
|
const stops = mode === "light" ? scale.light : scale.dark;
|
|
3730
|
-
const alias = (name) => `
|
|
3671
|
+
const alias = (name) => ` ${cssVarName(familyPath(prefix, name))}: var(${cssVarName(familyPath(CSS_BRAND, name))});`;
|
|
3731
3672
|
return [
|
|
3732
3673
|
...stops.map((x2) => alias(stopTokenName(x2.stop))),
|
|
3733
|
-
alias(
|
|
3734
|
-
alias(
|
|
3735
|
-
alias(
|
|
3736
|
-
alias(
|
|
3737
|
-
alias(
|
|
3674
|
+
alias(STAMP_FILL),
|
|
3675
|
+
alias(STAMP_FILL_HOVER),
|
|
3676
|
+
alias(STAMP_FILL_PRESSED),
|
|
3677
|
+
alias(STAMP_EDGE),
|
|
3678
|
+
alias(STAMP_ON)
|
|
3738
3679
|
];
|
|
3739
3680
|
};
|
|
3740
3681
|
const secondaryOn = (mode) => secondary && secondaryStyle !== "outline" && softOnCtaPasses(secondary, mode) ? "soft" : "solid";
|
|
3741
3682
|
const secondaryLight = secondary ? brandKindBody(CSS_SECONDARY, secondary, "light", page.light, secondaryOn("light")) : mirrorBody(CSS_SECONDARY, "light");
|
|
3742
3683
|
const secondaryDark = secondary ? brandKindBody(CSS_SECONDARY, secondary, "dark", page.dark, secondaryOn("dark")) : mirrorBody(CSS_SECONDARY, "dark");
|
|
3743
|
-
const brandIdentity = `
|
|
3744
|
-
const secondaryIdentity = secondary ? `
|
|
3684
|
+
const brandIdentity = ` ${cssVarName(absolutePath(CSS_BRAND))}: ${scale.identityHex};`;
|
|
3685
|
+
const secondaryIdentity = secondary ? ` ${cssVarName(absolutePath(CSS_SECONDARY))}: ${secondary.identityHex};` : ` ${cssVarName(absolutePath(CSS_SECONDARY))}: var(${cssVarName(absolutePath(CSS_BRAND))});`;
|
|
3745
3686
|
const p0hex = (s, fallback) => s ? stopHex(s) : fallback;
|
|
3746
|
-
const lightAnchors = [`
|
|
3747
|
-
const darkAnchors = [`
|
|
3687
|
+
const lightAnchors = [` ${cssVarName(familyPath(CSS_NEUTRAL, PAPER_0))}: ${p0hex(nScale.paper0, "#ffffff")};`, ` ${cssVarName(familyPath(CSS_NEUTRAL, PEN_100))}: ${p0hex(nScale.pen100, "#000000")};`];
|
|
3688
|
+
const darkAnchors = [` ${cssVarName(familyPath(CSS_NEUTRAL, PAPER_0))}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` ${cssVarName(familyPath(CSS_NEUTRAL, PEN_100))}: ${p0hex(nScale.pen100Dark, "#ffffff")};`];
|
|
3748
3689
|
const linkTrio = linkHex ? resolveLinkTrio(linkHex, contrastProfile) : null;
|
|
3749
|
-
const
|
|
3750
|
-
|
|
3751
|
-
`
|
|
3752
|
-
`
|
|
3690
|
+
const linkVar = (state) => cssVarName(linkPath("default", state));
|
|
3691
|
+
const link = (mode) => linkTrio ? mode === "light" ? [` ${linkVar("enabled")}: ${stopHex(linkTrio.link)};`, ` ${linkVar("hover")}: ${stopHex(linkTrio.linkHover)};`, ` ${linkVar("pressed")}: ${stopHex(linkTrio.linkPressed)};`] : [` ${linkVar("enabled")}: ${stopHex(linkTrio.linkDark)};`, ` ${linkVar("hover")}: ${stopHex(linkTrio.linkHoverDark)};`, ` ${linkVar("pressed")}: ${stopHex(linkTrio.linkPressedDark)};`] : [
|
|
3692
|
+
` ${linkVar("enabled")}: var(${cssVarName(familyPath(CSS_BRAND, stopTokenName(9)))});`,
|
|
3693
|
+
` ${linkVar("hover")}: var(${cssVarName(familyPath(CSS_BRAND, stopTokenName(10)))});`,
|
|
3694
|
+
` ${linkVar("pressed")}: var(${cssVarName(familyPath(CSS_BRAND, stopTokenName(11)))});`
|
|
3753
3695
|
];
|
|
3754
3696
|
const linkP3 = (mode) => {
|
|
3755
3697
|
if (!linkTrio) return [];
|
|
3756
|
-
const trio = mode === "light" ? [["
|
|
3757
|
-
return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => `
|
|
3698
|
+
const trio = mode === "light" ? [["enabled", linkTrio.link], ["hover", linkTrio.linkHover], ["pressed", linkTrio.linkPressed]] : [["enabled", linkTrio.linkDark], ["hover", linkTrio.linkHoverDark], ["pressed", linkTrio.linkPressedDark]];
|
|
3699
|
+
return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => ` ${linkVar(n)}: ${p3Value(s)};`);
|
|
3758
3700
|
};
|
|
3759
3701
|
const inverseSeed = linkHex ?? scale.identityHex ?? stopHex(scale.light.find((x2) => x2.stop === 9));
|
|
3760
3702
|
const linkInverseTrio = resolveLinkInverseTrio(inverseSeed, contrastProfile);
|
|
3761
|
-
const
|
|
3703
|
+
const inverseVar = (state) => cssVarName(linkPath("inverse", state));
|
|
3704
|
+
const linkInverse = (mode) => mode === "light" ? [` ${inverseVar("enabled")}: ${stopHex(linkInverseTrio.link)};`, ` ${inverseVar("hover")}: ${stopHex(linkInverseTrio.linkHover)};`, ` ${inverseVar("pressed")}: ${stopHex(linkInverseTrio.linkPressed)};`] : [` ${inverseVar("enabled")}: ${stopHex(linkInverseTrio.linkDark)};`, ` ${inverseVar("hover")}: ${stopHex(linkInverseTrio.linkHoverDark)};`, ` ${inverseVar("pressed")}: ${stopHex(linkInverseTrio.linkPressedDark)};`];
|
|
3762
3705
|
const linkInverseP3 = (mode) => {
|
|
3763
|
-
const trio = mode === "light" ? [["
|
|
3764
|
-
return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => `
|
|
3706
|
+
const trio = mode === "light" ? [["enabled", linkInverseTrio.link], ["hover", linkInverseTrio.linkHover], ["pressed", linkInverseTrio.linkPressed]] : [["enabled", linkInverseTrio.linkDark], ["hover", linkInverseTrio.linkHoverDark], ["pressed", linkInverseTrio.linkPressedDark]];
|
|
3707
|
+
return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => ` ${inverseVar(n)}: ${p3Value(s)};`);
|
|
3765
3708
|
};
|
|
3766
3709
|
const escape = (mode) => {
|
|
3767
3710
|
if (!ctaEscape) return [];
|
|
3768
3711
|
const esc = escapeCtaFamily(nScale, mode, contrastProfile);
|
|
3769
3712
|
return [
|
|
3770
|
-
`
|
|
3771
|
-
`
|
|
3772
|
-
`
|
|
3773
|
-
`
|
|
3713
|
+
` ${cssVarName(familyPath(CSS_BRAND, STAMP_FILL))}: ${stopHex(esc.cta)};`,
|
|
3714
|
+
` ${cssVarName(familyPath(CSS_BRAND, STAMP_FILL_HOVER))}: ${stopHex(esc.ctaHover)};`,
|
|
3715
|
+
` ${cssVarName(familyPath(CSS_BRAND, STAMP_FILL_PRESSED))}: ${stopHex(esc.ctaPressed)};`,
|
|
3716
|
+
` ${cssVarName(familyPath(CSS_BRAND, STAMP_ON))}: ${onColor(esc.onFillIsWhite)};`
|
|
3774
3717
|
];
|
|
3775
3718
|
};
|
|
3776
3719
|
const outline = (mode) => {
|
|
3777
3720
|
if (secondaryStyle !== "outline" || !secondary) return [];
|
|
3778
3721
|
const s8 = (mode === "light" ? secondary.light : secondary.dark).find((s) => s.stop === 8);
|
|
3779
|
-
const c = (
|
|
3722
|
+
const c = (v3) => Math.round(Math.min(1, Math.max(0, v3)) * 255);
|
|
3780
3723
|
const s8e = s8 ? srgbEmitChannels(s8) : null;
|
|
3724
|
+
const v2 = (leaf) => cssVarName(familyPath(CSS_SECONDARY, leaf));
|
|
3781
3725
|
return [
|
|
3782
|
-
`
|
|
3726
|
+
` ${v2(STAMP_FILL)}: transparent;`,
|
|
3783
3727
|
...s8e ? [
|
|
3784
|
-
`
|
|
3785
|
-
`
|
|
3728
|
+
` ${v2(STAMP_FILL_HOVER)}: rgba(${c(s8e.r)}, ${c(s8e.g)}, ${c(s8e.b)}, ${OUTLINE_HOVER_ALPHA});`,
|
|
3729
|
+
` ${v2(STAMP_FILL_PRESSED)}: rgba(${c(s8e.r)}, ${c(s8e.g)}, ${c(s8e.b)}, ${OUTLINE_PRESSED_ALPHA});`
|
|
3786
3730
|
] : [],
|
|
3787
|
-
`
|
|
3788
|
-
`
|
|
3731
|
+
` ${v2(STAMP_EDGE)}: var(${v2(stopTokenName(8))});`,
|
|
3732
|
+
` ${v2(STAMP_ON)}: var(${v2(stopTokenName(9))});`
|
|
3789
3733
|
];
|
|
3790
3734
|
};
|
|
3791
|
-
const dropOutlineCta = (lines) => secondaryStyle === "outline" ? lines.filter((l) => !
|
|
3792
|
-
const dropEscapeCta = (lines) => ctaEscape ? lines.filter((l) => !
|
|
3735
|
+
const dropOutlineCta = (lines) => secondaryStyle === "outline" ? lines.filter((l) => ![STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED].some((leaf) => l.startsWith(` ${cssVarName(familyPath(CSS_SECONDARY, leaf))}:`))) : lines;
|
|
3736
|
+
const dropEscapeCta = (lines) => ctaEscape ? lines.filter((l) => ![STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED].some((leaf) => l.startsWith(` ${cssVarName(familyPath(CSS_BRAND, leaf))}:`))) : lines;
|
|
3793
3737
|
const p3Light = [
|
|
3794
3738
|
...dropEscapeCta(brandKindP3Body(CSS_BRAND, scale, "light")),
|
|
3795
3739
|
...secondary ? dropOutlineCta(brandKindP3Body(CSS_SECONDARY, secondary, "light")) : [],
|
|
@@ -3897,17 +3841,6 @@ function resolveReferences(own, shared) {
|
|
|
3897
3841
|
for (const name of /* @__PURE__ */ new Set([...own.keys(), ...shared.keys()])) out[name] = resolve(lookup(name), 0);
|
|
3898
3842
|
return out;
|
|
3899
3843
|
}
|
|
3900
|
-
function systemCss() {
|
|
3901
|
-
const planeRef = (stop) => stop === PAPER_0 ? `var(--${PAPER_0})` : `var(--${CSS_FAMILY.neutral}-${stop})`;
|
|
3902
|
-
const pad = (r) => String(r).padStart(2, "0");
|
|
3903
|
-
const lines = (mode) => [
|
|
3904
|
-
...Object.entries(SURFACE_PLANE_LAW).map(([leaf, stops]) => ` --surface-${leaf.split("/").pop()}: ${planeRef(stops[mode])};`),
|
|
3905
|
-
...Object.keys(SHADOW_ALPHAS).map(Number).map((r) => ` --shadow-${pad(r)}: rgba(0, 0, 0, ${SHADOW_ALPHAS[r][mode]});`),
|
|
3906
|
-
` --scrim: rgba(0, 0, 0, ${SCRIM_ALPHA});`,
|
|
3907
|
-
` --disabled-opacity: ${DISABLED_OPACITY};`
|
|
3908
|
-
];
|
|
3909
|
-
return [":root {", ...lines("light"), "}", `:root${DARK_MARK}, ${DARK_MARK} {`, ...lines("dark"), "}"].join("\n");
|
|
3910
|
-
}
|
|
3911
3844
|
function tokensFromEmission(slug, css) {
|
|
3912
3845
|
const { light, dark, order } = readEmission(css);
|
|
3913
3846
|
const resolvedLight = resolveReferences(light, /* @__PURE__ */ new Map());
|
|
@@ -3919,7 +3852,6 @@ function tokensFromEmission(slug, css) {
|
|
|
3919
3852
|
function themeTokens(input) {
|
|
3920
3853
|
const css = [
|
|
3921
3854
|
signalsCss(input.contrastProfile),
|
|
3922
|
-
systemCss(),
|
|
3923
3855
|
brandCss(
|
|
3924
3856
|
input.slug,
|
|
3925
3857
|
input.displayName ?? input.slug,
|
|
@@ -3938,168 +3870,35 @@ function themeTokens(input) {
|
|
|
3938
3870
|
return tokensFromEmission(input.slug, css);
|
|
3939
3871
|
}
|
|
3940
3872
|
|
|
3941
|
-
// src/engine/interaction.ts
|
|
3942
|
-
var INTERACTION_POLE_FAMILY = { strong: "neutral-strong", inverse: "neutral-inverse" };
|
|
3943
|
-
var INTERACTION_FAMILIES = [
|
|
3944
|
-
CSS_FAMILY.neutral,
|
|
3945
|
-
CSS_FAMILY.brandPrimary,
|
|
3946
|
-
CSS_FAMILY.brandSecondary,
|
|
3947
|
-
...SIGNALS2.map((s) => s.emitName),
|
|
3948
|
-
INTERACTION_POLE_FAMILY.strong,
|
|
3949
|
-
INTERACTION_POLE_FAMILY.inverse
|
|
3950
|
-
];
|
|
3951
|
-
var INTERACTION_STATES = ["enabled", "hover", "pressed", "selected"];
|
|
3952
|
-
var INTERACTION_LADDER = {
|
|
3953
|
-
subtle: { enabled: 12, hover: 16, pressed: 24, selected: 32 },
|
|
3954
|
-
hint: { enabled: null, hover: 8, pressed: 12, selected: 16 }
|
|
3955
|
-
};
|
|
3956
|
-
var INTERACTION_ROWS = [
|
|
3957
|
-
"fg",
|
|
3958
|
-
"fg-strong",
|
|
3959
|
-
"fg-on-hint",
|
|
3960
|
-
"solid-bg-enabled",
|
|
3961
|
-
"solid-bg-hover",
|
|
3962
|
-
"solid-bg-pressed",
|
|
3963
|
-
"solid-border",
|
|
3964
|
-
"solid-fg",
|
|
3965
|
-
"subtle-bg-enabled",
|
|
3966
|
-
"subtle-bg-hover",
|
|
3967
|
-
"subtle-bg-pressed",
|
|
3968
|
-
"subtle-bg-selected",
|
|
3969
|
-
"hint-bg-enabled",
|
|
3970
|
-
"hint-bg-hover",
|
|
3971
|
-
"hint-bg-pressed",
|
|
3972
|
-
"hint-bg-selected"
|
|
3973
|
-
];
|
|
3974
|
-
var interactionVarName = (family, row) => `--${family}-${row}`;
|
|
3975
|
-
var isPole = (family) => family === INTERACTION_POLE_FAMILY.strong || family === INTERACTION_POLE_FAMILY.inverse;
|
|
3976
|
-
var hexChannels = (hex) => {
|
|
3977
|
-
const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex.trim());
|
|
3978
|
-
return m ? [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)] : null;
|
|
3979
|
-
};
|
|
3980
|
-
function interactionTintName(family) {
|
|
3981
|
-
if (family === INTERACTION_POLE_FAMILY.strong) return PEN_100;
|
|
3982
|
-
if (family === INTERACTION_POLE_FAMILY.inverse) return PAPER_0;
|
|
3983
|
-
return `${family}-${stopTokenName(8)}`;
|
|
3984
|
-
}
|
|
3985
|
-
function interactionRows(family, mode, tokens) {
|
|
3986
|
-
const neutral = CSS_FAMILY.neutral;
|
|
3987
|
-
const v2 = (name) => `var(--${name})`;
|
|
3988
|
-
const pen = (stop) => `${family}-${stopTokenName(stop)}`;
|
|
3989
|
-
const tintHex = tokens[mode][interactionTintName(family)];
|
|
3990
|
-
const ch = tintHex ? hexChannels(tintHex) : null;
|
|
3991
|
-
if (!ch) throw new Error(`interactionRows: no sRGB hex for ${interactionTintName(family)} in ${mode}`);
|
|
3992
|
-
const at = (rung) => rung === null ? v2(TRANSPARENT_VAR.slice(2)) : `rgba(${ch[0]}, ${ch[1]}, ${ch[2]}, ${OPACITY_RUNGS[rung]})`;
|
|
3993
|
-
const text = isPole(family) ? [["fg", v2(interactionTintName(family))], ["fg-strong", v2(interactionTintName(family))], ["fg-on-hint", v2(interactionTintName(family))]] : [["fg", v2(pen(10))], ["fg-strong", v2(pen(11))], ["fg-on-hint", v2(pen(9))]];
|
|
3994
|
-
const solid = family === INTERACTION_POLE_FAMILY.strong ? [
|
|
3995
|
-
["solid-bg-enabled", v2(PEN_100)],
|
|
3996
|
-
["solid-bg-hover", v2(`${neutral}-${stopTokenName(11)}`)],
|
|
3997
|
-
["solid-bg-pressed", v2(`${neutral}-${stopTokenName(10)}`)],
|
|
3998
|
-
["solid-border", v2(TRANSPARENT_VAR.slice(2))],
|
|
3999
|
-
["solid-fg", v2(PAPER_0)]
|
|
4000
|
-
] : family === INTERACTION_POLE_FAMILY.inverse ? [
|
|
4001
|
-
["solid-bg-enabled", v2(PAPER_0)],
|
|
4002
|
-
["solid-bg-hover", v2(`${neutral}-${stopTokenName(2)}`)],
|
|
4003
|
-
["solid-bg-pressed", v2(`${neutral}-${stopTokenName(3)}`)],
|
|
4004
|
-
["solid-border", v2(TRANSPARENT_VAR.slice(2))],
|
|
4005
|
-
["solid-fg", v2(PEN_100)]
|
|
4006
|
-
] : [
|
|
4007
|
-
["solid-bg-enabled", v2(`${family}-${STAMP_FILL}`)],
|
|
4008
|
-
["solid-bg-hover", v2(`${family}-${STAMP_FILL_HOVER}`)],
|
|
4009
|
-
["solid-bg-pressed", v2(`${family}-${STAMP_FILL_PRESSED}`)],
|
|
4010
|
-
["solid-border", v2(`${family}-${STAMP_EDGE}`)],
|
|
4011
|
-
["solid-fg", v2(`${family}-${STAMP_ON}`)]
|
|
4012
|
-
];
|
|
4013
|
-
const ladder = (tier) => INTERACTION_STATES.map((state) => [`${tier}-bg-${state}`, at(INTERACTION_LADDER[tier][state])]);
|
|
4014
|
-
return [...text, ...solid, ...ladder("subtle"), ...ladder("hint")];
|
|
4015
|
-
}
|
|
4016
|
-
var rowVariesByMode = (row) => /^(subtle|hint)-bg-/.test(row) && row !== "hint-bg-enabled";
|
|
4017
|
-
function interactionCss(slug, tokens) {
|
|
4018
|
-
const scope = `[data-brand="${slug}"]`;
|
|
4019
|
-
const light = [];
|
|
4020
|
-
const dark = [];
|
|
4021
|
-
for (const family of INTERACTION_FAMILIES) {
|
|
4022
|
-
for (const [row, value] of interactionRows(family, "light", tokens)) light.push(` ${interactionVarName(family, row)}: ${value};`);
|
|
4023
|
-
for (const [row, value] of interactionRows(family, "dark", tokens)) if (rowVariesByMode(row)) dark.push(` ${interactionVarName(family, row)}: ${value};`);
|
|
4024
|
-
}
|
|
4025
|
-
const scoped = INTERACTION_FAMILIES.flatMap((family) => [
|
|
4026
|
-
`${scope} [data-family="${family}"], ${scope}[data-family="${family}"] {`,
|
|
4027
|
-
...INTERACTION_ROWS.map((row) => ` --${row}: var(${interactionVarName(family, row)});`),
|
|
4028
|
-
`}`
|
|
4029
|
-
]);
|
|
4030
|
-
return [
|
|
4031
|
-
`/* Interaction register: solid, subtle and hint tiers per family, the translucent rows at the engine's opacity rungs */`,
|
|
4032
|
-
`${scope} {`,
|
|
4033
|
-
...light,
|
|
4034
|
-
`}`,
|
|
4035
|
-
`${scope}[data-theme="dark"] {`,
|
|
4036
|
-
...dark,
|
|
4037
|
-
`}`,
|
|
4038
|
-
...scoped
|
|
4039
|
-
].join("\n");
|
|
4040
|
-
}
|
|
4041
|
-
function interactionTokens(tokens) {
|
|
4042
|
-
const add = (mode) => {
|
|
4043
|
-
const m = /* @__PURE__ */ new Map();
|
|
4044
|
-
for (const family of INTERACTION_FAMILIES)
|
|
4045
|
-
for (const [row, value] of interactionRows(family, mode, tokens)) m.set(interactionVarName(family, row).slice(2), value);
|
|
4046
|
-
return m;
|
|
4047
|
-
};
|
|
4048
|
-
const rawLight = add("light");
|
|
4049
|
-
const rawDark = add("dark");
|
|
4050
|
-
const baseLight = new Map(Object.entries(tokens.light));
|
|
4051
|
-
const baseDark = new Map(Object.entries(tokens.dark));
|
|
4052
|
-
const light = resolveReferences(rawLight, baseLight);
|
|
4053
|
-
const dark = resolveReferences(rawDark, baseDark);
|
|
4054
|
-
const names = [...tokens.names, ...rawLight.keys()];
|
|
4055
|
-
const invariant = [...tokens.invariant, ...[...rawLight.keys()].filter((n) => rawLight.get(n) === rawDark.get(n))];
|
|
4056
|
-
return {
|
|
4057
|
-
slug: tokens.slug,
|
|
4058
|
-
names,
|
|
4059
|
-
light: { ...tokens.light, ...Object.fromEntries([...rawLight.keys()].map((n) => [n, light[n]])) },
|
|
4060
|
-
dark: { ...tokens.dark, ...Object.fromEntries([...rawDark.keys()].map((n) => [n, dark[n]])) },
|
|
4061
|
-
invariant,
|
|
4062
|
-
raw: {
|
|
4063
|
-
light: { ...tokens.raw.light, ...Object.fromEntries(rawLight) },
|
|
4064
|
-
dark: { ...tokens.raw.dark, ...Object.fromEntries(rawDark) }
|
|
4065
|
-
}
|
|
4066
|
-
};
|
|
4067
|
-
}
|
|
4068
|
-
|
|
4069
3873
|
// src/engine/figmaRender.ts
|
|
4070
|
-
var
|
|
4071
|
-
|
|
4072
|
-
$type: "color",
|
|
4073
|
-
|
|
4074
|
-
|
|
3874
|
+
var channel = (v2) => Math.round(Math.round(Math.min(1, Math.max(0, v2)) * 255) / 255 * 1e4) / 1e4;
|
|
3875
|
+
function color(r, g2, b, alpha = 1) {
|
|
3876
|
+
return { $type: "color", $value: { colorSpace: "srgb", components: [channel(r), channel(g2), channel(b)], alpha, hex: toHex2(r, g2, b) } };
|
|
3877
|
+
}
|
|
3878
|
+
var TRANSPARENT_TOKEN = color(0, 0, 0, 0);
|
|
4075
3879
|
var OFFSET_TOKEN = (rung, mode) => {
|
|
4076
3880
|
const c = mode === "light" ? 0 : 1;
|
|
4077
|
-
return
|
|
4078
|
-
$type: "color",
|
|
4079
|
-
$value: { colorSpace: "srgb", components: [c, c, c], alpha: OFFSET_ALPHAS[rung], hex: mode === "light" ? "#000000" : "#ffffff" }
|
|
4080
|
-
};
|
|
3881
|
+
return color(c, c, c, OFFSET_ALPHAS[rung]);
|
|
4081
3882
|
};
|
|
4082
3883
|
function colorFromStop(s) {
|
|
4083
3884
|
const { r, g: g2, b } = srgbEmitChannels(s);
|
|
4084
|
-
return
|
|
4085
|
-
$type: "color",
|
|
4086
|
-
$value: { colorSpace: "srgb", components: [clamp01(r), clamp01(g2), clamp01(b)], alpha: 1, hex: toHex2(r, g2, b) }
|
|
4087
|
-
};
|
|
4088
|
-
}
|
|
4089
|
-
function colorFromHex(white) {
|
|
4090
|
-
const v2 = white ? 1 : 0;
|
|
4091
|
-
return { $type: "color", $value: { colorSpace: "srgb", components: [v2, v2, v2], alpha: 1, hex: white ? "#ffffff" : "#000000" } };
|
|
3885
|
+
return color(r, g2, b);
|
|
4092
3886
|
}
|
|
3887
|
+
var pole = (white, alpha = 1) => white ? color(1, 1, 1, alpha) : color(0, 0, 0, alpha);
|
|
4093
3888
|
function colorFromHexString(hex) {
|
|
4094
3889
|
const h = hex.replace("#", "");
|
|
4095
|
-
|
|
4096
|
-
const g2 = parseInt(h.slice(2, 4), 16) / 255;
|
|
4097
|
-
const b = parseInt(h.slice(4, 6), 16) / 255;
|
|
4098
|
-
return { $type: "color", $value: { colorSpace: "srgb", components: [r, g2, b], alpha: 1, hex: hex.toLowerCase() } };
|
|
3890
|
+
return color(parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255);
|
|
4099
3891
|
}
|
|
4100
3892
|
function bandedLeaf(flat) {
|
|
4101
3893
|
return STAMP_STATE_LEAVES[flat] ?? flat;
|
|
4102
3894
|
}
|
|
3895
|
+
function groupEntries(g2) {
|
|
3896
|
+
const entries = Object.entries(g2);
|
|
3897
|
+
const digitLeading = (k) => /^\d/.test(k);
|
|
3898
|
+
if (entries.length > 1 && entries.every(([k]) => digitLeading(k)))
|
|
3899
|
+
return entries.sort((a, b) => parseInt(b[0], 10) - parseInt(a[0], 10));
|
|
3900
|
+
return entries;
|
|
3901
|
+
}
|
|
4103
3902
|
function putLeaf(g2, flat, tok) {
|
|
4104
3903
|
const path = bandedLeaf(flat).split("/");
|
|
4105
3904
|
let cur = g2;
|
|
@@ -4117,8 +3916,7 @@ function rampGroup(stops, onFillWhite, extra) {
|
|
|
4117
3916
|
if (extra?.ctaHover) putLeaf(g2, STAMP_FILL_HOVER, colorFromStop(extra.ctaHover));
|
|
4118
3917
|
if (extra?.ctaPressed) putLeaf(g2, STAMP_FILL_PRESSED, colorFromStop(extra.ctaPressed));
|
|
4119
3918
|
if (extra?.cta) putLeaf(g2, STAMP_EDGE, extra.ctaBorder ?? TRANSPARENT_TOKEN);
|
|
4120
|
-
putLeaf(g2, STAMP_ON,
|
|
4121
|
-
if (extra?.identityHex) g2["identity"] = colorFromHexString(extra.identityHex);
|
|
3919
|
+
putLeaf(g2, STAMP_ON, pole(onFillWhite));
|
|
4122
3920
|
return g2;
|
|
4123
3921
|
}
|
|
4124
3922
|
function themeToFigma(r, input) {
|
|
@@ -4131,10 +3929,6 @@ function themeToFigma(r, input) {
|
|
|
4131
3929
|
ctaBorder: ctaNeedsBorder(s, mode, borderPage(mode)) ? OFFSET_TOKEN(ctaBorderRung(prefix), mode) : TRANSPARENT_TOKEN,
|
|
4132
3930
|
...mode === "light" ? { cta: s.cta, ctaHover: s.ctaHover, ctaPressed: s.ctaPressed } : { cta: s.ctaDark, ctaHover: s.ctaHoverDark, ctaPressed: s.ctaPressedDark }
|
|
4133
3931
|
});
|
|
4134
|
-
const brandExtra = (s, mode, prefix) => ({
|
|
4135
|
-
identityHex: s.identityHex,
|
|
4136
|
-
...ctaFamily(s, mode, prefix)
|
|
4137
|
-
});
|
|
4138
3932
|
const nScale = generateNeutralScale(input.neutralH ?? scale.brandH, input.neutralLevel ?? "default", input.contrastProfile);
|
|
4139
3933
|
const lt = input.linkHex ? resolveLinkTrio(input.linkHex, input.contrastProfile) : null;
|
|
4140
3934
|
const invSeed = input.linkHex ?? scale.identityHex ?? (() => {
|
|
@@ -4143,10 +3937,9 @@ function themeToFigma(r, input) {
|
|
|
4143
3937
|
return toHex2(e.r, e.g, e.b);
|
|
4144
3938
|
})();
|
|
4145
3939
|
const invLt = resolveLinkInverseTrio(invSeed, input.contrastProfile);
|
|
4146
|
-
const neutralExtra = (mode) => ctaFamily(nScale, mode, CSS_FAMILY.neutral);
|
|
4147
3940
|
const build = (mode) => {
|
|
4148
3941
|
const p02 = mode === "light" ? nScale.paper0 : nScale.paper0Dark;
|
|
4149
|
-
const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark,
|
|
3942
|
+
const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, ctaFamily(nScale, mode, CSS_FAMILY.neutral));
|
|
4150
3943
|
const i02 = mode === "light" ? nScale.pen100 : nScale.pen100Dark;
|
|
4151
3944
|
const splicePen100 = (g3) => {
|
|
4152
3945
|
if (!i02) return g3;
|
|
@@ -4158,41 +3951,31 @@ function themeToFigma(r, input) {
|
|
|
4158
3951
|
return out;
|
|
4159
3952
|
};
|
|
4160
3953
|
const neutralGroup = splicePen100(p02 ? { [PAPER_0]: colorFromStop(p02), ...ramp } : ramp);
|
|
4161
|
-
const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark,
|
|
3954
|
+
const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, ctaFamily(secondary, mode, CSS_FAMILY.brandSecondary));
|
|
4162
3955
|
if (input.secondaryStyle === "outline" && input.secondary) {
|
|
4163
3956
|
const s8 = secondary[mode].find((s) => s.stop === 8);
|
|
4164
3957
|
const s9 = secondary[mode].find((s) => s.stop === 9);
|
|
4165
3958
|
putLeaf(secondaryGroup, STAMP_FILL, TRANSPARENT_TOKEN);
|
|
4166
3959
|
if (s8) {
|
|
4167
3960
|
const e = srgbEmitChannels(s8);
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
});
|
|
4172
|
-
putLeaf(secondaryGroup, STAMP_FILL_HOVER, alphaTint(OUTLINE_HOVER_ALPHA));
|
|
4173
|
-
putLeaf(secondaryGroup, STAMP_FILL_PRESSED, alphaTint(OUTLINE_PRESSED_ALPHA));
|
|
3961
|
+
putLeaf(secondaryGroup, STAMP_FILL_HOVER, color(e.r, e.g, e.b, OUTLINE_HOVER_ALPHA));
|
|
3962
|
+
putLeaf(secondaryGroup, STAMP_FILL_PRESSED, color(e.r, e.g, e.b, OUTLINE_PRESSED_ALPHA));
|
|
3963
|
+
putLeaf(secondaryGroup, STAMP_EDGE, colorFromStop(s8));
|
|
4174
3964
|
}
|
|
4175
|
-
if (s8) putLeaf(secondaryGroup, STAMP_EDGE, colorFromStop(s8));
|
|
4176
3965
|
if (s9) putLeaf(secondaryGroup, STAMP_ON, colorFromStop(s9));
|
|
4177
3966
|
}
|
|
4178
|
-
const softOnCta = (g3, white) =>
|
|
4179
|
-
const p = white ? 1 : 0;
|
|
4180
|
-
putLeaf(g3, STAMP_ON, {
|
|
4181
|
-
$type: "color",
|
|
4182
|
-
$value: { colorSpace: "srgb", components: [p, p, p], alpha: SOFT_ON_CTA_ALPHA[mode], hex: white ? "#ffffff" : "#000000" }
|
|
4183
|
-
});
|
|
4184
|
-
};
|
|
3967
|
+
const softOnCta = (g3, white) => putLeaf(g3, STAMP_ON, pole(white, SOFT_ON_CTA_ALPHA[mode]));
|
|
4185
3968
|
softOnCta(neutralGroup, mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark);
|
|
4186
3969
|
if (input.secondary && input.secondaryStyle !== "outline" && softOnCtaPasses(input.secondary, mode))
|
|
4187
3970
|
softOnCta(secondaryGroup, mode === "light" ? secondaryOnFillLight : secondaryOnFillDark);
|
|
4188
|
-
const brandGroup = rampGroup(scale[mode], mode === "light" ? scale.onFillTextIsWhite : scale.onFillTextIsWhiteDark,
|
|
3971
|
+
const brandGroup = rampGroup(scale[mode], mode === "light" ? scale.onFillTextIsWhite : scale.onFillTextIsWhiteDark, ctaFamily(scale, mode, CSS_FAMILY.brandPrimary));
|
|
4189
3972
|
const esc = input.ctaEscape ? escapeCtaFamily(nScale, mode, input.contrastProfile) : null;
|
|
4190
3973
|
if (esc) {
|
|
4191
3974
|
for (const g3 of input.secondary ? [brandGroup] : [brandGroup, secondaryGroup]) {
|
|
4192
3975
|
putLeaf(g3, STAMP_FILL, colorFromStop(esc.cta));
|
|
4193
3976
|
putLeaf(g3, STAMP_FILL_HOVER, colorFromStop(esc.ctaHover));
|
|
4194
3977
|
putLeaf(g3, STAMP_FILL_PRESSED, colorFromStop(esc.ctaPressed));
|
|
4195
|
-
putLeaf(g3, STAMP_ON,
|
|
3978
|
+
putLeaf(g3, STAMP_ON, pole(esc.onFillIsWhite));
|
|
4196
3979
|
}
|
|
4197
3980
|
}
|
|
4198
3981
|
const scaleTextAt = (n) => {
|
|
@@ -4200,121 +3983,111 @@ function themeToFigma(r, input) {
|
|
|
4200
3983
|
if (!s) throw new Error(`themeToFigma link: the brand scale has no pen stop ${n}`);
|
|
4201
3984
|
return s;
|
|
4202
3985
|
};
|
|
4203
|
-
const
|
|
4204
|
-
|
|
3986
|
+
const trio = (posture) => {
|
|
3987
|
+
const t = posture === "default" ? lt ? mode === "light" ? [lt.link, lt.linkHover, lt.linkPressed] : [lt.linkDark, lt.linkHoverDark, lt.linkPressedDark] : [scaleTextAt(9), scaleTextAt(10), scaleTextAt(11)] : mode === "light" ? [invLt.link, invLt.linkHover, invLt.linkPressed] : [invLt.linkDark, invLt.linkHoverDark, invLt.linkPressedDark];
|
|
3988
|
+
return Object.fromEntries(LINK_STATES.map((state, i) => [state, colorFromStop(t[i])]));
|
|
3989
|
+
};
|
|
4205
3990
|
const g2 = {
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
link: linkGroup,
|
|
4210
|
-
"link-inverse": linkInverseGroup
|
|
3991
|
+
[CSS_FAMILY.neutral]: neutralGroup,
|
|
3992
|
+
[CSS_FAMILY.brandPrimary]: brandGroup,
|
|
3993
|
+
[CSS_FAMILY.brandSecondary]: secondaryGroup
|
|
4211
3994
|
};
|
|
4212
3995
|
for (const sig of input.signals) {
|
|
4213
|
-
|
|
3996
|
+
const role = SIGNAL_EMIT_NAME[sig.name] ?? sig.name;
|
|
3997
|
+
g2[role] = rampGroup(
|
|
4214
3998
|
sig.scale[mode],
|
|
4215
3999
|
mode === "light" ? sig.scale.onFillTextIsWhite : sig.scale.onFillTextIsWhiteDark,
|
|
4216
|
-
|
|
4217
|
-
// Unreachable at the Lc 15 gate — no signal gets within 4 Lc of it — but defined, not accidental.
|
|
4218
|
-
ctaFamily(sig.scale, mode, sig.name)
|
|
4000
|
+
ctaFamily(sig.scale, mode, role)
|
|
4219
4001
|
);
|
|
4220
4002
|
}
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
$value: { colorSpace: "srgb", components: [c, c, c], alpha, hex: white ? "#ffffff" : "#000000" }
|
|
4226
|
-
};
|
|
4003
|
+
g2[LINK_GROUP] = { default: trio("default"), inverse: trio("inverse") };
|
|
4004
|
+
g2[ABSOLUTE_GROUP] = {
|
|
4005
|
+
[CSS_FAMILY.brandPrimary]: colorFromHexString(scale.identityHex ?? invSeed),
|
|
4006
|
+
[CSS_FAMILY.brandSecondary]: colorFromHexString((input.secondary ?? scale).identityHex ?? invSeed)
|
|
4227
4007
|
};
|
|
4228
|
-
putLeaf(g2, SYSTEM_LEAF.ABS_BLACK, colorFromHex(false));
|
|
4229
|
-
putLeaf(g2, SYSTEM_LEAF.ABS_WHITE, colorFromHex(true));
|
|
4230
|
-
for (const [path, law] of Object.entries(SURFACE_PLANE_LAW)) {
|
|
4231
|
-
const tok = neutralGroup[law[mode]];
|
|
4232
|
-
if (tok && "$type" in tok) putLeaf(g2, path, tok);
|
|
4233
|
-
}
|
|
4234
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.TRANSPARENT, pole(true, 0));
|
|
4235
|
-
for (const rung of Object.keys(OPACITY_RUNGS).map(Number))
|
|
4236
|
-
putLeaf(g2, opacityTokenPath(rung), { $type: "number", $value: rung });
|
|
4237
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.INK, pole(mode === "dark", SOFT_ON_CTA_ALPHA[mode]));
|
|
4238
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.AWAY_FROM_BG_06, OFFSET_TOKEN(6, mode));
|
|
4239
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.AWAY_FROM_BG_08, OFFSET_TOKEN(8, mode));
|
|
4240
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.AWAY_FROM_BG_16, OFFSET_TOKEN(16, mode));
|
|
4241
|
-
const inv = mode === "light" ? "dark" : "light";
|
|
4242
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.TOWARD_BG_06, OFFSET_TOKEN(6, inv));
|
|
4243
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.TOWARD_BG_08, OFFSET_TOKEN(8, inv));
|
|
4244
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.TOWARD_BG_16, OFFSET_TOKEN(16, inv));
|
|
4245
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.SHADOW_04, pole(false, SHADOW_ALPHAS[4][mode]));
|
|
4246
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.SHADOW_08, pole(false, SHADOW_ALPHAS[8][mode]));
|
|
4247
|
-
putLeaf(g2, SYSTEM_LEAF.ALPHA.SHADOW_12, pole(false, SHADOW_ALPHAS[12][mode]));
|
|
4248
4008
|
return g2;
|
|
4249
4009
|
};
|
|
4250
4010
|
return { light: build("light"), dark: build("dark") };
|
|
4251
4011
|
}
|
|
4252
4012
|
|
|
4253
|
-
// src/engine/
|
|
4254
|
-
var
|
|
4255
|
-
var
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
const
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4013
|
+
// src/engine/dtcgRender.ts
|
|
4014
|
+
var STAMP = [STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED, STAMP_EDGE, STAMP_ON];
|
|
4015
|
+
var STOPS = Array.from({ length: SCALE_STOP_COUNT }, (_, i) => stopTokenName(i + 1));
|
|
4016
|
+
function tokenPaths() {
|
|
4017
|
+
const family = (word, poles) => [
|
|
4018
|
+
...poles ? [familyPath(word, PAPER_0)] : [],
|
|
4019
|
+
...STOPS.map((leaf) => familyPath(word, leaf)),
|
|
4020
|
+
...poles ? [familyPath(word, PEN_100)] : [],
|
|
4021
|
+
...STAMP.map((leaf) => familyPath(word, leaf))
|
|
4022
|
+
];
|
|
4023
|
+
return [
|
|
4024
|
+
...family(CSS_FAMILY.neutral, true),
|
|
4025
|
+
...family(CSS_FAMILY.brandPrimary, false),
|
|
4026
|
+
...family(CSS_FAMILY.brandSecondary, false),
|
|
4027
|
+
...SIGNALS2.flatMap((s) => family(s.emitName, false)),
|
|
4028
|
+
...LINK_POSTURES.flatMap((posture) => LINK_STATES.map((state) => linkPath(posture, state))),
|
|
4029
|
+
absolutePath(CSS_FAMILY.brandPrimary),
|
|
4030
|
+
absolutePath(CSS_FAMILY.brandSecondary)
|
|
4031
|
+
];
|
|
4032
|
+
}
|
|
4033
|
+
var byName = () => new Map(tokenPaths().map((p) => [cssVarName(p).slice(2), p]));
|
|
4034
|
+
function tokenPathOf(name) {
|
|
4035
|
+
return byName().get(name);
|
|
4036
|
+
}
|
|
4037
|
+
function descriptionPathOf(name) {
|
|
4038
|
+
const p = tokenPathOf(name);
|
|
4039
|
+
return p && figmaPathOf(p);
|
|
4040
|
+
}
|
|
4041
|
+
var component = (c8) => Math.round(c8 / 255 * 1e4) / 1e4;
|
|
4042
|
+
var colorValue = (r, g2, b, alpha) => ({
|
|
4043
|
+
colorSpace: "srgb",
|
|
4044
|
+
components: [component(r), component(g2), component(b)],
|
|
4045
|
+
alpha,
|
|
4046
|
+
hex: "#" + [r, g2, b].map((c) => c.toString(16).padStart(2, "0")).join("")
|
|
4047
|
+
});
|
|
4048
|
+
function dtcgColor(value) {
|
|
4049
|
+
let m;
|
|
4050
|
+
if (m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(value)) return colorValue(parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16), 1);
|
|
4051
|
+
if (m = /^rgba\((\d+), (\d+), (\d+), ([0-9.]+)\)$/.exec(value)) return colorValue(+m[1], +m[2], +m[3], +m[4]);
|
|
4052
|
+
if (value === "transparent") return colorValue(0, 0, 0, 0);
|
|
4053
|
+
throw new Error(`dtcgColor: not a color the emitters write: ${value}`);
|
|
4054
|
+
}
|
|
4055
|
+
var ALIAS = /^var\(--([a-z0-9-]+)\)$/;
|
|
4056
|
+
var SEGMENT = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4057
|
+
function tokensToDtcg(tokens, options = {}) {
|
|
4058
|
+
const { rootGroup } = options;
|
|
4059
|
+
if (rootGroup !== void 0 && !SEGMENT.test(rootGroup)) throw new Error(`tokensToDtcg: rootGroup must be lower-case words and digits joined by hyphens: ${JSON.stringify(rootGroup)}`);
|
|
4060
|
+
const rooted = (path) => rootGroup === void 0 ? path : [rootGroup, ...path];
|
|
4061
|
+
const paths = byName();
|
|
4062
|
+
const roster = new Set(paths.keys());
|
|
4063
|
+
for (const name of tokens.names) if (!roster.has(name)) throw new Error(`tokensToDtcg: the emit carries a name the roster does not know: ${name}`);
|
|
4064
|
+
for (const name of roster) if (!(name in tokens.light)) throw new Error(`tokensToDtcg: the emit lacks ${name}`);
|
|
4065
|
+
const build = (mode) => {
|
|
4066
|
+
const doc = {};
|
|
4067
|
+
for (const [name, path] of paths) {
|
|
4068
|
+
const raw = tokens.raw[mode][name] ?? tokens.raw.light[name];
|
|
4069
|
+
const alias = ALIAS.exec(raw);
|
|
4070
|
+
let token;
|
|
4071
|
+
if (alias) {
|
|
4072
|
+
const target = paths.get(alias[1]);
|
|
4073
|
+
if (!target) throw new Error(`tokensToDtcg: ${name} references a name outside the roster: ${alias[1]}`);
|
|
4074
|
+
token = { $type: "color", $value: `{${rooted(target).join(".")}}` };
|
|
4075
|
+
} else {
|
|
4076
|
+
token = { $type: "color", $value: dtcgColor(tokens[mode][name]) };
|
|
4077
|
+
}
|
|
4078
|
+
const body = describeDocument(figmaPathOf(path));
|
|
4079
|
+
if (body) token.$description = body;
|
|
4080
|
+
let cur = doc;
|
|
4081
|
+
for (const seg of rooted(path).slice(0, -1)) {
|
|
4082
|
+
const next = cur[seg];
|
|
4083
|
+
if (next && "$type" in next) throw new Error(`tokensToDtcg: ${seg} is both a token and a group on the way to ${path.join("/")}`);
|
|
4084
|
+
cur = cur[seg] ?? (cur[seg] = {});
|
|
4085
|
+
}
|
|
4086
|
+
const leaf = path[path.length - 1];
|
|
4087
|
+
if (leaf in cur) throw new Error(`tokensToDtcg: duplicate path ${path.join("/")}`);
|
|
4088
|
+
cur[leaf] = token;
|
|
4089
|
+
}
|
|
4090
|
+
return doc;
|
|
4282
4091
|
};
|
|
4283
|
-
|
|
4284
|
-
const rv = roleVal[rr.role];
|
|
4285
|
-
group[rr.role] = { $type: "color", $value: hexToValue(rv.hex), $extensions: { [EXT_KEY]: { resolver: RESOLVER_ID, seed: seedRef, mode, ...rr } } };
|
|
4286
|
-
}
|
|
4287
|
-
return group;
|
|
4288
|
-
}
|
|
4289
|
-
function parseToken(token) {
|
|
4290
|
-
const ext = token.$extensions?.[EXT_KEY];
|
|
4291
|
-
if (!ext) throw new Error("missing $extensions." + EXT_KEY);
|
|
4292
|
-
if (ext.resolver !== RESOLVER_ID) throw new Error(`unknown resolver "${ext.resolver}" (this resolver is ${RESOLVER_ID})`);
|
|
4293
|
-
const { resolver, seed, mode, ...req } = ext;
|
|
4294
|
-
const isRole = "role" in req;
|
|
4295
|
-
if (!isRole) {
|
|
4296
|
-
for (const k of ["stop", "rootL", "group", "produce"]) if (req[k] === void 0) throw new Error(`stop requirement missing "${k}"`);
|
|
4297
|
-
}
|
|
4298
|
-
if (isRole) {
|
|
4299
|
-
for (const k of ["role", "produce", "floorL", "chromaMult"]) if (req[k] === void 0) throw new Error(`role requirement missing "${k}"`);
|
|
4300
|
-
}
|
|
4301
|
-
if (isRole && LEGACY_ROLE_NAMES[req.role]) req.role = LEGACY_ROLE_NAMES[req.role];
|
|
4302
|
-
return { req, seedRef: seed, mode };
|
|
4303
|
-
}
|
|
4304
|
-
function resolveDtcgRamp(group) {
|
|
4305
|
-
const seedHex = group.seed.$value.hex;
|
|
4306
|
-
const ons = group.$extensions?.[EXT_KEY]?.ons;
|
|
4307
|
-
if (!ons) throw new Error("missing group-level ons declaration");
|
|
4308
|
-
const stops = [];
|
|
4309
|
-
const roles = [];
|
|
4310
|
-
let mode;
|
|
4311
|
-
for (const [k, t] of Object.entries(group)) {
|
|
4312
|
-
if (k === "seed" || k === "$extensions") continue;
|
|
4313
|
-
const { req, mode: m } = parseToken(t);
|
|
4314
|
-
mode ?? (mode = m);
|
|
4315
|
-
"role" in req ? roles.push(req) : stops.push(req);
|
|
4316
|
-
}
|
|
4317
|
-
if (!mode) throw new Error("empty ramp group");
|
|
4318
|
-
stops.sort((a, b) => a.stop - b.stop);
|
|
4319
|
-
return resolveRamp(seedHex, mode, { stops, roles, ons });
|
|
4092
|
+
return { light: build("light"), dark: build("dark") };
|
|
4320
4093
|
}
|