okchroma 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ CSS_FAMILY: () => CSS_FAMILY,
23
24
  DEFAULT_LINK_HEX: () => DEFAULT_LINK_HEX,
24
25
  EXT_KEY: () => EXT_KEY,
25
26
  INK_0: () => INK_0,
@@ -46,11 +47,14 @@ __export(index_exports, {
46
47
  checkCollision: () => checkCollision,
47
48
  checkHueCollision: () => checkHueCollision,
48
49
  classifyArchetype: () => classifyArchetype,
50
+ ctaBorderRung: () => ctaBorderRung,
51
+ ctaNeedsBorder: () => ctaNeedsBorder,
49
52
  emitDtcgRamp: () => emitDtcgRamp,
50
53
  generateNeutralScale: () => generateNeutralScale,
51
54
  generateScale: () => generateScale,
52
55
  generateSubtleSecondary: () => generateSubtleSecondary,
53
56
  neutralCss: () => neutralCss,
57
+ pageStopFor: () => pageStopFor,
54
58
  parseToken: () => parseToken,
55
59
  resolveBrand: () => resolveBrand,
56
60
  resolveDtcgRamp: () => resolveDtcgRamp,
@@ -90,9 +94,11 @@ function stateStepL(restL, _mode, k) {
90
94
  return k * STATE_HOVER_K;
91
95
  }
92
96
  var OVERRIDE_LIGHT_BELOW = ARCHETYPES.find((a) => a.name === "rich").min;
97
+ var OVERRIDE_DARK_ABOVE = ARCHETYPES.find((a) => a.name === "light").min;
93
98
  function stateFillL(restL, mode, k) {
94
99
  const natural = mode === "dark" ? 1 : -1;
95
- const preferred = mode === "light" && restL < OVERRIDE_LIGHT_BELOW ? -natural : natural;
100
+ const overridden = mode === "light" ? restL < OVERRIDE_LIGHT_BELOW : restL > OVERRIDE_DARK_ABOVE;
101
+ const preferred = overridden ? -natural : natural;
96
102
  const fits = (d) => {
97
103
  const p = restL + d * stateStepL(restL, mode, 2);
98
104
  return p >= STATE_L_MIN && p <= STATE_L_MAX;
@@ -2619,7 +2625,7 @@ function generateNeutralScale(brandH, level = "default", contrastProfile) {
2619
2625
  scale.ctaHoverDark = nState(10, "dark", scale.ctaDark, 1);
2620
2626
  scale.ctaPressedDark = nState(11, "dark", scale.ctaDark, 2);
2621
2627
  const onEnforce = contrastProfile !== "apca";
2622
- const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
2628
+ const onFloor = 4.5;
2623
2629
  scale.onFillTextIsWhite = onTextIsWhite(apcaY(scale.cta.r, scale.cta.g, scale.cta.b), scale.cta.L, scale.cta.C, scale.cta.H, onEnforce, onFloor);
2624
2630
  scale.onFillTextIsWhiteDark = onTextIsWhite(apcaY(scale.ctaDark.r, scale.ctaDark.g, scale.ctaDark.b), scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, onEnforce, onFloor);
2625
2631
  return scale;
@@ -2650,7 +2656,7 @@ function generateSubtleSecondary(hex, opts) {
2650
2656
  scale.ctaHoverDark = st(10, "dark", scale.ctaDark, 1);
2651
2657
  scale.ctaPressedDark = st(11, "dark", scale.ctaDark, 2);
2652
2658
  const onEnforce = opts?.contrastProfile !== "apca";
2653
- const onFloor = opts?.contrastProfile === "apca" ? void 0 : 4.5;
2659
+ const onFloor = 4.5;
2654
2660
  scale.onFillTextIsWhite = onTextIsWhite(apcaY(scale.cta.r, scale.cta.g, scale.cta.b), scale.cta.L, scale.cta.C, scale.cta.H, onEnforce, onFloor);
2655
2661
  scale.onFillTextIsWhiteDark = onTextIsWhite(apcaY(scale.ctaDark.r, scale.ctaDark.g, scale.ctaDark.b), scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, onEnforce, onFloor);
2656
2662
  return scale;
@@ -2695,7 +2701,7 @@ var COLOR_WORD = {
2695
2701
  [FAMILY.info]: " (blue)"
2696
2702
  };
2697
2703
  var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
2698
- var WASH = { req: "subtle interaction states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2704
+ var WASH = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2699
2705
  var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
2700
2706
  var OVERLAY = {
2701
2707
  req: "translucent backgrounds that hold their reading on any paper",
@@ -2713,12 +2719,15 @@ var SCALE = {
2713
2719
  "wash-89": WASH,
2714
2720
  "wash-85": WASH,
2715
2721
  "wash-80": WASH,
2722
+ // ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
2723
+ // landing on the mark rows is worth its ic-ON-s noise — the one surviving body carrier)
2716
2724
  "mark-74": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
2717
2725
  "lead-53": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2718
2726
  "ink-42": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2719
2727
  // ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
2720
- // words, and a body carrying either floods that token's picker search — the C50 law)
2721
- "ink-30": { req: "strong-emphasis text, inverted backgrounds", contrast: AAA_BODY, theming: solved, collides: true },
2728
+ // words, and a body carrying either floods that token's picker search — the C50 law;
2729
+ // "strong-emphasis" reworded 2026-08-28: str-ON-g fed the "on" flood)
2730
+ "ink-30": { req: "heavy-emphasis text, inverted backgrounds", contrast: AAA_BODY, theming: solved, collides: true },
2722
2731
  // the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
2723
2732
  // these bodies on purpose: it stopped being a token label, so it floods nothing,
2724
2733
  // and a designer's "cta" query still lands on these rows.
@@ -2729,17 +2738,17 @@ var SCALE = {
2729
2738
  // ("fill" reworded out 2026-08-18: fill became a label word and is foreign here)
2730
2739
  "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]}` }
2731
2740
  };
2732
- var ABS = { req: "max contrast on CTAs, aliased global endpoints" };
2733
- var OFFSET = { req: "min APCA visibility", theming: "offsets buttons in themes whose CTAs sit close to the page" };
2741
+ var ABS = { req: "extreme poles for CTAs, aliased global endpoints" };
2742
+ var OFFSET = { req: "min APCA visibility", theming: "offsets CTAs in themes where they sit close to the page" };
2734
2743
  var SHADOW = { req: "drop shadows" };
2735
2744
  var PLANE = (req) => ({ req, theming: "aliased to the gray ramp" });
2736
2745
  var LINK = (state, contrast) => ({
2737
2746
  req: "links" + state,
2738
2747
  contrast,
2739
- theming: "rides the theme\u2019s text action; custom seed re-solves; overridable per theme"
2748
+ theming: "rides the theme\u2019s link color; custom seed re-solves; overridable per theme"
2740
2749
  });
2741
2750
  var LINK_INVERSE = (state, contrast) => ({
2742
- req: "links on inverted backgrounds" + state,
2751
+ req: "links over inverted backgrounds" + state,
2743
2752
  contrast,
2744
2753
  theming: "same seed as the link, re-solved for inverted backgrounds; overridable per theme"
2745
2754
  });
@@ -2750,19 +2759,23 @@ var SYSTEM = {
2750
2759
  // the anchor is engine-resolved and rides the neutral in BOTH plugins now —
2751
2760
  // NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
2752
2761
  "system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
2753
- "system/abs-alt": { req: "identity seed reference", theming: "the theme\u2019s companion input, as given" },
2762
+ // ("companion" reworded 2026-08-28: compani-ON fed the "on" flood)
2763
+ "system/abs-alt": { req: "identity seed reference", theming: "the theme\u2019s paired input, as given" },
2754
2764
  "system/alpha/transparent": { req: "aliased off-states" },
2755
2765
  // ("dimming" reworded 2026-08-18: the surface planes took dim as a label word)
2756
2766
  "system/alpha/abs-black-060": { req: "veils the page behind modals" },
2757
- // ("fills" reworded 2026-08-18: fill became a label word and is foreign here)
2758
- "system/alpha/ink": { req: "soft on-color for quiet CTAs" },
2767
+ // ("fills" reworded 2026-08-18: fill became a label word and is foreign here;
2768
+ // "on-color" reworded 2026-08-28: the bare on fed the "on" flood)
2769
+ "system/alpha/ink": { req: "soft text pole for quiet CTAs" },
2759
2770
  "system/alpha/006": OFFSET,
2760
2771
  "system/alpha/008": OFFSET,
2761
2772
  "system/alpha/016": OFFSET,
2762
2773
  "system/alpha/shadow-04": SHADOW,
2763
2774
  "system/alpha/shadow-08": SHADOW,
2764
2775
  "system/alpha/shadow-12": SHADOW,
2765
- "system/surface/dim": PLANE("recessed elevation plane"),
2776
+ // ("elevation" dropped 2026-08-28: elevati-ON fed the "on" flood; the sibling
2777
+ // planes never carried the word)
2778
+ "system/surface/dim": PLANE("recessed plane"),
2766
2779
  "system/surface/low": PLANE("resting page plane"),
2767
2780
  "system/surface/mid": PLANE("raised plane \u2014 cards, menus"),
2768
2781
  // ("overlays" reworded 2026-08-13: paper-overlay took the word as a label — the C50 law)
@@ -3627,7 +3640,7 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
3627
3640
  };
3628
3641
  const softOnCta = (mode) => {
3629
3642
  if (!secondary || secondaryStyle === "outline") return [];
3630
- if (secondaryStyle !== "default" && !softOnCtaPasses(secondary, mode)) return [];
3643
+ if (!softOnCtaPasses(secondary, mode)) return [];
3631
3644
  const white = mode === "light" ? secondary.onFillTextIsWhite : secondary.onFillTextIsWhiteDark;
3632
3645
  return [` --${CSS_SECONDARY}-stamp-on: rgba(${white ? "255, 255, 255" : "0, 0, 0"}, ${SOFT_ON_CTA_ALPHA[mode]});`];
3633
3646
  };
@@ -3820,7 +3833,7 @@ function themeToFigma(r, input) {
3820
3833
  });
3821
3834
  };
3822
3835
  softOnCta(neutralGroup, mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark);
3823
- if (input.secondary && input.secondaryStyle !== "outline" && (input.secondaryStyle === "default" || softOnCtaPasses(input.secondary, mode)))
3836
+ if (input.secondary && input.secondaryStyle !== "outline" && softOnCtaPasses(input.secondary, mode))
3824
3837
  softOnCta(secondaryGroup, mode === "light" ? secondaryOnFillLight : secondaryOnFillDark);
3825
3838
  const brandGroup = rampGroup(scale[mode], mode === "light" ? scale.onFillTextIsWhite : scale.onFillTextIsWhiteDark, brandExtra(scale, mode, CSS_FAMILY.brandPrimary));
3826
3839
  const esc = input.ctaEscape ? escapeCtaFamily(nScale, mode, input.contrastProfile) : null;
@@ -20,9 +20,11 @@ function stateStepL(restL, _mode, k) {
20
20
  return k * STATE_HOVER_K;
21
21
  }
22
22
  var OVERRIDE_LIGHT_BELOW = ARCHETYPES.find((a) => a.name === "rich").min;
23
+ var OVERRIDE_DARK_ABOVE = ARCHETYPES.find((a) => a.name === "light").min;
23
24
  function stateFillL(restL, mode, k) {
24
25
  const natural = mode === "dark" ? 1 : -1;
25
- const preferred = mode === "light" && restL < OVERRIDE_LIGHT_BELOW ? -natural : natural;
26
+ const overridden = mode === "light" ? restL < OVERRIDE_LIGHT_BELOW : restL > OVERRIDE_DARK_ABOVE;
27
+ const preferred = overridden ? -natural : natural;
26
28
  const fits = (d) => {
27
29
  const p = restL + d * stateStepL(restL, mode, 2);
28
30
  return p >= STATE_L_MIN && p <= STATE_L_MAX;
@@ -2549,7 +2551,7 @@ function generateNeutralScale(brandH, level = "default", contrastProfile) {
2549
2551
  scale.ctaHoverDark = nState(10, "dark", scale.ctaDark, 1);
2550
2552
  scale.ctaPressedDark = nState(11, "dark", scale.ctaDark, 2);
2551
2553
  const onEnforce = contrastProfile !== "apca";
2552
- const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
2554
+ const onFloor = 4.5;
2553
2555
  scale.onFillTextIsWhite = onTextIsWhite(apcaY(scale.cta.r, scale.cta.g, scale.cta.b), scale.cta.L, scale.cta.C, scale.cta.H, onEnforce, onFloor);
2554
2556
  scale.onFillTextIsWhiteDark = onTextIsWhite(apcaY(scale.ctaDark.r, scale.ctaDark.g, scale.ctaDark.b), scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, onEnforce, onFloor);
2555
2557
  return scale;
@@ -2580,7 +2582,7 @@ function generateSubtleSecondary(hex, opts) {
2580
2582
  scale.ctaHoverDark = st(10, "dark", scale.ctaDark, 1);
2581
2583
  scale.ctaPressedDark = st(11, "dark", scale.ctaDark, 2);
2582
2584
  const onEnforce = opts?.contrastProfile !== "apca";
2583
- const onFloor = opts?.contrastProfile === "apca" ? void 0 : 4.5;
2585
+ const onFloor = 4.5;
2584
2586
  scale.onFillTextIsWhite = onTextIsWhite(apcaY(scale.cta.r, scale.cta.g, scale.cta.b), scale.cta.L, scale.cta.C, scale.cta.H, onEnforce, onFloor);
2585
2587
  scale.onFillTextIsWhiteDark = onTextIsWhite(apcaY(scale.ctaDark.r, scale.ctaDark.g, scale.ctaDark.b), scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, onEnforce, onFloor);
2586
2588
  return scale;
@@ -2625,7 +2627,7 @@ var COLOR_WORD = {
2625
2627
  [FAMILY.info]: " (blue)"
2626
2628
  };
2627
2629
  var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
2628
- var WASH = { req: "subtle interaction states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2630
+ var WASH = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2629
2631
  var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
2630
2632
  var OVERLAY = {
2631
2633
  req: "translucent backgrounds that hold their reading on any paper",
@@ -2643,12 +2645,15 @@ var SCALE = {
2643
2645
  "wash-89": WASH,
2644
2646
  "wash-85": WASH,
2645
2647
  "wash-80": WASH,
2648
+ // ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
2649
+ // landing on the mark rows is worth its ic-ON-s noise — the one surviving body carrier)
2646
2650
  "mark-74": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
2647
2651
  "lead-53": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2648
2652
  "ink-42": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2649
2653
  // ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
2650
- // words, and a body carrying either floods that token's picker search — the C50 law)
2651
- "ink-30": { req: "strong-emphasis text, inverted backgrounds", contrast: AAA_BODY, theming: solved, collides: true },
2654
+ // words, and a body carrying either floods that token's picker search — the C50 law;
2655
+ // "strong-emphasis" reworded 2026-08-28: str-ON-g fed the "on" flood)
2656
+ "ink-30": { req: "heavy-emphasis text, inverted backgrounds", contrast: AAA_BODY, theming: solved, collides: true },
2652
2657
  // the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
2653
2658
  // these bodies on purpose: it stopped being a token label, so it floods nothing,
2654
2659
  // and a designer's "cta" query still lands on these rows.
@@ -2659,17 +2664,17 @@ var SCALE = {
2659
2664
  // ("fill" reworded out 2026-08-18: fill became a label word and is foreign here)
2660
2665
  "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]}` }
2661
2666
  };
2662
- var ABS = { req: "max contrast on CTAs, aliased global endpoints" };
2663
- var OFFSET = { req: "min APCA visibility", theming: "offsets buttons in themes whose CTAs sit close to the page" };
2667
+ var ABS = { req: "extreme poles for CTAs, aliased global endpoints" };
2668
+ var OFFSET = { req: "min APCA visibility", theming: "offsets CTAs in themes where they sit close to the page" };
2664
2669
  var SHADOW = { req: "drop shadows" };
2665
2670
  var PLANE = (req) => ({ req, theming: "aliased to the gray ramp" });
2666
2671
  var LINK = (state, contrast) => ({
2667
2672
  req: "links" + state,
2668
2673
  contrast,
2669
- theming: "rides the theme\u2019s text action; custom seed re-solves; overridable per theme"
2674
+ theming: "rides the theme\u2019s link color; custom seed re-solves; overridable per theme"
2670
2675
  });
2671
2676
  var LINK_INVERSE = (state, contrast) => ({
2672
- req: "links on inverted backgrounds" + state,
2677
+ req: "links over inverted backgrounds" + state,
2673
2678
  contrast,
2674
2679
  theming: "same seed as the link, re-solved for inverted backgrounds; overridable per theme"
2675
2680
  });
@@ -2680,19 +2685,23 @@ var SYSTEM = {
2680
2685
  // the anchor is engine-resolved and rides the neutral in BOTH plugins now —
2681
2686
  // NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
2682
2687
  "system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
2683
- "system/abs-alt": { req: "identity seed reference", theming: "the theme\u2019s companion input, as given" },
2688
+ // ("companion" reworded 2026-08-28: compani-ON fed the "on" flood)
2689
+ "system/abs-alt": { req: "identity seed reference", theming: "the theme\u2019s paired input, as given" },
2684
2690
  "system/alpha/transparent": { req: "aliased off-states" },
2685
2691
  // ("dimming" reworded 2026-08-18: the surface planes took dim as a label word)
2686
2692
  "system/alpha/abs-black-060": { req: "veils the page behind modals" },
2687
- // ("fills" reworded 2026-08-18: fill became a label word and is foreign here)
2688
- "system/alpha/ink": { req: "soft on-color for quiet CTAs" },
2693
+ // ("fills" reworded 2026-08-18: fill became a label word and is foreign here;
2694
+ // "on-color" reworded 2026-08-28: the bare on fed the "on" flood)
2695
+ "system/alpha/ink": { req: "soft text pole for quiet CTAs" },
2689
2696
  "system/alpha/006": OFFSET,
2690
2697
  "system/alpha/008": OFFSET,
2691
2698
  "system/alpha/016": OFFSET,
2692
2699
  "system/alpha/shadow-04": SHADOW,
2693
2700
  "system/alpha/shadow-08": SHADOW,
2694
2701
  "system/alpha/shadow-12": SHADOW,
2695
- "system/surface/dim": PLANE("recessed elevation plane"),
2702
+ // ("elevation" dropped 2026-08-28: elevati-ON fed the "on" flood; the sibling
2703
+ // planes never carried the word)
2704
+ "system/surface/dim": PLANE("recessed plane"),
2696
2705
  "system/surface/low": PLANE("resting page plane"),
2697
2706
  "system/surface/mid": PLANE("raised plane \u2014 cards, menus"),
2698
2707
  // ("overlays" reworded 2026-08-13: paper-overlay took the word as a label — the C50 law)
@@ -3557,7 +3566,7 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
3557
3566
  };
3558
3567
  const softOnCta = (mode) => {
3559
3568
  if (!secondary || secondaryStyle === "outline") return [];
3560
- if (secondaryStyle !== "default" && !softOnCtaPasses(secondary, mode)) return [];
3569
+ if (!softOnCtaPasses(secondary, mode)) return [];
3561
3570
  const white = mode === "light" ? secondary.onFillTextIsWhite : secondary.onFillTextIsWhiteDark;
3562
3571
  return [` --${CSS_SECONDARY}-stamp-on: rgba(${white ? "255, 255, 255" : "0, 0, 0"}, ${SOFT_ON_CTA_ALPHA[mode]});`];
3563
3572
  };
@@ -3750,7 +3759,7 @@ function themeToFigma(r, input) {
3750
3759
  });
3751
3760
  };
3752
3761
  softOnCta(neutralGroup, mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark);
3753
- if (input.secondary && input.secondaryStyle !== "outline" && (input.secondaryStyle === "default" || softOnCtaPasses(input.secondary, mode)))
3762
+ if (input.secondary && input.secondaryStyle !== "outline" && softOnCtaPasses(input.secondary, mode))
3754
3763
  softOnCta(secondaryGroup, mode === "light" ? secondaryOnFillLight : secondaryOnFillDark);
3755
3764
  const brandGroup = rampGroup(scale[mode], mode === "light" ? scale.onFillTextIsWhite : scale.onFillTextIsWhiteDark, brandExtra(scale, mode, CSS_FAMILY.brandPrimary));
3756
3765
  const esc = input.ctaEscape ? escapeCtaFamily(nScale, mode, input.contrastProfile) : null;
@@ -3853,6 +3862,7 @@ function resolveDtcgRamp(group) {
3853
3862
  return resolveRamp(seedHex, mode, { stops, roles, ons });
3854
3863
  }
3855
3864
  export {
3865
+ CSS_FAMILY,
3856
3866
  DEFAULT_LINK_HEX,
3857
3867
  EXT_KEY,
3858
3868
  INK_0,
@@ -3879,11 +3889,14 @@ export {
3879
3889
  checkCollision,
3880
3890
  checkHueCollision,
3881
3891
  classifyArchetype,
3892
+ ctaBorderRung,
3893
+ ctaNeedsBorder,
3882
3894
  emitDtcgRamp,
3883
3895
  generateNeutralScale,
3884
3896
  generateScale,
3885
3897
  generateSubtleSecondary,
3886
3898
  neutralCss,
3899
+ pageStopFor,
3887
3900
  parseToken,
3888
3901
  resolveBrand,
3889
3902
  resolveDtcgRamp,
@@ -1,6 +1,7 @@
1
1
  export { generateScale, generateNeutralScale, generateSubtleSecondary, type GeneratedScale, type ColorStop, type GenerateOptions, type NeutralLevel, type ContrastProfile, } from './engine/colorEngine';
2
2
  export { resolveBrand, resolveTheme, resolveLinkTrio, resolveLinkInverseTrio, DEFAULT_LINK_HEX, SIGNAL_SCALES, signalScalesFor, SECONDARY_DISTINCT_DELTA_E, SUBTLE_TINT_MULT, SUBTLE_PASTEL_K, OUTLINE_HOVER_ALPHA, type ResolvedBrand, type ResolvedTheme, type ResolvedSecondary, type SecondaryLevel, type SecondaryStyle, type SignalOverride, } from './engine/resolve';
3
- export { brandCss, neutralCss, signalsCss, stopsToVars, toHex, stopHex, OFFSET_ALPHAS } from './engine/cssRender';
3
+ export { brandCss, neutralCss, signalsCss, stopsToVars, toHex, stopHex, OFFSET_ALPHAS, ctaNeedsBorder, ctaBorderRung, pageStopFor, } from './engine/cssRender';
4
+ export { CSS_FAMILY } from './engine/tokenDescriptions';
4
5
  export { themeToFigma, type FigmaGroup, type FigmaColorToken, type ThemeInput, } from './engine/figmaRender';
5
6
  export { emitDtcgRamp, resolveDtcgRamp, parseToken, EXT_KEY, RESOLVER_ID, type DtcgRampGroup, type DtcgRequirementToken, type DtcgSeedToken, type DtcgColorValue, } from './engine/requirements/dtcg';
6
7
  export { MODE_SPECS, type ModeSpec } from './engine/requirements/spec';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okchroma",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "license": "MIT",
5
5
  "description": "Color token engine: one brand hex in, a full WCAG-enforced light+dark token system out (CSS variables, DTCG JSON, Figma variables)",
6
6
  "repository": {
@@ -18,7 +18,8 @@
18
18
  }
19
19
  },
20
20
  "files": [
21
- "dist-lib"
21
+ "dist-lib",
22
+ "tokens"
22
23
  ],
23
24
  "sideEffects": false,
24
25
  "scripts": {
@@ -0,0 +1,204 @@
1
+ /*
2
+ Semantic token layer — maps role names to primitive tokens.
3
+ This file never changes per brand. Only the primitives (--brand-*, --neutral-*) change.
4
+ */
5
+
6
+ /* ─── Global surface / fg / border ─────────────────────────────────────────── */
7
+
8
+ [data-brand] {
9
+ /* Elevation planes sunken → low → base → high (owner spec 2026-07-24 Figma
10
+ elevation study; renamed 2026-08-12 — base is the raised plane components sit
11
+ on, the page behind it is low, the two extremes are edge cases): both themes
12
+ use the SAME four stops, in reversed order — elevation always moves toward
13
+ paper-100's side of the ramp. Light descends the ramp as elevation rises
14
+ (sunken paper-95 → high paper-100 white); dark ascends it (sunken paper-100
15
+ black → high paper-95). paper-100 flips white→black with the mode, so it is
16
+ always the extreme pole: the high in light, the sunken in dark. The stops
17
+ themselves have never moved through any of the renames. */
18
+ --surface-dim: var(--neutral-paper-95);
19
+ --surface-low: var(--neutral-paper-97);
20
+ --surface-mid: var(--neutral-paper-99);
21
+ --surface-high: var(--paper-100, #ffffff);
22
+
23
+ --fg-default: var(--neutral-ink-30);
24
+ --fg-subtle: var(--neutral-lead-53);
25
+ /* --fg-link moved to the SYSTEM LINK trio below (owner 2026-07-16: one link per
26
+ theme, riding --link — no longer the brand's raw ink pair) */
27
+
28
+ /* --border-default IS THE ACCESSIBILITY BORDER (owner 2026-07-29): a border that has
29
+ to be there — inputs and other controls — so it rides mark-74, the stop that
30
+ carries the WCAG 1.4.11 3:1 non-text require (highlight-8 pre-Stage-B). It sat on
31
+ --neutral-highlight-9 until that stop was deleted; mark-74 is where the
32
+ requirement actually lives, and it is the one stop this round does not move.
33
+ Decorative borders are --border-subtle (wash-89) and the per-family
34
+ --*-border-default (wash-85). */
35
+ --border-default: var(--neutral-mark-74);
36
+ --border-subtle: var(--neutral-wash-89);
37
+
38
+ /* Shadow transparencies (owner 2026-07-27) — pure black, mirrors the plugin's
39
+ system/alpha/shadow-04|08|12 rows 1:1. Dark is heavier by necessity: near
40
+ black a light-mode alpha vanishes, so the dark values carry the same roles
41
+ at 32/48/64%. Elevation recipes (--elev-*) compose these, so dark shadows
42
+ fall out of the token swap with nothing bespoke per style. */
43
+ --shadow-04: rgba(0, 0, 0, 0.04);
44
+ --shadow-08: rgba(0, 0, 0, 0.08);
45
+ --shadow-12: rgba(0, 0, 0, 0.12);
46
+ }
47
+
48
+ [data-brand][data-theme="dark"] {
49
+ /* Dark runs the same four stops upward: sunken paper-100 (black) → low paper-99 →
50
+ base paper-97 → high paper-95 — the one-level highest background (C27: every
51
+ family's paper-95 shares one photometric level, so chips sit flush on the high
52
+ plane by construction; a generated candidate for it was tried and retired,
53
+ owner 2026-07-28). Low needs its own override here because the shared block's
54
+ low is paper-97 (the light value); the stops themselves did not move. */
55
+ --surface-dim: var(--paper-100, #000000);
56
+ --surface-low: var(--neutral-paper-99);
57
+ --surface-mid: var(--neutral-paper-97);
58
+ --surface-high: var(--neutral-paper-95);
59
+
60
+ --shadow-04: rgba(0, 0, 0, 0.32);
61
+ --shadow-08: rgba(0, 0, 0, 0.48);
62
+ --shadow-12: rgba(0, 0, 0, 0.64);
63
+ }
64
+
65
+ /* ─── role.brand ────────────────────────────────────────────────────────────── */
66
+
67
+ [data-brand] {
68
+ --brand-fg: var(--brand-ink-30);
69
+ --brand-fg-hover: var(--brand-lead-53);
70
+ --brand-fg-alt: var(--brand-lead-53);
71
+ --brand-fg-alt-hover: var(--brand-ink-30);
72
+ --brand-fg-on-emphasis: var(--brand-stamp-on);
73
+
74
+ --brand-bg-faint: var(--brand-paper-97);
75
+ --brand-bg-subtle: var(--brand-wash-89);
76
+ --brand-bg-subtle-hover: var(--brand-wash-85);
77
+ --brand-bg-emphasis: var(--brand-stamp-fill);
78
+ --brand-bg-emphasis-hover: var(--brand-stamp-fill-hover);
79
+ --brand-bg-emphasis-pressed: var(--brand-stamp-fill-pressed);
80
+
81
+ --brand-border-subtle: var(--brand-wash-92);
82
+ --brand-border-subtle-hover: var(--brand-wash-89);
83
+ --brand-border-default: var(--brand-wash-85);
84
+ --brand-border-default-hover: var(--brand-mark-74);
85
+ --brand-border-emphasis: var(--brand-stamp-fill);
86
+ --brand-border-emphasis-hover: var(--brand-stamp-fill-hover);
87
+
88
+ /* the SYSTEM LINK (one trio per theme — aliases the primary's ink stops unless a custom
89
+ link color de-conflicts it; --link/-hover/-pressed are emitted per brand) */
90
+ --fg-link: var(--link);
91
+ --fg-link-hover: var(--link-hover);
92
+ --fg-link-pressed: var(--link-pressed);
93
+
94
+ /* the INVERSE link trio (owner round 2026-08-19): the same link seed re-solved for
95
+ text on inverted surfaces (an ink-30 fill); always its own emitted values —
96
+ no stop is anchored against that ground, so there is no alias posture */
97
+ --fg-link-inverse: var(--link-inverse);
98
+ --fg-link-inverse-hover: var(--link-inverse-hover);
99
+ --fg-link-inverse-pressed: var(--link-inverse-pressed);
100
+ }
101
+
102
+ /* ─── role.secondary (the brand's secondary ramp; mirrors brand when none) ──── */
103
+
104
+ [data-brand] {
105
+ --brand-alt-fg: var(--brand-alt-ink-30);
106
+ --brand-alt-fg-hover: var(--brand-alt-lead-53);
107
+ --brand-alt-fg-alt: var(--brand-alt-lead-53);
108
+ --brand-alt-fg-alt-hover: var(--brand-alt-ink-30);
109
+ --brand-alt-fg-on-emphasis: var(--brand-alt-stamp-on);
110
+
111
+ --brand-alt-bg-faint: var(--brand-alt-paper-97);
112
+ --brand-alt-bg-subtle: var(--brand-alt-wash-89);
113
+ --brand-alt-bg-subtle-hover: var(--brand-alt-wash-85);
114
+ --brand-alt-bg-emphasis: var(--brand-alt-stamp-fill);
115
+ --brand-alt-bg-emphasis-hover: var(--brand-alt-stamp-fill-hover);
116
+ --brand-alt-bg-emphasis-pressed: var(--brand-alt-stamp-fill-pressed);
117
+
118
+ --brand-alt-border-subtle: var(--brand-alt-wash-92);
119
+ --brand-alt-border-subtle-hover: var(--brand-alt-wash-89);
120
+ --brand-alt-border-default: var(--brand-alt-wash-85);
121
+ --brand-alt-border-default-hover: var(--brand-alt-mark-74);
122
+ --brand-alt-border-emphasis: var(--brand-alt-stamp-fill);
123
+ --brand-alt-border-emphasis-hover: var(--brand-alt-stamp-fill-hover);
124
+ }
125
+
126
+ /* ─── Signal roles ──────────────────────────────────────────────────────────
127
+ Aliases onto engine-generated signal primitives (dist/signals.css).
128
+ Mode switching happens at the primitive layer, so one block covers both.
129
+ Signal families emit by ROLE (owner 2026-07-27: critical/warning/positive/
130
+ info — the re-pointable in-between tier; identity stays engine-internal),
131
+ so the role tokens and the family ramp share a prefix: --critical-fg rides
132
+ --critical-ink-30. The old alert-high/alert-med role prefixes collapsed
133
+ into critical/warning in the same round.
134
+
135
+ -fg-on-emphasis rides --paper-100, NOT a solved on-color: the engine's on-highlight
136
+ token was deleted with the highlight band (owner 2026-07-29). paper-100 is the
137
+ mode-flipping paper extreme — white in light, the tinted deep ground in dark — and
138
+ it clears 4.5 against lead-53 everywhere (agnostic 360-seed sweep, worst 4.96
139
+ light / 8.04 dark). -bg-emphasis / -border-emphasis moved from highlight-9 to
140
+ lead-53 (ink-9 pre-Stage-B) with it; in dark that reads lighter (contrast vs
141
+ paper-95, median 6.01 → 7.72). Stage B (owner 2026-08-07, names only) relabeled
142
+ every leaf in the block below; the stops themselves did not move. */
143
+
144
+ [data-brand] {
145
+ --critical-fg: var(--critical-ink-30);
146
+ --critical-fg-hover: var(--critical-lead-53);
147
+ --critical-fg-alt: var(--critical-lead-53);
148
+ --critical-fg-alt-hover: var(--critical-ink-30);
149
+ --critical-fg-on-emphasis: var(--paper-100);
150
+ --critical-bg-faint: var(--critical-paper-97);
151
+ --critical-bg-subtle: var(--critical-wash-89);
152
+ --critical-bg-subtle-hover: var(--critical-wash-85);
153
+ --critical-bg-emphasis: var(--critical-lead-53);
154
+ --critical-border-subtle: var(--critical-wash-92);
155
+ --critical-border-subtle-hover: var(--critical-wash-89);
156
+ --critical-border-default: var(--critical-wash-85);
157
+ --critical-border-default-hover: var(--critical-mark-74);
158
+ --critical-border-emphasis: var(--critical-lead-53);
159
+
160
+ --warning-fg: var(--warning-ink-30);
161
+ --warning-fg-hover: var(--warning-lead-53);
162
+ --warning-fg-alt: var(--warning-lead-53);
163
+ --warning-fg-alt-hover: var(--warning-ink-30);
164
+ --warning-fg-on-emphasis: var(--paper-100);
165
+ --warning-bg-faint: var(--warning-paper-97);
166
+ --warning-bg-subtle: var(--warning-wash-89);
167
+ --warning-bg-subtle-hover: var(--warning-wash-85);
168
+ --warning-bg-emphasis: var(--warning-lead-53);
169
+ --warning-border-subtle: var(--warning-wash-92);
170
+ --warning-border-subtle-hover: var(--warning-wash-89);
171
+ --warning-border-default: var(--warning-wash-85);
172
+ --warning-border-default-hover: var(--warning-mark-74);
173
+ --warning-border-emphasis: var(--warning-lead-53);
174
+
175
+ --positive-fg: var(--positive-ink-30);
176
+ --positive-fg-hover: var(--positive-lead-53);
177
+ --positive-fg-alt: var(--positive-lead-53);
178
+ --positive-fg-alt-hover: var(--positive-ink-30);
179
+ --positive-fg-on-emphasis: var(--paper-100);
180
+ --positive-bg-faint: var(--positive-paper-97);
181
+ --positive-bg-subtle: var(--positive-wash-89);
182
+ --positive-bg-subtle-hover: var(--positive-wash-85);
183
+ --positive-bg-emphasis: var(--positive-lead-53);
184
+ --positive-border-subtle: var(--positive-wash-92);
185
+ --positive-border-subtle-hover: var(--positive-wash-89);
186
+ --positive-border-default: var(--positive-wash-85);
187
+ --positive-border-default-hover: var(--positive-mark-74);
188
+ --positive-border-emphasis: var(--positive-lead-53);
189
+
190
+ --info-fg: var(--info-ink-30);
191
+ --info-fg-hover: var(--info-lead-53);
192
+ --info-fg-alt: var(--info-lead-53);
193
+ --info-fg-alt-hover: var(--info-ink-30);
194
+ --info-fg-on-emphasis: var(--paper-100);
195
+ --info-bg-faint: var(--info-paper-97);
196
+ --info-bg-subtle: var(--info-wash-89);
197
+ --info-bg-subtle-hover: var(--info-wash-85);
198
+ --info-bg-emphasis: var(--info-lead-53);
199
+ --info-border-subtle: var(--info-wash-92);
200
+ --info-border-subtle-hover: var(--info-wash-89);
201
+ --info-border-default: var(--info-wash-85);
202
+ --info-border-default-hover: var(--info-mark-74);
203
+ --info-border-emphasis: var(--info-lead-53);
204
+ }