okchroma 0.2.0 → 0.2.2

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.
@@ -2358,6 +2358,8 @@ function solveDarkCtaExit(cur, cFor, darkH, redDark, enforceLc, coLc) {
2358
2358
  var oklchToSrgb = (L, C2, H2) => Object.values(srgbEmitChannels({ L, C: C2, H: H2 })).map((c) => Math.max(0, Math.min(1, c)));
2359
2359
  var toHex = (rgb) => "#" + rgb.map((c) => Math.round(c * 255).toString(16).padStart(2, "0")).join("");
2360
2360
  var CTA_CLEARANCE_CAPS = [0.05, 0.92];
2361
+ var CHROMATIC_W80_WORST_SHIP_Y = { light: 0.459357, dark: 0.074936 };
2362
+ var CHROMATIC_P3_WORST_SHIP_Y = { light: 0.836007, dark: 0.016041 };
2361
2363
  function resolveRamp(hex, mode, spec, opts) {
2362
2364
  const o = hexToOklch(hex);
2363
2365
  const seed = { hex, ...o };
@@ -2501,11 +2503,18 @@ function resolveRamp(hex, mode, spec, opts) {
2501
2503
  const own = contrastRatio(y2, anchorShipY);
2502
2504
  return paperBound ? Math.min(own, contrastRatio(y2, crossBoundY)) : own;
2503
2505
  };
2504
- if (shipRatio(placed.L) < sp.require.target) {
2506
+ const highlighterBoundY = ctx.opts?.crossHighlighterBoundY && sp.stop >= 10 && !textGround ? ctx.opts.crossHighlighterBoundY[mode] : void 0;
2507
+ const highlighterRatio = (L) => highlighterBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), highlighterBoundY);
2508
+ const paperBoundY = ctx.opts?.crossPaperBoundY && sp.stop >= 8 && !textGround ? ctx.opts.crossPaperBoundY[mode] : void 0;
2509
+ const paperBar = sp.stop === 8 ? STOP_8_NONTEXT_CONTRAST : sp.stop === 9 ? PENCIL_9_CONTRAST : PEN_10_CONTRAST;
2510
+ const paperRatio = (L) => paperBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), paperBoundY);
2511
+ const target = sp.require.target;
2512
+ const clears = (L) => shipRatio(L) >= target && highlighterRatio(L) >= PEN_10_CONTRAST && paperRatio(L) >= paperBar;
2513
+ if (!clears(placed.L)) {
2505
2514
  const away = shippedY(placed.L, placed.C, placed.H) < anchorShipY ? -1 : 1;
2506
2515
  let L2 = placed.L;
2507
- for (let i = 0; i < 100 && shipRatio(L2) < sp.require.target; i++) L2 += away * 1e-3;
2508
- if (shipRatio(L2) >= sp.require.target) {
2516
+ for (let i = 0; i < 100 && !clears(L2); i++) L2 += away * 1e-3;
2517
+ if (clears(L2)) {
2509
2518
  placed = { ...placed, L: L2 };
2510
2519
  clamped = true;
2511
2520
  }
@@ -2516,6 +2525,8 @@ function resolveRamp(hex, mode, spec, opts) {
2516
2525
  shipRatio(placed.L)
2517
2526
  );
2518
2527
  if (got < sp.require.target - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${got.toFixed(2)} < required ${sp.require.target}`;
2528
+ else if (highlighterRatio(placed.L) < PEN_10_CONTRAST - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${highlighterRatio(placed.L).toFixed(2)} < required ${PEN_10_CONTRAST} on the worst chromatic highlighter-20`;
2529
+ else if (paperRatio(placed.L) < paperBar - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${paperRatio(placed.L).toFixed(2)} < required ${paperBar} on the worst chromatic paper-5`;
2519
2530
  } else if (sp.require?.metric === "apca") {
2520
2531
  const ga = apcaGroundOf(sp.require, sp.stop);
2521
2532
  const refA = apcaYAt(ga.L, ga.C, ga.H);
@@ -2715,7 +2726,11 @@ function generateNeutralScale(brandH, level = "default", contrastProfile) {
2715
2726
  const scale = generateScale(grayHex, "neutral", "light", {
2716
2727
  chromaCurve: curve,
2717
2728
  enforceOnFillContrast: true,
2718
- contrastProfile
2729
+ contrastProfile,
2730
+ // T13: the neutral's pens clear every chromatic family's highlighter-20 (the symmetric
2731
+ // pen guarantee); the chromatic ramps clear the neutral's through NEUTRAL_W80_WORST_SHIP_Y
2732
+ crossHighlighterBoundY: CHROMATIC_W80_WORST_SHIP_Y,
2733
+ crossPaperBoundY: CHROMATIC_P3_WORST_SHIP_Y
2719
2734
  });
2720
2735
  const asCta = (stop, src) => makeStop(stop, src.L, src.C, src.H);
2721
2736
  scale.cta = asCta(9, scale.light[3]);
@@ -2279,6 +2279,8 @@ function solveDarkCtaExit(cur, cFor, darkH, redDark, enforceLc, coLc) {
2279
2279
  var oklchToSrgb = (L, C2, H2) => Object.values(srgbEmitChannels({ L, C: C2, H: H2 })).map((c) => Math.max(0, Math.min(1, c)));
2280
2280
  var toHex = (rgb) => "#" + rgb.map((c) => Math.round(c * 255).toString(16).padStart(2, "0")).join("");
2281
2281
  var CTA_CLEARANCE_CAPS = [0.05, 0.92];
2282
+ var CHROMATIC_W80_WORST_SHIP_Y = { light: 0.459357, dark: 0.074936 };
2283
+ var CHROMATIC_P3_WORST_SHIP_Y = { light: 0.836007, dark: 0.016041 };
2282
2284
  function resolveRamp(hex, mode, spec, opts) {
2283
2285
  const o = hexToOklch(hex);
2284
2286
  const seed = { hex, ...o };
@@ -2422,11 +2424,18 @@ function resolveRamp(hex, mode, spec, opts) {
2422
2424
  const own = contrastRatio(y2, anchorShipY);
2423
2425
  return paperBound ? Math.min(own, contrastRatio(y2, crossBoundY)) : own;
2424
2426
  };
2425
- if (shipRatio(placed.L) < sp.require.target) {
2427
+ const highlighterBoundY = ctx.opts?.crossHighlighterBoundY && sp.stop >= 10 && !textGround ? ctx.opts.crossHighlighterBoundY[mode] : void 0;
2428
+ const highlighterRatio = (L) => highlighterBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), highlighterBoundY);
2429
+ const paperBoundY = ctx.opts?.crossPaperBoundY && sp.stop >= 8 && !textGround ? ctx.opts.crossPaperBoundY[mode] : void 0;
2430
+ const paperBar = sp.stop === 8 ? STOP_8_NONTEXT_CONTRAST : sp.stop === 9 ? PENCIL_9_CONTRAST : PEN_10_CONTRAST;
2431
+ const paperRatio = (L) => paperBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), paperBoundY);
2432
+ const target = sp.require.target;
2433
+ const clears = (L) => shipRatio(L) >= target && highlighterRatio(L) >= PEN_10_CONTRAST && paperRatio(L) >= paperBar;
2434
+ if (!clears(placed.L)) {
2426
2435
  const away = shippedY(placed.L, placed.C, placed.H) < anchorShipY ? -1 : 1;
2427
2436
  let L2 = placed.L;
2428
- for (let i = 0; i < 100 && shipRatio(L2) < sp.require.target; i++) L2 += away * 1e-3;
2429
- if (shipRatio(L2) >= sp.require.target) {
2437
+ for (let i = 0; i < 100 && !clears(L2); i++) L2 += away * 1e-3;
2438
+ if (clears(L2)) {
2430
2439
  placed = { ...placed, L: L2 };
2431
2440
  clamped = true;
2432
2441
  }
@@ -2437,6 +2446,8 @@ function resolveRamp(hex, mode, spec, opts) {
2437
2446
  shipRatio(placed.L)
2438
2447
  );
2439
2448
  if (got < sp.require.target - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${got.toFixed(2)} < required ${sp.require.target}`;
2449
+ else if (highlighterRatio(placed.L) < PEN_10_CONTRAST - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${highlighterRatio(placed.L).toFixed(2)} < required ${PEN_10_CONTRAST} on the worst chromatic highlighter-20`;
2450
+ else if (paperRatio(placed.L) < paperBar - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${paperRatio(placed.L).toFixed(2)} < required ${paperBar} on the worst chromatic paper-5`;
2440
2451
  } else if (sp.require?.metric === "apca") {
2441
2452
  const ga = apcaGroundOf(sp.require, sp.stop);
2442
2453
  const refA = apcaYAt(ga.L, ga.C, ga.H);
@@ -2636,7 +2647,11 @@ function generateNeutralScale(brandH, level = "default", contrastProfile) {
2636
2647
  const scale = generateScale(grayHex, "neutral", "light", {
2637
2648
  chromaCurve: curve,
2638
2649
  enforceOnFillContrast: true,
2639
- contrastProfile
2650
+ contrastProfile,
2651
+ // T13: the neutral's pens clear every chromatic family's highlighter-20 (the symmetric
2652
+ // pen guarantee); the chromatic ramps clear the neutral's through NEUTRAL_W80_WORST_SHIP_Y
2653
+ crossHighlighterBoundY: CHROMATIC_W80_WORST_SHIP_Y,
2654
+ crossPaperBoundY: CHROMATIC_P3_WORST_SHIP_Y
2640
2655
  });
2641
2656
  const asCta = (stop, src) => makeStop(stop, src.L, src.C, src.H);
2642
2657
  scale.cta = asCta(9, scale.light[3]);
@@ -89,6 +89,14 @@ export interface GenerateOptions {
89
89
  H: number;
90
90
  };
91
91
  };
92
+ crossHighlighterBoundY?: {
93
+ light: number;
94
+ dark: number;
95
+ };
96
+ crossPaperBoundY?: {
97
+ light: number;
98
+ dark: number;
99
+ };
92
100
  deltaHKPlace?: boolean;
93
101
  deltaChromaEq?: boolean;
94
102
  deltaLiftFloor?: boolean;
@@ -43,4 +43,12 @@ export type ResolvedRamp = {
43
43
  };
44
44
  };
45
45
  export type { ResolveOpts };
46
+ export declare const CHROMATIC_W80_WORST_SHIP_Y: {
47
+ readonly light: 0.459357;
48
+ readonly dark: 0.074936;
49
+ };
50
+ export declare const CHROMATIC_P3_WORST_SHIP_Y: {
51
+ readonly light: 0.836007;
52
+ readonly dark: 0.016041;
53
+ };
46
54
  export declare function resolveRamp(hex: string, mode: 'light' | 'dark', spec?: ModeSpec, opts?: ResolveOpts): ResolvedRamp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okchroma",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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": {