okchroma 0.6.1 → 0.7.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.
@@ -20,27 +20,21 @@ 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,
23
25
  CSS_FAMILY: () => CSS_FAMILY,
24
26
  DEFAULT_LINK_HEX: () => DEFAULT_LINK_HEX,
25
- DISABLED_OPACITY: () => DISABLED_OPACITY,
26
- EXT_KEY: () => EXT_KEY,
27
- INTERACTION_FAMILIES: () => INTERACTION_FAMILIES,
28
- INTERACTION_LADDER: () => INTERACTION_LADDER,
29
- INTERACTION_POLE_FAMILY: () => INTERACTION_POLE_FAMILY,
30
- INTERACTION_ROWS: () => INTERACTION_ROWS,
31
- INTERACTION_RUNGS: () => INTERACTION_RUNGS,
32
- INTERACTION_STATES: () => INTERACTION_STATES,
27
+ GUARANTEE_SCOPE: () => GUARANTEE_SCOPE,
28
+ LINK_GROUP: () => LINK_GROUP,
29
+ LINK_POSTURES: () => LINK_POSTURES,
30
+ LINK_STATES: () => LINK_STATES,
33
31
  MODE_SPECS: () => MODE_SPECS,
34
32
  OFFSET_ALPHAS: () => OFFSET_ALPHAS,
35
- OPACITY_RUNGS: () => OPACITY_RUNGS,
36
33
  OUTLINE_HOVER_ALPHA: () => OUTLINE_HOVER_ALPHA,
37
34
  PAPER_0: () => PAPER_0,
38
35
  PEN_100: () => PEN_100,
39
- RESOLVER_ID: () => RESOLVER_ID,
40
36
  SCALE_STOP_COUNT: () => SCALE_STOP_COUNT,
41
- SCRIM_ALPHA: () => SCRIM_ALPHA,
42
37
  SECONDARY_DISTINCT_DELTA_E: () => SECONDARY_DISTINCT_DELTA_E,
43
- SHADOW_ALPHAS: () => SHADOW_ALPHAS,
44
38
  SIGNALS: () => SIGNALS2,
45
39
  SIGNAL_EMIT_NAME: () => SIGNAL_EMIT_NAME,
46
40
  SIGNAL_SCALES: () => SIGNAL_SCALES,
@@ -48,38 +42,35 @@ __export(index_exports, {
48
42
  STAMP_FILL: () => STAMP_FILL,
49
43
  STAMP_FILL_HOVER: () => STAMP_FILL_HOVER,
50
44
  STAMP_FILL_PRESSED: () => STAMP_FILL_PRESSED,
45
+ STAMP_LEAF: () => STAMP_LEAF,
51
46
  STAMP_ON: () => STAMP_ON,
52
47
  STAMP_STATE_LEAVES: () => STAMP_STATE_LEAVES,
53
48
  SUBTLE_PASTEL_K: () => SUBTLE_PASTEL_K,
54
49
  SUBTLE_TINT_MULT: () => SUBTLE_TINT_MULT,
55
- SURFACE_PLANE_LAW: () => SURFACE_PLANE_LAW,
56
- SYSTEM_LEAF: () => SYSTEM_LEAF,
50
+ absolutePath: () => absolutePath,
57
51
  brandCss: () => brandCss,
58
52
  checkAllCollisions: () => checkAllCollisions,
59
53
  checkCollision: () => checkCollision,
60
54
  checkHueCollision: () => checkHueCollision,
61
55
  classifyArchetype: () => classifyArchetype,
56
+ cssVarName: () => cssVarName,
62
57
  ctaBorderRung: () => ctaBorderRung,
63
58
  ctaNeedsBorder: () => ctaNeedsBorder,
64
- emitDtcgRamp: () => emitDtcgRamp,
59
+ describeDocument: () => describeDocument,
60
+ describeToken: () => describeToken,
61
+ descriptionPathOf: () => descriptionPathOf,
62
+ familyPath: () => familyPath,
63
+ figmaPathOf: () => figmaPathOf,
65
64
  generateNeutralScale: () => generateNeutralScale,
66
65
  generateScale: () => generateScale,
67
66
  generateSubtleSecondary: () => generateSubtleSecondary,
68
- interactionCss: () => interactionCss,
69
- interactionRows: () => interactionRows,
70
- interactionTintName: () => interactionTintName,
71
- interactionTokens: () => interactionTokens,
72
- interactionVarName: () => interactionVarName,
67
+ groupEntries: () => groupEntries,
68
+ linkPath: () => linkPath,
73
69
  neutralCss: () => neutralCss,
74
70
  neutralTintHue: () => neutralTintHue,
75
- opacityLeafName: () => opacityLeafName,
76
- opacityTokenPath: () => opacityTokenPath,
77
- opacityVarName: () => opacityVarName,
78
71
  pageStopFor: () => pageStopFor,
79
- parseToken: () => parseToken,
80
72
  readEmission: () => readEmission,
81
73
  resolveBrand: () => resolveBrand,
82
- resolveDtcgRamp: () => resolveDtcgRamp,
83
74
  resolveLinkInverseTrio: () => resolveLinkInverseTrio,
84
75
  resolveLinkTrio: () => resolveLinkTrio,
85
76
  resolveReferences: () => resolveReferences,
@@ -89,12 +80,13 @@ __export(index_exports, {
89
80
  stopHex: () => stopHex,
90
81
  stopTokenName: () => stopTokenName,
91
82
  stopsToVars: () => stopsToVars,
92
- systemCss: () => systemCss,
93
83
  themeToFigma: () => themeToFigma,
94
84
  themeTokens: () => themeTokens,
95
85
  toHex: () => toHex2,
96
86
  tokenOrder: () => tokenOrder,
97
- tokensFromEmission: () => tokensFromEmission
87
+ tokenPathOf: () => tokenPathOf,
88
+ tokensFromEmission: () => tokensFromEmission,
89
+ tokensToDtcg: () => tokensToDtcg
98
90
  });
99
91
  module.exports = __toCommonJS(index_exports);
100
92
 
@@ -825,29 +817,14 @@ function perceptualDarkC(L, H2, nativeC, keep = KEEP_DARK, gamut = MASTER_GAMUT)
825
817
  var SHARED_NAMES = {
826
818
  1: "paper-1",
827
819
  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
820
  3: "paper-5",
837
821
  4: "chalk-8",
838
822
  5: "chalk-11",
839
823
  6: "chalk-15",
840
824
  7: "chalk-20",
841
825
  8: "highlighter-26",
842
- // THE PEN BAND (C49, owner 2026-08-05): the first text stop + emphasis fill (the
843
- // 2026-07-29 highlight collapse — the old highlight-9 and on-highlight are
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.
826
+ // the pen band: the first text stop doubles as the emphasis fill, the between stop and
827
+ // the strong stop follow; the three read as states are the text-style CTA (C49)
851
828
  9: "pencil-47",
852
829
  10: "pen-58",
853
830
  11: "pen-70"
@@ -879,63 +856,21 @@ var STAMP_LEAF = {
879
856
  EDGE: STAMP_STATE_LEAVES[STAMP_EDGE],
880
857
  ON: STAMP_STATE_LEAVES[STAMP_ON]
881
858
  };
882
- var SYSTEM_LEAF = {
883
- ABS_BLACK: "system/abs-black",
884
- ABS_WHITE: "system/abs-white",
885
- SURFACE: {
886
- DIM: "system/surface/dim",
887
- LOW: "system/surface/low",
888
- MID: "system/surface/mid",
889
- HIGH: "system/surface/high"
890
- },
891
- ALPHA: {
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
- };
859
+ var cssVarName = (path) => `--${path.join("-")}`;
860
+ var figmaPathOf = (path) => path.join("/");
861
+ var familyPath = (family, leaf) => [family, ...(STAMP_STATE_LEAVES[leaf] ?? leaf).split("/")];
862
+ var LINK_GROUP = "link";
863
+ var LINK_POSTURES = ["default", "inverse"];
864
+ var LINK_STATES = ["enabled", "hover", "pressed"];
865
+ var linkPath = (posture, state) => [LINK_GROUP, posture, state];
866
+ var ABSOLUTE_GROUP = "absolute";
867
+ var absolutePath = (family) => [ABSOLUTE_GROUP, family];
932
868
  var TOKEN_ORDER = [
933
869
  ...Object.values(SHARED_NAMES),
934
870
  STAMP_FILL,
935
871
  STAMP_FILL_HOVER,
936
872
  STAMP_FILL_PRESSED,
937
- STAMP_ON,
938
- "identity"
873
+ STAMP_ON
939
874
  ];
940
875
  function tokenOrder(name) {
941
876
  const i = TOKEN_ORDER.indexOf(name);
@@ -2864,8 +2799,34 @@ var COLOR_WORD = {
2864
2799
  [FAMILY.positive]: " (green)",
2865
2800
  [FAMILY.info]: " (blue)"
2866
2801
  };
2867
- var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
2868
- var CHALK = { req: "decorative borders, inverted text, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2802
+ var COLLIDES = "shifts to avoid similar colors";
2803
+ var GUARANTEE_SCOPE = {
2804
+ highlighter: { level: AA_LARGE, grounds: ["paper"] },
2805
+ pencil: { level: AA_BODY, grounds: ["paper"] },
2806
+ pen: { level: AA_BODY, grounds: ["paper", "chalk"] }
2807
+ };
2808
+ var BAND_STOPS = {
2809
+ highlighter: ["highlighter-26"],
2810
+ pencil: ["pencil-47"],
2811
+ pen: ["pen-58", "pen-70"]
2812
+ };
2813
+ var list = (words) => words.length > 1 ? `${words.slice(0, -1).join(", ")} and ${words[words.length - 1]}` : words[0];
2814
+ var ofFamilies = (fam) => fam === FAMILY.neutral ? "of every family" : "of this family and of the neutral";
2815
+ var fromFamilies = (fam) => fam === FAMILY.neutral ? "from every family" : "from this family and from the neutral";
2816
+ var claimOf = (band) => (fam) => `${GUARANTEE_SCOPE[band].level} on every ${GUARANTEE_SCOPE[band].grounds.join(" and ")} ${ofFamilies(fam)}`;
2817
+ var groundLine = (ground) => (fam) => {
2818
+ const bands = Object.keys(GUARANTEE_SCOPE).filter((b) => GUARANTEE_SCOPE[b].grounds.includes(ground));
2819
+ const byLevel = /* @__PURE__ */ new Map();
2820
+ for (const b of bands) {
2821
+ const stops = [...BAND_STOPS[b], ...b === "pen" && fam === FAMILY.neutral ? ["pen-100"] : []];
2822
+ byLevel.set(GUARANTEE_SCOPE[b].level, [...byLevel.get(GUARANTEE_SCOPE[b].level) ?? [], ...stops]);
2823
+ }
2824
+ const clears = [...byLevel.entries()].map(([level, stops]) => `${list(stops)} clear${stops.length === 1 ? "s" : ""} it as ${level}`).join("; ");
2825
+ const notHeld = Object.keys(GUARANTEE_SCOPE).filter((b) => !bands.includes(b));
2826
+ return `${clears}, ${fromFamilies(fam)}${notHeld.length ? `; ${list(notHeld.map((b) => `the ${b}`))} ${notHeld.length === 1 ? "is" : "are"} not held against it` : ""}`;
2827
+ };
2828
+ var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true, ground: groundLine("paper") };
2829
+ var CHALK = { req: "decorative borders, inverted text, illos, signal hierarchy", theming: (f) => TINT[f], collides: true, ground: groundLine("chalk") };
2869
2830
  var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
2870
2831
  var OVERLAY = {
2871
2832
  req: "translucent backgrounds that hold their reading on any paper",
@@ -2877,106 +2838,122 @@ var SCALE = {
2877
2838
  "paper-99-overlay": OVERLAY,
2878
2839
  "paper-3": PAPER,
2879
2840
  "paper-97-overlay": OVERLAY,
2880
- "paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
2841
+ "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
2842
  "paper-95-overlay": OVERLAY,
2882
2843
  "chalk-8": CHALK,
2883
2844
  "chalk-11": CHALK,
2884
2845
  "chalk-15": CHALK,
2885
2846
  "chalk-20": CHALK,
2886
- // ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
2887
- // landing on the highlighter rows is worth its ic-ON-s noise — the one surviving body carrier)
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
- // ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
2892
- // words, and a body carrying either floods that token's picker search — the C50 law;
2893
- // "strong-emphasis" reworded 2026-08-28: str-ON-g fed the "on" flood)
2894
- "pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2895
- // the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
2896
- // these bodies on purpose: it stopped being a token label, so it floods nothing,
2897
- // and a designer's "cta" query still lands on these rows.
2898
- "stamp/fill": { req: "CTAs", theming: (f) => `fully re-solved per theme and family${COLOR_WORD[f]}` },
2899
- "stamp/fill-hover": { req: "CTA pointer-over state", theming: (f) => `follows its rest fill${COLOR_WORD[f]}` },
2900
- "stamp/fill-pressed": { req: "CTA pressed state", theming: (f) => `follows its rest fill${COLOR_WORD[f]}` },
2901
- "stamp/edge": { req: "min APCA visibility", theming: (f) => `draws for CTAs that sit close to the page; strength per family tier${COLOR_WORD[f]}` },
2902
- // ("fill" reworded out 2026-08-18: fill became a label word and is foreign here)
2903
- "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]}` }
2847
+ // ("icons" stays although it carries "on": an "icon" query landing on the highlighter
2848
+ // rows is worth that noise)
2849
+ "highlighter-26": { req: "focus rings, icons, large text, translucent state layers over any ground", contrast: AA_LARGE, claim: claimOf("highlighter"), theming: solved, collides: true },
2850
+ "pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, claim: claimOf("pencil"), theming: solved, collides: true },
2851
+ "pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, claim: claimOf("pen"), theming: solved, collides: true },
2852
+ // ("heavy-emphasis": "high" and "low" are plane words in the community plugin and
2853
+ // "strong" carries "on")
2854
+ "pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, claim: claimOf("pen"), theming: solved, collides: true },
2855
+ // the stamp family. "CTA" stays in these bodies on purpose: it is not a token label, so
2856
+ // it floods nothing, and a designer's "cta" query lands on these rows.
2857
+ // the usage lines are the owner's guidance, for the document rendering: the fill is
2858
+ // misused as a standalone color in the wild
2859
+ "stamp/fill": {
2860
+ req: "CTAs",
2861
+ theming: (f) => `fully re-solved per theme and family${COLOR_WORD[f]}`,
2862
+ 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"
2863
+ },
2864
+ "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" },
2865
+ "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" },
2866
+ "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" },
2867
+ // ("fill" is a label word and foreign here)
2868
+ "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" }
2869
+ };
2870
+ var NEUTRAL_ONLY = {
2871
+ // the pole is measured against the neutral's own stops only; as text, it is the on-color
2872
+ // of every family's emphasis fill (the band audit holds that)
2873
+ "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` },
2874
+ // the literal pole carries no tint, so no theming line
2875
+ "pen-100": { req: "max-emphasis text", contrast: AA_BODY, claim: () => `${AA_BODY} on every paper and chalk of every family` }
2904
2876
  };
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
2877
  var LINK = (state, contrast) => ({
2913
2878
  req: "links" + state,
2914
2879
  contrast,
2915
- theming: "rides the theme\u2019s link color; custom seed re-solves; overridable per theme"
2880
+ claim: () => `${contrast} on every paper of the neutral`,
2881
+ theming: "rides the theme\u2019s link color; custom seed re-solves; overridable per theme",
2882
+ use: "hyperlinks; not a text-style CTA, which is the text stops read as states"
2916
2883
  });
2917
2884
  var LINK_INVERSE = (state, contrast) => ({
2918
2885
  req: "links over inverted backgrounds" + state,
2919
2886
  contrast,
2920
- theming: "same seed as the link, re-solved for inverted backgrounds; overridable per theme"
2887
+ claim: () => `${contrast} on any family's pen-70`,
2888
+ theming: "same seed as the link, re-solved for inverted backgrounds; overridable per theme",
2889
+ use: "hyperlinks on a pen-70 ground; not a text-style CTA"
2921
2890
  });
2922
2891
  var SYSTEM = {
2923
- "system/abs-black": ABS,
2924
- "system/abs-white": ABS,
2925
- // (the community system/pen-100 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
2926
- // the anchor is engine-resolved and rides the neutral in BOTH plugins now —
2927
- // NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
2928
- "system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
2929
- // ("companion" reworded 2026-08-28: compani-ON fed the "on" flood)
2930
- "system/abs-alt": { req: "identity seed reference", theming: "the theme\u2019s paired input, as given" },
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)
2892
+ "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" },
2893
+ "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" },
2894
+ "link/default/enabled": LINK("", AA_BODY),
2895
+ "link/default/hover": LINK(" pointer-over", AA_BODY),
2896
+ "link/default/pressed": LINK(" pressed", AA_BODY),
2897
+ "link/inverse/enabled": LINK_INVERSE("", AA_BODY),
2898
+ "link/inverse/hover": LINK_INVERSE(" pointer-over", AA_BODY),
2899
+ "link/inverse/pressed": LINK_INVERSE(" pressed", AA_BODY)
2971
2900
  };
2972
2901
  var PREFIXES = [
2973
- // community spellings FIRST: the 2026-08-21 family rename made the ext word a
2974
- // PREFIX of the community shapes ('brand/' would shadow 'brand/primary/' and
2975
- // 'brand/alt/'), so most-specific must be tried before the family words
2902
+ // community spellings first: the family word is a prefix of the community shapes
2903
+ // ('brand/' would shadow 'brand/primary/' and 'brand/alt/'), so most-specific wins
2976
2904
  ["brand/primary/", FAMILY.brandPrimary],
2977
2905
  ["brand/alt/", FAMILY.brandSecondary],
2978
2906
  ...FAMILIES.map((f) => [f + "/", f])
2979
2907
  ];
2908
+ var ZONE_MAP = [
2909
+ // brand-alt before brand: canonicalize is first-match startsWith, and brand is a prefix
2910
+ // of brand-alt
2911
+ ["system/abs-alt", "absolute/brand-alt"],
2912
+ ["system/abs-primary", "absolute/brand"],
2913
+ ["system/link/", "link/"],
2914
+ ["base/", ""],
2915
+ ["primitive/", ""]
2916
+ ];
2917
+ function canonicalize(path) {
2918
+ for (const [prefix, home] of ZONE_MAP)
2919
+ if (path.startsWith(prefix)) return home + path.slice(prefix.length);
2920
+ return path;
2921
+ }
2922
+ function bodyFor(path) {
2923
+ const canonical = canonicalize(path);
2924
+ if (SYSTEM[canonical]) return { body: SYSTEM[canonical], fam: FAMILY.neutral };
2925
+ const hit = PREFIXES.find(([p]) => canonical.startsWith(p));
2926
+ if (!hit) return void 0;
2927
+ const [prefix, fam] = hit;
2928
+ const leaf = canonical.slice(prefix.length);
2929
+ const body = fam === FAMILY.neutral && NEUTRAL_ONLY[leaf] || SCALE[leaf];
2930
+ return body ? { body, fam } : void 0;
2931
+ }
2932
+ function describeToken(path) {
2933
+ const canonical = canonicalize(path);
2934
+ const title = canonical.replace(/[/-]/g, " ");
2935
+ const hit = bodyFor(canonical);
2936
+ if (!hit) return title;
2937
+ const { body, fam } = hit;
2938
+ const lines = [title, `Req for: ${body.req}`];
2939
+ if (body.contrast) lines.push(body.contrast);
2940
+ if (body.theming) lines.push(`Theming: ${typeof body.theming === "function" ? body.theming(fam) : body.theming}`);
2941
+ if (body.collides && SIGNALS.includes(fam)) lines.push(COLLIDES);
2942
+ return lines.join("\n");
2943
+ }
2944
+ function describeDocument(path) {
2945
+ const hit = bodyFor(canonicalize(path));
2946
+ if (!hit) return "";
2947
+ const { body, fam } = hit;
2948
+ const lines = [`Req for: ${body.req}`];
2949
+ if (body.claim) lines.push(body.claim(fam));
2950
+ else if (body.contrast) lines.push(body.contrast);
2951
+ if (body.ground) lines.push(`Ground: ${body.ground(fam)}`);
2952
+ if (body.use) lines.push(`Use: ${body.use}`);
2953
+ if (body.theming) lines.push(`Theming: ${typeof body.theming === "function" ? body.theming(fam) : body.theming}`);
2954
+ if (body.collides && SIGNALS.includes(fam)) lines.push(COLLIDES);
2955
+ return lines.join("\n");
2956
+ }
2980
2957
 
2981
2958
  // src/engine/signals.ts
2982
2959
  var SIGNALS2 = [
@@ -3295,7 +3272,7 @@ var OUTLINE_PRESSED_ALPHA = 0.18;
3295
3272
  var SOFT_ON_CTA_ALPHA = { light: 0.75, dark: 0.8 };
3296
3273
  function softOnCtaPasses(s, mode) {
3297
3274
  const white = mode === "light" ? s.onFillTextIsWhite : s.onFillTextIsWhiteDark;
3298
- const pole = white ? 1 : 0;
3275
+ const pole2 = white ? 1 : 0;
3299
3276
  const a = SOFT_ON_CTA_ALPHA[mode];
3300
3277
  const states = mode === "light" ? [s.cta, s.ctaHover, s.ctaPressed] : [s.ctaDark, s.ctaHoverDark, s.ctaPressedDark];
3301
3278
  const lin = (v2) => v2 <= 0.04045 ? v2 / 12.92 : ((v2 + 0.055) / 1.055) ** 2.4;
@@ -3305,7 +3282,7 @@ function softOnCtaPasses(s, mode) {
3305
3282
  const e = srgbEmitChannels(st);
3306
3283
  const fr = q2(e.r), fg = q2(e.g), fb = q2(e.b);
3307
3284
  const fillY = relY(fr, fg, fb);
3308
- const textY = relY(pole * a + fr * (1 - a), pole * a + fg * (1 - a), pole * a + fb * (1 - a));
3285
+ const textY = relY(pole2 * a + fr * (1 - a), pole2 * a + fg * (1 - a), pole2 * a + fb * (1 - a));
3309
3286
  if (contrastRatio(textY, fillY) < 4.5) return false;
3310
3287
  }
3311
3288
  return true;
@@ -3551,7 +3528,7 @@ var p3Differs = (s) => s.C > clampChromaToGamut(s.L, s.C, s.H, "srgb") + 1e-4;
3551
3528
  var P3_SUPPORTS = "@supports (color: color(display-p3 1 1 1))";
3552
3529
  var P3_MEDIA = "@media (color-gamut: p3)";
3553
3530
  function stopsToVars(stops, prefix) {
3554
- return [...stops].sort((a, b) => tokenOrder(stopTokenName(a.stop)) - tokenOrder(stopTokenName(b.stop))).map((s) => ` --${prefix}-${stopTokenName(s.stop)}: ${stopHex(s)};`).join("\n");
3531
+ 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
3532
  }
3556
3533
  var onColor = (white) => white ? "#ffffff" : "#000000";
3557
3534
  var CTA_BORDER_LC_FLOOR = 15;
@@ -3562,34 +3539,7 @@ function pageStopFor(neutral, mode) {
3562
3539
  }
3563
3540
  var OFFSET_ALPHAS = { 6: 0.06, 8: 0.08, 16: 0.16 };
3564
3541
  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
3542
  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
3543
  function ctaPageLc(s, mode, page) {
3594
3544
  const cta = mode === "light" ? s.cta : s.ctaDark;
3595
3545
  return Math.abs(apcaLc(apcaY(cta.r, cta.g, cta.b), apcaY(page.r, page.g, page.b)));
@@ -3604,23 +3554,20 @@ function brandKindBody(prefix, s, mode, page, on = prefix === CSS_NEUTRAL ? "sof
3604
3554
  const onCta = mode === "light" ? s.onFillTextIsWhite : s.onFillTextIsWhiteDark;
3605
3555
  const border = ctaNeedsBorder(s, mode, page);
3606
3556
  const scaleBlock = [
3607
- ...stops.map((x2) => ({ name: stopTokenName(x2.stop), line: ` --${prefix}-${stopTokenName(x2.stop)}: ${stopHex(x2)};` }))
3557
+ ...stops.map((x2) => ({ name: stopTokenName(x2.stop), line: ` ${cssVarName(familyPath(prefix, stopTokenName(x2.stop)))}: ${stopHex(x2)};` }))
3608
3558
  ].sort((a, b) => tokenOrder(a.name) - tokenOrder(b.name)).map((v2) => v2.line).join("\n");
3609
3559
  return [
3610
3560
  scaleBlock,
3611
- ` --${prefix}-stamp-fill: ${stopHex(f.cta)};`,
3612
- ` --${prefix}-stamp-fill-hover: ${stopHex(f.ctaHover)};`,
3613
- ` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
3614
- ` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
3615
- // the SOFT on-cta (owner 2026-08-04: "neutral cta on's should also be the alpha"): the
3616
- // neutral's cta is the scale-fed CHALK-level fill, the system's other quiet cta, so its
3617
- // button text takes the pole AT ALPHA like the default-model secondary's — composited
3618
- // over whatever state the fill is in, so hover/pressed carry their own legibility. Same
3619
- // register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
3620
- // primitive. The neutral's is unconditional (the parameter default); the secondary's is
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)};`
3561
+ ` ${cssVarName(familyPath(prefix, STAMP_FILL))}: ${stopHex(f.cta)};`,
3562
+ ` ${cssVarName(familyPath(prefix, STAMP_FILL_HOVER))}: ${stopHex(f.ctaHover)};`,
3563
+ ` ${cssVarName(familyPath(prefix, STAMP_FILL_PRESSED))}: ${stopHex(f.ctaPressed)};`,
3564
+ ` ${cssVarName(familyPath(prefix, STAMP_EDGE))}: ${border ? offsetRgba(ctaBorderRung(prefix), mode) : "transparent"};`,
3565
+ // the soft on-text: a quiet fill's button text is the pole at alpha, composited over
3566
+ // whatever state the fill is in so hover and pressed carry their own legibility (C47).
3567
+ // The neutral's is unconditional (the parameter default); the secondary's is the
3568
+ // caller's per-mode verdict. Loud fills (the brand, the signals, the cta escape) keep
3569
+ // the solid pole.
3570
+ 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
3571
  ];
3625
3572
  }
3626
3573
  function ctaFamilyOf(s, mode) {
@@ -3630,9 +3577,9 @@ function brandKindP3Body(prefix, s, mode) {
3630
3577
  const stops = mode === "light" ? s.light : s.dark;
3631
3578
  const f = ctaFamilyOf(s, mode);
3632
3579
  const out = [];
3633
- for (const st of stops) if (p3Differs(st)) out.push(` --${prefix}-${stopTokenName(st.stop)}: ${p3Value(st)};`);
3634
- for (const [name, st] of [["stamp-fill", f.cta], ["stamp-fill-hover", f.ctaHover], ["stamp-fill-pressed", f.ctaPressed]])
3635
- if (p3Differs(st)) out.push(` --${prefix}-${name}: ${p3Value(st)};`);
3580
+ for (const st of stops) if (p3Differs(st)) out.push(` ${cssVarName(familyPath(prefix, stopTokenName(st.stop)))}: ${p3Value(st)};`);
3581
+ for (const [name, st] of [[STAMP_FILL, f.cta], [STAMP_FILL_HOVER, f.ctaHover], [STAMP_FILL_PRESSED, f.ctaPressed]])
3582
+ if (p3Differs(st)) out.push(` ${cssVarName(familyPath(prefix, name))}: ${p3Value(st)};`);
3636
3583
  return out;
3637
3584
  }
3638
3585
  function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBorder = true) {
@@ -3643,16 +3590,14 @@ function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBor
3643
3590
  const p3Dark = brandKindP3Body(CSS_NEUTRAL, s, "dark");
3644
3591
  return [
3645
3592
  `${selector} {`,
3646
- ` --${PAPER_0}: ${p02(s.paper0, "#ffffff")};`,
3647
- ` --${PEN_100}: ${p02(s.pen100, "#000000")};`,
3648
- // the neutral IS the page, so it is judged against its own paper stop
3649
- // the neutral's overlays never read the visibility bar (the exempt family), so
3650
- // the brandless chrome context passes 0
3593
+ ` ${cssVarName(familyPath(CSS_NEUTRAL, PAPER_0))}: ${p02(s.paper0, "#ffffff")};`,
3594
+ ` ${cssVarName(familyPath(CSS_NEUTRAL, PEN_100))}: ${p02(s.pen100, "#000000")};`,
3595
+ // the neutral is the page, so it is judged against its own paper stop
3651
3596
  ...brandKindBody(CSS_NEUTRAL, s, "light", nPage("light")),
3652
3597
  `}`,
3653
3598
  `${selector}[data-theme="dark"] {`,
3654
- ` --${PAPER_0}: ${p02(s.paper0Dark, "#000000")};`,
3655
- ` --${PEN_100}: ${p02(s.pen100Dark, "#ffffff")};`,
3599
+ ` ${cssVarName(familyPath(CSS_NEUTRAL, PAPER_0))}: ${p02(s.paper0Dark, "#000000")};`,
3600
+ ` ${cssVarName(familyPath(CSS_NEUTRAL, PEN_100))}: ${p02(s.pen100Dark, "#ffffff")};`,
3656
3601
  ...brandKindBody(CSS_NEUTRAL, s, "dark", nPage("dark")),
3657
3602
  `}`,
3658
3603
  ...p3Light.length || p3Dark.length ? [
@@ -3684,16 +3629,9 @@ function signalsCss(contrastProfile) {
3684
3629
  return [
3685
3630
  `/* Signal scales \u2014 engine-generated from canonical hexes, shared across brands */`,
3686
3631
  `: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
3632
  ...lightBlocks,
3694
3633
  `}`,
3695
3634
  `${SIGNALS_DARK_SELECTOR} {`,
3696
- ...alphaRootVars("dark"),
3697
3635
  ...darkBlocks,
3698
3636
  `}`,
3699
3637
  ...p3LightBlocks.length || p3DarkBlocks.length ? [
@@ -3727,69 +3665,72 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
3727
3665
  const page = ctaBorder ? { light: pageStopFor(nScale, "light"), dark: pageStopFor(nScale, "dark") } : { light: void 0, dark: void 0 };
3728
3666
  const mirrorBody = (prefix, mode) => {
3729
3667
  const stops = mode === "light" ? scale.light : scale.dark;
3730
- const alias = (name) => ` --${prefix}-${name}: var(--${CSS_BRAND}-${name});`;
3668
+ const alias = (name) => ` ${cssVarName(familyPath(prefix, name))}: var(${cssVarName(familyPath(CSS_BRAND, name))});`;
3731
3669
  return [
3732
3670
  ...stops.map((x2) => alias(stopTokenName(x2.stop))),
3733
- alias("stamp-fill"),
3734
- alias("stamp-fill-hover"),
3735
- alias("stamp-fill-pressed"),
3736
- alias("stamp-edge"),
3737
- alias("stamp-on")
3671
+ alias(STAMP_FILL),
3672
+ alias(STAMP_FILL_HOVER),
3673
+ alias(STAMP_FILL_PRESSED),
3674
+ alias(STAMP_EDGE),
3675
+ alias(STAMP_ON)
3738
3676
  ];
3739
3677
  };
3740
3678
  const secondaryOn = (mode) => secondary && secondaryStyle !== "outline" && softOnCtaPasses(secondary, mode) ? "soft" : "solid";
3741
3679
  const secondaryLight = secondary ? brandKindBody(CSS_SECONDARY, secondary, "light", page.light, secondaryOn("light")) : mirrorBody(CSS_SECONDARY, "light");
3742
3680
  const secondaryDark = secondary ? brandKindBody(CSS_SECONDARY, secondary, "dark", page.dark, secondaryOn("dark")) : mirrorBody(CSS_SECONDARY, "dark");
3743
- const brandIdentity = ` --${CSS_BRAND}-identity: ${scale.identityHex};`;
3744
- const secondaryIdentity = secondary ? ` --${CSS_SECONDARY}-identity: ${secondary.identityHex};` : ` --${CSS_SECONDARY}-identity: var(--${CSS_BRAND}-identity);`;
3681
+ const brandIdentity = ` ${cssVarName(absolutePath(CSS_BRAND))}: ${scale.identityHex};`;
3682
+ const secondaryIdentity = secondary ? ` ${cssVarName(absolutePath(CSS_SECONDARY))}: ${secondary.identityHex};` : ` ${cssVarName(absolutePath(CSS_SECONDARY))}: var(${cssVarName(absolutePath(CSS_BRAND))});`;
3745
3683
  const p0hex = (s, fallback) => s ? stopHex(s) : fallback;
3746
- const lightAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0, "#ffffff")};`, ` --${PEN_100}: ${p0hex(nScale.pen100, "#000000")};`];
3747
- const darkAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` --${PEN_100}: ${p0hex(nScale.pen100Dark, "#ffffff")};`];
3684
+ const lightAnchors = [` ${cssVarName(familyPath(CSS_NEUTRAL, PAPER_0))}: ${p0hex(nScale.paper0, "#ffffff")};`, ` ${cssVarName(familyPath(CSS_NEUTRAL, PEN_100))}: ${p0hex(nScale.pen100, "#000000")};`];
3685
+ const darkAnchors = [` ${cssVarName(familyPath(CSS_NEUTRAL, PAPER_0))}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` ${cssVarName(familyPath(CSS_NEUTRAL, PEN_100))}: ${p0hex(nScale.pen100Dark, "#ffffff")};`];
3748
3686
  const linkTrio = linkHex ? resolveLinkTrio(linkHex, contrastProfile) : null;
3749
- const link = (mode) => linkTrio ? mode === "light" ? [` --link: ${stopHex(linkTrio.link)};`, ` --link-hover: ${stopHex(linkTrio.linkHover)};`, ` --link-pressed: ${stopHex(linkTrio.linkPressed)};`] : [` --link: ${stopHex(linkTrio.linkDark)};`, ` --link-hover: ${stopHex(linkTrio.linkHoverDark)};`, ` --link-pressed: ${stopHex(linkTrio.linkPressedDark)};`] : [
3750
- ` --link: var(--${CSS_BRAND}-${stopTokenName(9)});`,
3751
- ` --link-hover: var(--${CSS_BRAND}-${stopTokenName(10)});`,
3752
- ` --link-pressed: var(--${CSS_BRAND}-${stopTokenName(11)});`
3687
+ const linkVar = (state) => cssVarName(linkPath("default", state));
3688
+ 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)};`] : [
3689
+ ` ${linkVar("enabled")}: var(${cssVarName(familyPath(CSS_BRAND, stopTokenName(9)))});`,
3690
+ ` ${linkVar("hover")}: var(${cssVarName(familyPath(CSS_BRAND, stopTokenName(10)))});`,
3691
+ ` ${linkVar("pressed")}: var(${cssVarName(familyPath(CSS_BRAND, stopTokenName(11)))});`
3753
3692
  ];
3754
3693
  const linkP3 = (mode) => {
3755
3694
  if (!linkTrio) return [];
3756
- const trio = mode === "light" ? [["link", linkTrio.link], ["link-hover", linkTrio.linkHover], ["link-pressed", linkTrio.linkPressed]] : [["link", linkTrio.linkDark], ["link-hover", linkTrio.linkHoverDark], ["link-pressed", linkTrio.linkPressedDark]];
3757
- return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => ` --${n}: ${p3Value(s)};`);
3695
+ const trio = mode === "light" ? [["enabled", linkTrio.link], ["hover", linkTrio.linkHover], ["pressed", linkTrio.linkPressed]] : [["enabled", linkTrio.linkDark], ["hover", linkTrio.linkHoverDark], ["pressed", linkTrio.linkPressedDark]];
3696
+ return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => ` ${linkVar(n)}: ${p3Value(s)};`);
3758
3697
  };
3759
3698
  const inverseSeed = linkHex ?? scale.identityHex ?? stopHex(scale.light.find((x2) => x2.stop === 9));
3760
3699
  const linkInverseTrio = resolveLinkInverseTrio(inverseSeed, contrastProfile);
3761
- const linkInverse = (mode) => mode === "light" ? [` --link-inverse: ${stopHex(linkInverseTrio.link)};`, ` --link-inverse-hover: ${stopHex(linkInverseTrio.linkHover)};`, ` --link-inverse-pressed: ${stopHex(linkInverseTrio.linkPressed)};`] : [` --link-inverse: ${stopHex(linkInverseTrio.linkDark)};`, ` --link-inverse-hover: ${stopHex(linkInverseTrio.linkHoverDark)};`, ` --link-inverse-pressed: ${stopHex(linkInverseTrio.linkPressedDark)};`];
3700
+ const inverseVar = (state) => cssVarName(linkPath("inverse", state));
3701
+ 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
3702
  const linkInverseP3 = (mode) => {
3763
- const trio = mode === "light" ? [["link-inverse", linkInverseTrio.link], ["link-inverse-hover", linkInverseTrio.linkHover], ["link-inverse-pressed", linkInverseTrio.linkPressed]] : [["link-inverse", linkInverseTrio.linkDark], ["link-inverse-hover", linkInverseTrio.linkHoverDark], ["link-inverse-pressed", linkInverseTrio.linkPressedDark]];
3764
- return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => ` --${n}: ${p3Value(s)};`);
3703
+ const trio = mode === "light" ? [["enabled", linkInverseTrio.link], ["hover", linkInverseTrio.linkHover], ["pressed", linkInverseTrio.linkPressed]] : [["enabled", linkInverseTrio.linkDark], ["hover", linkInverseTrio.linkHoverDark], ["pressed", linkInverseTrio.linkPressedDark]];
3704
+ return trio.filter(([, s]) => p3Differs(s)).map(([n, s]) => ` ${inverseVar(n)}: ${p3Value(s)};`);
3765
3705
  };
3766
3706
  const escape = (mode) => {
3767
3707
  if (!ctaEscape) return [];
3768
3708
  const esc = escapeCtaFamily(nScale, mode, contrastProfile);
3769
3709
  return [
3770
- ` --${CSS_BRAND}-stamp-fill: ${stopHex(esc.cta)};`,
3771
- ` --${CSS_BRAND}-stamp-fill-hover: ${stopHex(esc.ctaHover)};`,
3772
- ` --${CSS_BRAND}-stamp-fill-pressed: ${stopHex(esc.ctaPressed)};`,
3773
- ` --${CSS_BRAND}-stamp-on: ${onColor(esc.onFillIsWhite)};`
3710
+ ` ${cssVarName(familyPath(CSS_BRAND, STAMP_FILL))}: ${stopHex(esc.cta)};`,
3711
+ ` ${cssVarName(familyPath(CSS_BRAND, STAMP_FILL_HOVER))}: ${stopHex(esc.ctaHover)};`,
3712
+ ` ${cssVarName(familyPath(CSS_BRAND, STAMP_FILL_PRESSED))}: ${stopHex(esc.ctaPressed)};`,
3713
+ ` ${cssVarName(familyPath(CSS_BRAND, STAMP_ON))}: ${onColor(esc.onFillIsWhite)};`
3774
3714
  ];
3775
3715
  };
3776
3716
  const outline = (mode) => {
3777
3717
  if (secondaryStyle !== "outline" || !secondary) return [];
3778
3718
  const s8 = (mode === "light" ? secondary.light : secondary.dark).find((s) => s.stop === 8);
3779
- const c = (v2) => Math.round(Math.min(1, Math.max(0, v2)) * 255);
3719
+ const c = (v3) => Math.round(Math.min(1, Math.max(0, v3)) * 255);
3780
3720
  const s8e = s8 ? srgbEmitChannels(s8) : null;
3721
+ const v2 = (leaf) => cssVarName(familyPath(CSS_SECONDARY, leaf));
3781
3722
  return [
3782
- ` --${CSS_SECONDARY}-stamp-fill: transparent;`,
3723
+ ` ${v2(STAMP_FILL)}: transparent;`,
3783
3724
  ...s8e ? [
3784
- ` --${CSS_SECONDARY}-stamp-fill-hover: rgba(${c(s8e.r)}, ${c(s8e.g)}, ${c(s8e.b)}, ${OUTLINE_HOVER_ALPHA});`,
3785
- ` --${CSS_SECONDARY}-stamp-fill-pressed: rgba(${c(s8e.r)}, ${c(s8e.g)}, ${c(s8e.b)}, ${OUTLINE_PRESSED_ALPHA});`
3725
+ ` ${v2(STAMP_FILL_HOVER)}: rgba(${c(s8e.r)}, ${c(s8e.g)}, ${c(s8e.b)}, ${OUTLINE_HOVER_ALPHA});`,
3726
+ ` ${v2(STAMP_FILL_PRESSED)}: rgba(${c(s8e.r)}, ${c(s8e.g)}, ${c(s8e.b)}, ${OUTLINE_PRESSED_ALPHA});`
3786
3727
  ] : [],
3787
- ` --${CSS_SECONDARY}-stamp-edge: var(--${CSS_SECONDARY}-${stopTokenName(8)});`,
3788
- ` --${CSS_SECONDARY}-stamp-on: var(--${CSS_SECONDARY}-${stopTokenName(9)});`
3728
+ ` ${v2(STAMP_EDGE)}: var(${v2(stopTokenName(8))});`,
3729
+ ` ${v2(STAMP_ON)}: var(${v2(stopTokenName(9))});`
3789
3730
  ];
3790
3731
  };
3791
- const dropOutlineCta = (lines) => secondaryStyle === "outline" ? lines.filter((l) => !l.startsWith(` --${CSS_SECONDARY}-stamp-fill:`) && !l.startsWith(` --${CSS_SECONDARY}-stamp-fill-hover:`) && !l.startsWith(` --${CSS_SECONDARY}-stamp-fill-pressed:`)) : lines;
3792
- const dropEscapeCta = (lines) => ctaEscape ? lines.filter((l) => !new RegExp(`^ --${CSS_BRAND}-stamp-fill(-hover|-pressed)?:`).test(l)) : lines;
3732
+ 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;
3733
+ 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
3734
  const p3Light = [
3794
3735
  ...dropEscapeCta(brandKindP3Body(CSS_BRAND, scale, "light")),
3795
3736
  ...secondary ? dropOutlineCta(brandKindP3Body(CSS_SECONDARY, secondary, "light")) : [],
@@ -3897,17 +3838,6 @@ function resolveReferences(own, shared) {
3897
3838
  for (const name of /* @__PURE__ */ new Set([...own.keys(), ...shared.keys()])) out[name] = resolve(lookup(name), 0);
3898
3839
  return out;
3899
3840
  }
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
3841
  function tokensFromEmission(slug, css) {
3912
3842
  const { light, dark, order } = readEmission(css);
3913
3843
  const resolvedLight = resolveReferences(light, /* @__PURE__ */ new Map());
@@ -3919,7 +3849,6 @@ function tokensFromEmission(slug, css) {
3919
3849
  function themeTokens(input) {
3920
3850
  const css = [
3921
3851
  signalsCss(input.contrastProfile),
3922
- systemCss(),
3923
3852
  brandCss(
3924
3853
  input.slug,
3925
3854
  input.displayName ?? input.slug,
@@ -3938,168 +3867,35 @@ function themeTokens(input) {
3938
3867
  return tokensFromEmission(input.slug, css);
3939
3868
  }
3940
3869
 
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
3870
  // src/engine/figmaRender.ts
4070
- var clamp01 = (v2) => Math.min(1, Math.max(0, v2));
4071
- var TRANSPARENT_TOKEN = {
4072
- $type: "color",
4073
- $value: { colorSpace: "srgb", components: [0, 0, 0], alpha: 0, hex: "#000000" }
4074
- };
3871
+ var channel = (v2) => Math.round(Math.round(Math.min(1, Math.max(0, v2)) * 255) / 255 * 1e4) / 1e4;
3872
+ function color(r, g2, b, alpha = 1) {
3873
+ return { $type: "color", $value: { colorSpace: "srgb", components: [channel(r), channel(g2), channel(b)], alpha, hex: toHex2(r, g2, b) } };
3874
+ }
3875
+ var TRANSPARENT_TOKEN = color(0, 0, 0, 0);
4075
3876
  var OFFSET_TOKEN = (rung, mode) => {
4076
3877
  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
- };
3878
+ return color(c, c, c, OFFSET_ALPHAS[rung]);
4081
3879
  };
4082
3880
  function colorFromStop(s) {
4083
3881
  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" } };
3882
+ return color(r, g2, b);
4092
3883
  }
3884
+ var pole = (white, alpha = 1) => white ? color(1, 1, 1, alpha) : color(0, 0, 0, alpha);
4093
3885
  function colorFromHexString(hex) {
4094
3886
  const h = hex.replace("#", "");
4095
- const r = parseInt(h.slice(0, 2), 16) / 255;
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() } };
3887
+ return color(parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255);
4099
3888
  }
4100
3889
  function bandedLeaf(flat) {
4101
3890
  return STAMP_STATE_LEAVES[flat] ?? flat;
4102
3891
  }
3892
+ function groupEntries(g2) {
3893
+ const entries = Object.entries(g2);
3894
+ const digitLeading = (k) => /^\d/.test(k);
3895
+ if (entries.length > 1 && entries.every(([k]) => digitLeading(k)))
3896
+ return entries.sort((a, b) => parseInt(b[0], 10) - parseInt(a[0], 10));
3897
+ return entries;
3898
+ }
4103
3899
  function putLeaf(g2, flat, tok) {
4104
3900
  const path = bandedLeaf(flat).split("/");
4105
3901
  let cur = g2;
@@ -4117,8 +3913,7 @@ function rampGroup(stops, onFillWhite, extra) {
4117
3913
  if (extra?.ctaHover) putLeaf(g2, STAMP_FILL_HOVER, colorFromStop(extra.ctaHover));
4118
3914
  if (extra?.ctaPressed) putLeaf(g2, STAMP_FILL_PRESSED, colorFromStop(extra.ctaPressed));
4119
3915
  if (extra?.cta) putLeaf(g2, STAMP_EDGE, extra.ctaBorder ?? TRANSPARENT_TOKEN);
4120
- putLeaf(g2, STAMP_ON, colorFromHex(onFillWhite));
4121
- if (extra?.identityHex) g2["identity"] = colorFromHexString(extra.identityHex);
3916
+ putLeaf(g2, STAMP_ON, pole(onFillWhite));
4122
3917
  return g2;
4123
3918
  }
4124
3919
  function themeToFigma(r, input) {
@@ -4131,10 +3926,6 @@ function themeToFigma(r, input) {
4131
3926
  ctaBorder: ctaNeedsBorder(s, mode, borderPage(mode)) ? OFFSET_TOKEN(ctaBorderRung(prefix), mode) : TRANSPARENT_TOKEN,
4132
3927
  ...mode === "light" ? { cta: s.cta, ctaHover: s.ctaHover, ctaPressed: s.ctaPressed } : { cta: s.ctaDark, ctaHover: s.ctaHoverDark, ctaPressed: s.ctaPressedDark }
4133
3928
  });
4134
- const brandExtra = (s, mode, prefix) => ({
4135
- identityHex: s.identityHex,
4136
- ...ctaFamily(s, mode, prefix)
4137
- });
4138
3929
  const nScale = generateNeutralScale(input.neutralH ?? scale.brandH, input.neutralLevel ?? "default", input.contrastProfile);
4139
3930
  const lt = input.linkHex ? resolveLinkTrio(input.linkHex, input.contrastProfile) : null;
4140
3931
  const invSeed = input.linkHex ?? scale.identityHex ?? (() => {
@@ -4143,10 +3934,9 @@ function themeToFigma(r, input) {
4143
3934
  return toHex2(e.r, e.g, e.b);
4144
3935
  })();
4145
3936
  const invLt = resolveLinkInverseTrio(invSeed, input.contrastProfile);
4146
- const neutralExtra = (mode) => ctaFamily(nScale, mode, CSS_FAMILY.neutral);
4147
3937
  const build = (mode) => {
4148
3938
  const p02 = mode === "light" ? nScale.paper0 : nScale.paper0Dark;
4149
- const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, neutralExtra(mode));
3939
+ const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, ctaFamily(nScale, mode, CSS_FAMILY.neutral));
4150
3940
  const i02 = mode === "light" ? nScale.pen100 : nScale.pen100Dark;
4151
3941
  const splicePen100 = (g3) => {
4152
3942
  if (!i02) return g3;
@@ -4158,41 +3948,31 @@ function themeToFigma(r, input) {
4158
3948
  return out;
4159
3949
  };
4160
3950
  const neutralGroup = splicePen100(p02 ? { [PAPER_0]: colorFromStop(p02), ...ramp } : ramp);
4161
- const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, brandExtra(secondary, mode, CSS_FAMILY.brandSecondary));
3951
+ const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, ctaFamily(secondary, mode, CSS_FAMILY.brandSecondary));
4162
3952
  if (input.secondaryStyle === "outline" && input.secondary) {
4163
3953
  const s8 = secondary[mode].find((s) => s.stop === 8);
4164
3954
  const s9 = secondary[mode].find((s) => s.stop === 9);
4165
3955
  putLeaf(secondaryGroup, STAMP_FILL, TRANSPARENT_TOKEN);
4166
3956
  if (s8) {
4167
3957
  const e = srgbEmitChannels(s8);
4168
- const alphaTint = (alpha) => ({
4169
- $type: "color",
4170
- $value: { colorSpace: "srgb", components: [clamp01(e.r), clamp01(e.g), clamp01(e.b)], alpha, hex: toHex2(e.r, e.g, e.b) }
4171
- });
4172
- putLeaf(secondaryGroup, STAMP_FILL_HOVER, alphaTint(OUTLINE_HOVER_ALPHA));
4173
- putLeaf(secondaryGroup, STAMP_FILL_PRESSED, alphaTint(OUTLINE_PRESSED_ALPHA));
3958
+ putLeaf(secondaryGroup, STAMP_FILL_HOVER, color(e.r, e.g, e.b, OUTLINE_HOVER_ALPHA));
3959
+ putLeaf(secondaryGroup, STAMP_FILL_PRESSED, color(e.r, e.g, e.b, OUTLINE_PRESSED_ALPHA));
3960
+ putLeaf(secondaryGroup, STAMP_EDGE, colorFromStop(s8));
4174
3961
  }
4175
- if (s8) putLeaf(secondaryGroup, STAMP_EDGE, colorFromStop(s8));
4176
3962
  if (s9) putLeaf(secondaryGroup, STAMP_ON, colorFromStop(s9));
4177
3963
  }
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
- };
3964
+ const softOnCta = (g3, white) => putLeaf(g3, STAMP_ON, pole(white, SOFT_ON_CTA_ALPHA[mode]));
4185
3965
  softOnCta(neutralGroup, mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark);
4186
3966
  if (input.secondary && input.secondaryStyle !== "outline" && softOnCtaPasses(input.secondary, mode))
4187
3967
  softOnCta(secondaryGroup, mode === "light" ? secondaryOnFillLight : secondaryOnFillDark);
4188
- const brandGroup = rampGroup(scale[mode], mode === "light" ? scale.onFillTextIsWhite : scale.onFillTextIsWhiteDark, brandExtra(scale, mode, CSS_FAMILY.brandPrimary));
3968
+ const brandGroup = rampGroup(scale[mode], mode === "light" ? scale.onFillTextIsWhite : scale.onFillTextIsWhiteDark, ctaFamily(scale, mode, CSS_FAMILY.brandPrimary));
4189
3969
  const esc = input.ctaEscape ? escapeCtaFamily(nScale, mode, input.contrastProfile) : null;
4190
3970
  if (esc) {
4191
3971
  for (const g3 of input.secondary ? [brandGroup] : [brandGroup, secondaryGroup]) {
4192
3972
  putLeaf(g3, STAMP_FILL, colorFromStop(esc.cta));
4193
3973
  putLeaf(g3, STAMP_FILL_HOVER, colorFromStop(esc.ctaHover));
4194
3974
  putLeaf(g3, STAMP_FILL_PRESSED, colorFromStop(esc.ctaPressed));
4195
- putLeaf(g3, STAMP_ON, colorFromHex(esc.onFillIsWhite));
3975
+ putLeaf(g3, STAMP_ON, pole(esc.onFillIsWhite));
4196
3976
  }
4197
3977
  }
4198
3978
  const scaleTextAt = (n) => {
@@ -4200,121 +3980,107 @@ function themeToFigma(r, input) {
4200
3980
  if (!s) throw new Error(`themeToFigma link: the brand scale has no pen stop ${n}`);
4201
3981
  return s;
4202
3982
  };
4203
- const linkGroup = lt ? mode === "light" ? { "link": colorFromStop(lt.link), "link-hover": colorFromStop(lt.linkHover), "link-pressed": colorFromStop(lt.linkPressed) } : { "link": colorFromStop(lt.linkDark), "link-hover": colorFromStop(lt.linkHoverDark), "link-pressed": colorFromStop(lt.linkPressedDark) } : { "link": colorFromStop(scaleTextAt(9)), "link-hover": colorFromStop(scaleTextAt(10)), "link-pressed": colorFromStop(scaleTextAt(11)) };
4204
- const linkInverseGroup = mode === "light" ? { "link": colorFromStop(invLt.link), "link-hover": colorFromStop(invLt.linkHover), "link-pressed": colorFromStop(invLt.linkPressed) } : { "link": colorFromStop(invLt.linkDark), "link-hover": colorFromStop(invLt.linkHoverDark), "link-pressed": colorFromStop(invLt.linkPressedDark) };
3983
+ const trio = (posture) => {
3984
+ 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];
3985
+ return Object.fromEntries(LINK_STATES.map((state, i) => [state, colorFromStop(t[i])]));
3986
+ };
4205
3987
  const g2 = {
4206
- brand: brandGroup,
4207
- secondary: secondaryGroup,
4208
- neutral: neutralGroup,
4209
- link: linkGroup,
4210
- "link-inverse": linkInverseGroup
3988
+ [CSS_FAMILY.neutral]: neutralGroup,
3989
+ [CSS_FAMILY.brandPrimary]: brandGroup,
3990
+ [CSS_FAMILY.brandSecondary]: secondaryGroup
4211
3991
  };
4212
3992
  for (const sig of input.signals) {
4213
- g2[sig.name] = rampGroup(
3993
+ const role = SIGNAL_EMIT_NAME[sig.name] ?? sig.name;
3994
+ g2[role] = rampGroup(
4214
3995
  sig.scale[mode],
4215
3996
  mode === "light" ? sig.scale.onFillTextIsWhite : sig.scale.onFillTextIsWhiteDark,
4216
- // signals rank with the primary (ctaBorderRung: anything not neutral/secondary takes 16).
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)
3997
+ ctaFamily(sig.scale, mode, role)
4219
3998
  );
4220
3999
  }
4221
- const pole = (white, alpha) => {
4222
- const c = white ? 1 : 0;
4223
- return {
4224
- $type: "color",
4225
- $value: { colorSpace: "srgb", components: [c, c, c], alpha, hex: white ? "#ffffff" : "#000000" }
4226
- };
4000
+ g2[LINK_GROUP] = { default: trio("default"), inverse: trio("inverse") };
4001
+ g2[ABSOLUTE_GROUP] = {
4002
+ [CSS_FAMILY.brandPrimary]: colorFromHexString(scale.identityHex ?? invSeed),
4003
+ [CSS_FAMILY.brandSecondary]: colorFromHexString((input.secondary ?? scale).identityHex ?? invSeed)
4227
4004
  };
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
4005
  return g2;
4249
4006
  };
4250
4007
  return { light: build("light"), dark: build("dark") };
4251
4008
  }
4252
4009
 
4253
- // src/engine/requirements/dtcg.ts
4254
- var EXT_KEY = "org.okchroma.requirement";
4255
- var RESOLVER_ID = "okchroma-reqtoken@2";
4256
- var LEGACY_ROLE_NAMES = {
4257
- "cta": STAMP_FILL,
4258
- "cta-hover": STAMP_FILL_HOVER,
4259
- "cta-pressed": STAMP_FILL_PRESSED
4260
- };
4261
- var hexToValue = (hex) => {
4262
- const h = hex.replace("#", "");
4263
- const c = (i) => Math.round(parseInt(h.slice(i, i + 2), 16) / 255 * 1e4) / 1e4;
4264
- return { colorSpace: "srgb", components: [c(0), c(2), c(4)], alpha: 1, hex: hex.toLowerCase() };
4265
- };
4266
- function emitDtcgRamp(hex, mode, groupName, spec) {
4267
- spec ?? (spec = MODE_SPECS[mode]);
4268
- const ramp = resolveRamp(hex, mode, spec);
4269
- const seedRef = `{${groupName}.seed}`;
4270
- const group = {
4271
- seed: { $type: "color", $value: hexToValue(hex) },
4272
- $extensions: { [EXT_KEY]: { resolver: RESOLVER_ID, ons: spec.ons } }
4273
- };
4274
- for (const sp of spec.stops) {
4275
- const st = ramp.stops.find((s) => s.stop === sp.stop);
4276
- group[String(sp.stop)] = { $type: "color", $value: hexToValue(st.hex), $extensions: { [EXT_KEY]: { resolver: RESOLVER_ID, seed: seedRef, mode, ...sp } } };
4277
- }
4278
- const roleVal = {
4279
- [STAMP_FILL]: ramp.roles.cta,
4280
- [STAMP_FILL_HOVER]: ramp.roles.ctaHover,
4281
- [STAMP_FILL_PRESSED]: ramp.roles.ctaPressed
4010
+ // src/engine/dtcgRender.ts
4011
+ var STAMP = [STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED, STAMP_EDGE, STAMP_ON];
4012
+ var STOPS = Array.from({ length: SCALE_STOP_COUNT }, (_, i) => stopTokenName(i + 1));
4013
+ function tokenPaths() {
4014
+ const family = (word, poles) => [
4015
+ ...poles ? [familyPath(word, PAPER_0)] : [],
4016
+ ...STOPS.map((leaf) => familyPath(word, leaf)),
4017
+ ...poles ? [familyPath(word, PEN_100)] : [],
4018
+ ...STAMP.map((leaf) => familyPath(word, leaf))
4019
+ ];
4020
+ return [
4021
+ ...family(CSS_FAMILY.neutral, true),
4022
+ ...family(CSS_FAMILY.brandPrimary, false),
4023
+ ...family(CSS_FAMILY.brandSecondary, false),
4024
+ ...SIGNALS2.flatMap((s) => family(s.emitName, false)),
4025
+ ...LINK_POSTURES.flatMap((posture) => LINK_STATES.map((state) => linkPath(posture, state))),
4026
+ absolutePath(CSS_FAMILY.brandPrimary),
4027
+ absolutePath(CSS_FAMILY.brandSecondary)
4028
+ ];
4029
+ }
4030
+ var byName = () => new Map(tokenPaths().map((p) => [cssVarName(p).slice(2), p]));
4031
+ function tokenPathOf(name) {
4032
+ return byName().get(name);
4033
+ }
4034
+ function descriptionPathOf(name) {
4035
+ const p = tokenPathOf(name);
4036
+ return p && figmaPathOf(p);
4037
+ }
4038
+ var component = (c8) => Math.round(c8 / 255 * 1e4) / 1e4;
4039
+ var colorValue = (r, g2, b, alpha) => ({
4040
+ colorSpace: "srgb",
4041
+ components: [component(r), component(g2), component(b)],
4042
+ alpha,
4043
+ hex: "#" + [r, g2, b].map((c) => c.toString(16).padStart(2, "0")).join("")
4044
+ });
4045
+ function dtcgColor(value) {
4046
+ let m;
4047
+ 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);
4048
+ if (m = /^rgba\((\d+), (\d+), (\d+), ([0-9.]+)\)$/.exec(value)) return colorValue(+m[1], +m[2], +m[3], +m[4]);
4049
+ if (value === "transparent") return colorValue(0, 0, 0, 0);
4050
+ throw new Error(`dtcgColor: not a color the emitters write: ${value}`);
4051
+ }
4052
+ var ALIAS = /^var\(--([a-z0-9-]+)\)$/;
4053
+ function tokensToDtcg(tokens) {
4054
+ const paths = byName();
4055
+ const roster = new Set(paths.keys());
4056
+ 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}`);
4057
+ for (const name of roster) if (!(name in tokens.light)) throw new Error(`tokensToDtcg: the emit lacks ${name}`);
4058
+ const build = (mode) => {
4059
+ const doc = {};
4060
+ for (const [name, path] of paths) {
4061
+ const raw = tokens.raw[mode][name] ?? tokens.raw.light[name];
4062
+ const alias = ALIAS.exec(raw);
4063
+ let token;
4064
+ if (alias) {
4065
+ const target = paths.get(alias[1]);
4066
+ if (!target) throw new Error(`tokensToDtcg: ${name} references a name outside the roster: ${alias[1]}`);
4067
+ token = { $type: "color", $value: `{${target.join(".")}}` };
4068
+ } else {
4069
+ token = { $type: "color", $value: dtcgColor(tokens[mode][name]) };
4070
+ }
4071
+ const body = describeDocument(figmaPathOf(path));
4072
+ if (body) token.$description = body;
4073
+ let cur = doc;
4074
+ for (const seg of path.slice(0, -1)) {
4075
+ const next = cur[seg];
4076
+ if (next && "$type" in next) throw new Error(`tokensToDtcg: ${seg} is both a token and a group on the way to ${path.join("/")}`);
4077
+ cur = cur[seg] ?? (cur[seg] = {});
4078
+ }
4079
+ const leaf = path[path.length - 1];
4080
+ if (leaf in cur) throw new Error(`tokensToDtcg: duplicate path ${path.join("/")}`);
4081
+ cur[leaf] = token;
4082
+ }
4083
+ return doc;
4282
4084
  };
4283
- for (const rr of spec.roles) {
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 });
4085
+ return { light: build("light"), dark: build("dark") };
4320
4086
  }