okchroma 0.1.7 → 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.
@@ -24,11 +24,11 @@ __export(index_exports, {
24
24
  DEFAULT_LINK_HEX: () => DEFAULT_LINK_HEX,
25
25
  DISABLED_OPACITY: () => DISABLED_OPACITY,
26
26
  EXT_KEY: () => EXT_KEY,
27
- INK_0: () => INK_0,
28
27
  MODE_SPECS: () => MODE_SPECS,
29
28
  OFFSET_ALPHAS: () => OFFSET_ALPHAS,
30
29
  OUTLINE_HOVER_ALPHA: () => OUTLINE_HOVER_ALPHA,
31
- PAPER_100: () => PAPER_100,
30
+ PAPER_0: () => PAPER_0,
31
+ PEN_100: () => PEN_100,
32
32
  RESOLVER_ID: () => RESOLVER_ID,
33
33
  SCALE_STOP_COUNT: () => SCALE_STOP_COUNT,
34
34
  SCRIM_ALPHA: () => SCRIM_ALPHA,
@@ -282,12 +282,12 @@ var SCALE_C_LIGHT = {
282
282
  6: { base: 0.068, sat: 0.85 },
283
283
  7: { base: 0.086, sat: 0.78 },
284
284
  8: { base: 0.142, sat: 0.78 },
285
- 9: { inkMult: 0.95, inkMaxC: 0.15, chromaFloor: chromaFloorBase(10) },
285
+ 9: { textMult: 0.95, textMaxC: 0.15, chromaFloor: chromaFloorBase(10) },
286
286
  // ink-10 keeps the FIRST-text register (ink-9's row, C49): the retired hover law
287
287
  // evaluated ink-9's chroma register at the between L, so the same params reproduce it;
288
288
  // the damped strong register below belongs to the top text stop alone.
289
- 10: { inkMult: 0.95, inkMaxC: 0.15, chromaFloor: chromaFloorBase(10) },
290
- 11: { inkMult: 0.5, inkMaxC: 0.08, chromaFloor: chromaFloorBase(11) }
289
+ 10: { textMult: 0.95, textMaxC: 0.15, chromaFloor: chromaFloorBase(10) },
290
+ 11: { textMult: 0.5, textMaxC: 0.08, chromaFloor: chromaFloorBase(11) }
291
291
  };
292
292
  var SCALE_C_DARK = {
293
293
  0: { sat: 0.4 },
@@ -299,9 +299,9 @@ var SCALE_C_DARK = {
299
299
  6: { sat: 0.76 },
300
300
  7: { sat: 0.8 },
301
301
  8: { sat: 0.84 },
302
- 9: { inkMult: 0.95, inkMaxC: 0.12, chromaFloor: chromaFloorBase(10) },
303
- 10: { inkMult: 0.95, inkMaxC: 0.12, chromaFloor: chromaFloorBase(10) },
304
- 11: { inkMult: 0.62, inkMaxC: 0.045, chromaFloor: chromaFloorBase(11) }
302
+ 9: { textMult: 0.95, textMaxC: 0.12, chromaFloor: chromaFloorBase(10) },
303
+ 10: { textMult: 0.95, textMaxC: 0.12, chromaFloor: chromaFloorBase(10) },
304
+ 11: { textMult: 0.62, textMaxC: 0.045, chromaFloor: chromaFloorBase(11) }
305
305
  };
306
306
  var DARK_CTA_C = {
307
307
  brand: {
@@ -318,10 +318,10 @@ var DARK_CTA_C = {
318
318
  };
319
319
  var STOP_8_NONTEXT_CONTRAST = 3;
320
320
  var DARK_BAND_TOP_LIFT = 1.19;
321
- var INK_9_CONTRAST = 4.5;
322
- var INK_10_CONTRAST = 4.5;
323
- var INK_11_CONTRAST_FLOOR = 7;
324
- var INK_30_GROUND = {
321
+ var PENCIL_9_CONTRAST = 4.5;
322
+ var PEN_10_CONTRAST = 4.5;
323
+ var PEN_11_CONTRAST_FLOOR = 7;
324
+ var PEN_70_GROUND = {
325
325
  wcag: {
326
326
  light: { L: 0.3334966864733433, C: 0.07399966383476321, H: 104.26776675496032 },
327
327
  dark: { L: 0.907688606079988, C: 0.045, H: 293.66431934911327 }
@@ -397,7 +397,7 @@ var NEUTRAL_TINT_POINTS = {
397
397
  [0.95, 0.895],
398
398
  // paper-3 ─┘
399
399
  [0.924, 1],
400
- // wash-4 ─┐ peak: the mid-wash band, the neutral's most-branded moment
400
+ // wash-4 ─┐ peak: the mid-highlighter band, the neutral's most-branded moment
401
401
  [0.892, 1],
402
402
  // wash-5 ─┘
403
403
  [0.852, 0.842],
@@ -405,7 +405,7 @@ var NEUTRAL_TINT_POINTS = {
405
405
  [0.801, 0.653],
406
406
  // wash-7 │
407
407
  [0.738, 0.505],
408
- // wax-74
408
+ // crayon-26
409
409
  [0.6, 0.4],
410
410
  // knee (ex-scaffold) the drop-off: hue fades as you climb
411
411
  [0.56, 0.347],
@@ -431,7 +431,7 @@ var NEUTRAL_TINT_POINTS = {
431
431
  [0.42, 0.653],
432
432
  // wash-7
433
433
  [0.55, 0.505],
434
- // wax-74
434
+ // crayon-26
435
435
  [0.6, 0.4],
436
436
  // knee (ex-scaffold)
437
437
  [0.64, 0.347],
@@ -802,23 +802,23 @@ function perceptualDarkC(L, H2, nativeC, keep = KEEP_DARK, gamut = MASTER_GAMUT)
802
802
 
803
803
  // src/engine/tokenNames.ts
804
804
  var SHARED_NAMES = {
805
- 1: "paper-99",
806
- 2: "paper-97",
805
+ 1: "paper-1",
806
+ 2: "paper-3",
807
807
  // stop 3 renamed wash-3 → paper-3 (owner 2026-07-24, elevation round): it is a
808
808
  // PLANE in both themes (light sink / dark pop), so it belongs to the surface band.
809
- // NAME ONLY — generation is index-keyed and unchanged; the wash-collision machinery
810
- // (collision.ts WASH_STOPS) deliberately still spans stops 3–7. (The requirement
809
+ // NAME ONLY — generation is index-keyed and unchanged; the highlighter-collision machinery
810
+ // (collision.ts HIGHLIGHTER_STOPS) deliberately still spans stops 3–7. (The requirement
811
811
  // spec's `group` label followed the band to 'paper' on 2026-08-31 — labels only,
812
812
  // the spans never moved; see spec.ts groupOf.)
813
813
  // Stage B (owner 2026-08-07, names only): relabeled to the ROOT_L-derived digit,
814
- // paper-95 — same band, same index.
815
- 3: "paper-95",
816
- 4: "wash-92",
817
- 5: "wash-89",
818
- 6: "wash-85",
819
- 7: "wash-80",
820
- 8: "wax-74",
821
- // THE INK BAND (C49, owner 2026-08-05): the first text stop + emphasis fill (the
814
+ // paper-5 — same band, same index.
815
+ 3: "paper-5",
816
+ 4: "highlighter-8",
817
+ 5: "highlighter-11",
818
+ 6: "highlighter-15",
819
+ 7: "highlighter-20",
820
+ 8: "crayon-26",
821
+ // THE PEN BAND (C49, owner 2026-08-05): the first text stop + emphasis fill (the
822
822
  // 2026-07-29 highlight collapse — the old highlight-9 and on-highlight are
823
823
  // deleted; this stop carries both jobs, its on-color a paper token), the between
824
824
  // text stop (a value the retired text-cta hover state used to generate bespokely,
@@ -827,9 +827,9 @@ var SHARED_NAMES = {
827
827
  // two pre-collapse stop INDICES (11, and the off-scale anchor's 12 — emitted as a
828
828
  // literal in cssRender, not through this table) with the between stop taking the
829
829
  // vacated index 10; the indices (9/10/11) are unchanged since.
830
- 9: "lead-53",
831
- 10: "ink-42",
832
- 11: "ink-30"
830
+ 9: "pencil-47",
831
+ 10: "pen-58",
832
+ 11: "pen-70"
833
833
  };
834
834
  var SCALE_STOP_COUNT = Object.keys(SHARED_NAMES).length;
835
835
  function stopTokenName(stop) {
@@ -837,8 +837,8 @@ function stopTokenName(stop) {
837
837
  if (!name) throw new Error(`stopTokenName: unexpected stop ${stop}`);
838
838
  return name;
839
839
  }
840
- var PAPER_100 = "paper-100";
841
- var INK_0 = "ink-0";
840
+ var PAPER_0 = "paper-0";
841
+ var PEN_100 = "pen-100";
842
842
  var STAMP_FILL = "stamp-fill";
843
843
  var STAMP_FILL_HOVER = "stamp-fill-hover";
844
844
  var STAMP_FILL_PRESSED = "stamp-fill-pressed";
@@ -875,7 +875,7 @@ var SYSTEM_LEAF = {
875
875
  // and the 2026-08-29 inverse ladder): away-from-bg is the page-polarity pole
876
876
  // (black in light, white in dark), toward-bg the flipped pole — STATE LAYERS for
877
877
  // INVERTED grounds, whose polarity is opposite the page's. (Never call toward-bg
878
- // a "wash": that word belongs to the tinted band stops.) The reversal lives in
878
+ // a "highlighter": that word belongs to the tinted band stops.) The reversal lives in
879
879
  // the engine: a consumer on an inverted ground names one token and never picks a
880
880
  // pole. Colored fills never ride these; their states stay engine-solved stops.
881
881
  // Rungs are two-digit percent, harmonizing with shadow-04/08/12.
@@ -891,10 +891,10 @@ var SYSTEM_LEAF = {
891
891
  }
892
892
  };
893
893
  var SURFACE_PLANE_LAW = {
894
- [SYSTEM_LEAF.SURFACE.DIM]: { light: SHARED_NAMES[3], dark: PAPER_100 },
894
+ [SYSTEM_LEAF.SURFACE.DIM]: { light: SHARED_NAMES[3], dark: PAPER_0 },
895
895
  [SYSTEM_LEAF.SURFACE.LOW]: { light: SHARED_NAMES[2], dark: SHARED_NAMES[1] },
896
896
  [SYSTEM_LEAF.SURFACE.MID]: { light: SHARED_NAMES[1], dark: SHARED_NAMES[2] },
897
- [SYSTEM_LEAF.SURFACE.HIGH]: { light: PAPER_100, dark: SHARED_NAMES[3] }
897
+ [SYSTEM_LEAF.SURFACE.HIGH]: { light: PAPER_0, dark: SHARED_NAMES[3] }
898
898
  };
899
899
  var TOKEN_ORDER = [
900
900
  ...Object.values(SHARED_NAMES),
@@ -910,26 +910,26 @@ function tokenOrder(name) {
910
910
  }
911
911
 
912
912
  // src/engine/requirements/spec.ts
913
- var inkLane = (g2) => g2 === "lead" || g2 === "ink";
914
- var groupOf = (stop) => (stop === 0 ? PAPER_100 : stopTokenName(stop)).split("-")[0];
913
+ var textLane = (g2) => g2 === "pencil" || g2 === "pen" || g2 === "lead" || g2 === "ink";
914
+ var groupOf = (stop) => (stop === 0 ? PAPER_0 : stopTokenName(stop)).split("-")[0];
915
915
  var PAPER0_DARK_ROOT_L = 0.16;
916
916
  var PL_LADDER = { hue: "warm-drift", L: "perceptual", chroma: "ladder" };
917
917
  var PL_TEXT = { hue: "warm-drift", L: "perceptual", chroma: "brand" };
918
918
  var P_LIFT = { hue: "warm-torsion", L: "perceptual-lift", chroma: "ladder" };
919
919
  var P_FIXED = { hue: "warm-torsion", L: "fixed", chroma: "ladder" };
920
920
  var P_TEXT = { hue: "warm-torsion", L: "perceptual", chroma: "brand" };
921
- var S8 = { metric: "wcag", against: "paper-95", target: STOP_8_NONTEXT_CONTRAST, level: "AA" };
922
- var T9 = { metric: "wcag", against: "paper-97", target: INK_9_CONTRAST, level: "AA" };
923
- var T10 = { metric: "wcag", against: "wash-80", target: INK_10_CONTRAST, level: "AA" };
924
- var T11 = { metric: "wcag", against: "paper-97", target: INK_11_CONTRAST_FLOOR, level: "AAA" };
921
+ var S8 = { metric: "wcag", against: "paper-5", target: STOP_8_NONTEXT_CONTRAST, level: "AA" };
922
+ var T9 = { metric: "wcag", against: "paper-3", target: PENCIL_9_CONTRAST, level: "AA" };
923
+ var T10 = { metric: "wcag", against: "highlighter-20", target: PEN_10_CONTRAST, level: "AA" };
924
+ var T11 = { metric: "wcag", against: "paper-3", target: PEN_11_CONTRAST_FLOOR, level: "AAA" };
925
925
  var ONS = { onFill: { metric: "apca-pole", enforce: true, ratioFloor: 4.5, coEnforceLc: 65 } };
926
926
  var LIGHT = {
927
927
  stops: [
928
- // paper-100 (paper-0 pre-Stage-B): the resolved ladder extreme — in light it genuinely is white (rootL 1.0, zero chroma)
928
+ // paper-0 (paper-0 pre-Stage-B): the resolved ladder extreme — in light it genuinely is white (rootL 1.0, zero chroma)
929
929
  { stop: 0, rootL: 1, group: groupOf(0), produce: { hue: "warm-drift", L: "fixed", chroma: "ladder" }, satFraction: SCALE_C_LIGHT[0].sat, baseC: SCALE_C_LIGHT[0].base },
930
- // paper/wash/wax-74: perceptual ladder/envelope blend on the geometric ROOT_L_LIGHT scaffold. Separation
930
+ // paper/highlighter/crayon-26: perceptual ladder/envelope blend on the geometric ROOT_L_LIGHT scaffold. Separation
931
931
  // falls out of the shape (see above) — no min-separation require. Only stop 8 carries a require: the WCAG
932
- // 3:1 vs the resolved paper-97 (paper-2) (re-solves automatically since it references paper-97 (paper-2)).
932
+ // 3:1 vs the resolved paper-3 (paper-2) (re-solves automatically since it references paper-3 (paper-2)).
933
933
  ...[1, 2, 3, 4, 5, 6, 7, 8].map((stop) => ({
934
934
  stop,
935
935
  rootL: ROOT_L_LIGHT[stop],
@@ -939,12 +939,12 @@ var LIGHT = {
939
939
  baseC: SCALE_C_LIGHT[stop].base,
940
940
  require: stop === 8 ? S8 : void 0
941
941
  })),
942
- // ink text: perceptual + contrast-required. lead-53 (ink-9) is ALSO the emphasis fill (the
943
- // highlight-9 collapse, owner 2026-07-29). ink-42 (ink-10) is the between text stop (C49) —
942
+ // pen text: perceptual + contrast-required. pencil-47 (ink-9) is ALSO the emphasis fill (the
943
+ // highlight-9 collapse, owner 2026-07-29). pen-58 (ink-10) is the between text stop (C49) —
944
944
  // a normal stop like its neighbors; the three together are the text-register cta.
945
- { stop: 9, rootL: ROOT_L_LIGHT[9], group: groupOf(9), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[9].inkMult, inkMaxC: SCALE_C_LIGHT[9].inkMaxC, chromaFloor: SCALE_C_LIGHT[9].chromaFloor, require: T9 },
946
- { stop: 10, rootL: ROOT_L_LIGHT[10], group: groupOf(10), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[10].inkMult, inkMaxC: SCALE_C_LIGHT[10].inkMaxC, chromaFloor: SCALE_C_LIGHT[10].chromaFloor, require: T10 },
947
- { stop: 11, rootL: ROOT_L_LIGHT[11], group: groupOf(11), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[11].inkMult, inkMaxC: SCALE_C_LIGHT[11].inkMaxC, chromaFloor: SCALE_C_LIGHT[11].chromaFloor, require: T11 }
945
+ { stop: 9, rootL: ROOT_L_LIGHT[9], group: groupOf(9), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[9].textMult, textMaxC: SCALE_C_LIGHT[9].textMaxC, chromaFloor: SCALE_C_LIGHT[9].chromaFloor, require: T9 },
946
+ { stop: 10, rootL: ROOT_L_LIGHT[10], group: groupOf(10), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[10].textMult, textMaxC: SCALE_C_LIGHT[10].textMaxC, chromaFloor: SCALE_C_LIGHT[10].chromaFloor, require: T10 },
947
+ { stop: 11, rootL: ROOT_L_LIGHT[11], group: groupOf(11), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[11].textMult, textMaxC: SCALE_C_LIGHT[11].textMaxC, chromaFloor: SCALE_C_LIGHT[11].chromaFloor, require: T11 }
948
948
  ],
949
949
  roles: [
950
950
  { role: "cta", produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: 0, chromaMult: 1 },
@@ -955,10 +955,10 @@ var LIGHT = {
955
955
  };
956
956
  var DARK = {
957
957
  stops: [
958
- // paper-100 (paper-0 pre-Stage-B): the resolved ladder extreme — one seam BELOW paper-99 (paper-1), deep and brand-tinted, never the
958
+ // paper-0 (paper-0 pre-Stage-B): the resolved ladder extreme — one seam BELOW paper-1 (paper-1), deep and brand-tinted, never the
959
959
  // absolute void (the old hard-coded #000000 was "too much"). Lift applies like the rest of the scale.
960
960
  { stop: 0, rootL: PAPER0_DARK_ROOT_L, group: groupOf(0), produce: P_LIFT, satFraction: SCALE_C_DARK[0].sat },
961
- // paper/wash 1–7: perceptual on the dark scaffold. stop 8: FIXED at the hand-placed scaffold BUT with the
961
+ // paper/highlighter 1–7: perceptual on the dark scaffold. stop 8: FIXED at the hand-placed scaffold BUT with the
962
962
  // 3:1 non-text require DECLARED (the Stage-5 flip, owner-approved) — the blue-recede failure is prevented
963
963
  // BY RULE, not by patch. The require now genuinely PLACES the stop rather than catching a few low-luminance
964
964
  // hues: on the delta-carry path it solves from the sentinel every time, and the old claim that "most hues
@@ -972,11 +972,11 @@ var DARK = {
972
972
  satFraction: SCALE_C_DARK[stop].sat,
973
973
  require: stop === 8 ? S8 : void 0
974
974
  })),
975
- // ink text: perceptual + the contrast requires DECLARED in dark too (Stage-5 flip): the scaffold already
975
+ // pen text: perceptual + the contrast requires DECLARED in dark too (Stage-5 flip): the scaffold already
976
976
  // clears them for every hue (the gate proves it), so values don't move — but the guarantee is now a rule.
977
- { stop: 9, rootL: ROOT_L_DARK[9], group: groupOf(9), produce: P_TEXT, chromaMult: SCALE_C_DARK[9].inkMult, inkMaxC: SCALE_C_DARK[9].inkMaxC, chromaFloor: SCALE_C_DARK[9].chromaFloor, require: T9 },
978
- { stop: 10, rootL: ROOT_L_DARK[10], group: groupOf(10), produce: P_TEXT, chromaMult: SCALE_C_DARK[10].inkMult, inkMaxC: SCALE_C_DARK[10].inkMaxC, chromaFloor: SCALE_C_DARK[10].chromaFloor, require: T10 },
979
- { stop: 11, rootL: ROOT_L_DARK[11], group: groupOf(11), produce: P_TEXT, chromaMult: SCALE_C_DARK[11].inkMult, inkMaxC: SCALE_C_DARK[11].inkMaxC, chromaFloor: SCALE_C_DARK[11].chromaFloor, require: T11 }
977
+ { stop: 9, rootL: ROOT_L_DARK[9], group: groupOf(9), produce: P_TEXT, chromaMult: SCALE_C_DARK[9].textMult, textMaxC: SCALE_C_DARK[9].textMaxC, chromaFloor: SCALE_C_DARK[9].chromaFloor, require: T9 },
978
+ { stop: 10, rootL: ROOT_L_DARK[10], group: groupOf(10), produce: P_TEXT, chromaMult: SCALE_C_DARK[10].textMult, textMaxC: SCALE_C_DARK[10].textMaxC, chromaFloor: SCALE_C_DARK[10].chromaFloor, require: T10 },
979
+ { stop: 11, rootL: ROOT_L_DARK[11], group: groupOf(11), produce: P_TEXT, chromaMult: SCALE_C_DARK[11].textMult, textMaxC: SCALE_C_DARK[11].textMaxC, chromaFloor: SCALE_C_DARK[11].chromaFloor, require: T11 }
980
980
  ],
981
981
  roles: [
982
982
  { role: "cta", produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
@@ -987,7 +987,7 @@ var DARK = {
987
987
  };
988
988
  var MODE_SPECS = { light: LIGHT, dark: DARK };
989
989
 
990
- // ../../../Users/emilygerrity/okchroma/node_modules/helmlab/dist/index.js
990
+ // node_modules/helmlab/dist/index.js
991
991
  function o0(n) {
992
992
  let t = new Float64Array(9);
993
993
  for (let e = 0; e < 3; e++) for (let s = 0; s < 3; s++) t[e * 3 + s] = n[e][s];
@@ -1903,7 +1903,7 @@ function p2Diff(a, b) {
1903
1903
  var DEFAULT_APCA_LC_MAP = { 3: 30, 4.5: 75, 6.5: 85, 7: 90 };
1904
1904
  function toApca(req, lcMap) {
1905
1905
  if (req.metric !== "wcag") return req;
1906
- if (req.against === "wash-80") return { metric: "apca", against: "paper-97", targetLc: 85 };
1906
+ if (req.against === "highlighter-20") return { metric: "apca", against: "paper-3", targetLc: 85 };
1907
1907
  const targetLc = lcMap[req.target];
1908
1908
  if (targetLc === void 0) throw new Error(`apca profile: no Lc mapping for wcag target ${req.target}`);
1909
1909
  return { metric: "apca", against: req.against, targetLc };
@@ -1917,7 +1917,7 @@ function withProfile(spec, profile, lcMap = DEFAULT_APCA_LC_MAP) {
1917
1917
  // (The stop-9 CARVE-OUT is gone with the stop, owner 2026-07-29. It existed because
1918
1918
  // highlight-9's require was a fill-vs-its-own-plane bar with no honest slot in a map
1919
1919
  // of TEXT bars — 3 → 30 non-text, 4.5 → 75 body, 7 → 90 — so the apca lane kept its
1920
- // own hl-9 placement instead. Today's stop 9 is an INK stop: 4.5 is a text bar, the
1920
+ // own hl-9 placement instead. Today's stop 9 is a PEN stop: 4.5 is a text bar, the
1921
1921
  // 75 slot is its correct translation, and it maps like every other require.)
1922
1922
  stops: spec.stops.map((s) => s.require ? { ...s, require: toApca(s.require, lcMap) } : s),
1923
1923
  ons: {
@@ -2079,16 +2079,16 @@ function separationClampLight(ctx, placed, chromaAt, ref, target) {
2079
2079
  return { L: pass, C: ceilingAt(pass), H: ctx.lightHueAt(pass) };
2080
2080
  }
2081
2081
  function placeLightText(ctx, rootL, cMult, maxLFor, deepen, maxC = Infinity) {
2082
- const inkC = Math.min(cMult * ctx.brandC, maxC);
2083
- const anchorL = perceptualRungL(rootL, clampChromaToGamut(rootL, inkC, ctx.lightHueAt(rootL)), ctx.lightHueAt(rootL));
2082
+ const textC = Math.min(cMult * ctx.brandC, maxC);
2083
+ const anchorL = perceptualRungL(rootL, clampChromaToGamut(rootL, textC, ctx.lightHueAt(rootL)), ctx.lightHueAt(rootL));
2084
2084
  let H2 = ctx.lightHueAt(anchorL);
2085
- let C2 = clampChromaToGamut(anchorL, inkC, H2);
2085
+ let C2 = clampChromaToGamut(anchorL, textC, H2);
2086
2086
  let L = Math.min(anchorL, maxLFor(C2, H2));
2087
2087
  H2 = ctx.lightHueAt(L);
2088
- C2 = clampChromaToGamut(L, inkC, H2);
2088
+ C2 = clampChromaToGamut(L, textC, H2);
2089
2089
  L = Math.min(anchorL, maxLFor(C2, H2));
2090
2090
  L = Math.min(L - deepen, maxLFor(C2, ctx.lightHueAt(L - deepen)));
2091
- return { L, C: ctx.cAt("light", L, inkC), H: ctx.lightHueAt(L) };
2091
+ return { L, C: ctx.cAt("light", L, textC), H: ctx.lightHueAt(L) };
2092
2092
  }
2093
2093
  function onFillIsWhiteLight(ctx, enforce, ratioFloor) {
2094
2094
  const fill9ApcaY = apcaYAt(ctx.scaleL, clampChromaToGamut(ctx.scaleL, ctx.cAt("light", ctx.scaleL, ctx.brandC), ctx.brandH), ctx.brandH);
@@ -2102,7 +2102,7 @@ function buildDarkContext(ctx, specFloorL = DARK_CTA_MIN_L) {
2102
2102
  return { dark9L, darkC9, darkHueAtL };
2103
2103
  }
2104
2104
  var darkScaleChromaAt = (ctx, dctx, stopIndex, multiplier) => (L) => ctx.cAt("dark", L, ctx.opts?.darkChromaCurve ? ctx.opts.darkChromaCurve(L, dctx.darkHueAtL(L), ctx.brandC, dctx.darkC9) : applyChromaFloor(ctx.subtleC, multiplier, chromaFloorBase(stopIndex), ctx.darkFloorStrength));
2105
- var darkInkChromaAt = (ctx, dctx, floorBase, multiplier, maxC = Infinity) => (L) => ctx.cAt("dark", L, Math.min(applyChromaFloor(ctx.brandC, multiplier, floorBase, ctx.darkFloorStrength), maxC));
2105
+ var darkTextChromaAt = (ctx, dctx, floorBase, multiplier, maxC = Infinity) => (L) => ctx.cAt("dark", L, Math.min(applyChromaFloor(ctx.brandC, multiplier, floorBase, ctx.darkFloorStrength), maxC));
2106
2106
  function placeDark(dctx, rootL, chromaAt, lift = false) {
2107
2107
  let L = perceptualRungL(rootL, chromaAt(rootL), dctx.darkHueAtL(rootL));
2108
2108
  if (lift) L = Math.max(L, rootL);
@@ -2358,12 +2358,14 @@ 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 };
2364
2366
  const ctx = buildContext(hex, opts);
2365
2367
  spec ?? (spec = MODE_SPECS[mode]);
2366
- if (ctx.opts?.inkGround) {
2368
+ if (ctx.opts?.textGround) {
2367
2369
  spec = {
2368
2370
  ...spec,
2369
2371
  stops: spec.stops.map((s) => s.stop === 10 && s.require?.metric === "wcag" ? { ...s, require: { ...s.require, target: 6.5 } } : s)
@@ -2385,12 +2387,12 @@ function resolveRamp(hex, mode, spec, opts) {
2385
2387
  if (!ref) throw new Error(`stop ${forWhom}: require against stop ${stopNum} but it is not resolved yet`);
2386
2388
  return ref;
2387
2389
  };
2388
- const AGAINST_STOP = { "paper-99": 1, "paper-97": 2, "paper-95": 3, "wash-80": 7 };
2390
+ const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "highlighter-20": 7 };
2389
2391
  const declaredAnchor = (req) => req.metric === "min-separation" ? 1 : AGAINST_STOP[req.against] ?? 2;
2390
2392
  const wcagAnchorStop = (req, stop) => stop >= 9 && declaredAnchor(req) <= 3 ? 3 : declaredAnchor(req);
2391
- const inkGround = ctx.opts?.inkGround?.[mode];
2392
- const groundOf = (req, stop) => inkGround && stop >= 9 ? inkGround : refOf(wcagAnchorStop(req, stop), stop);
2393
- const apcaGroundOf = (req, stop) => inkGround && stop >= 9 ? inkGround : refOf(declaredAnchor(req), stop);
2393
+ const textGround = ctx.opts?.textGround?.[mode];
2394
+ const groundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(wcagAnchorStop(req, stop), stop);
2395
+ const apcaGroundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(declaredAnchor(req), stop);
2394
2396
  const NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
2395
2397
  const NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
2396
2398
  const maxLForOf = (req, forWhom, withMargin) => {
@@ -2408,14 +2410,14 @@ function resolveRamp(hex, mode, spec, opts) {
2408
2410
  }
2409
2411
  throw new Error(`stop ${forWhom}: ${req.metric} is not a contrast require`);
2410
2412
  };
2411
- const INK_DEEPEN = 0;
2413
+ const TEXT_DEEPEN = 0;
2412
2414
  for (const sp of spec.stops) {
2413
2415
  let placed;
2414
2416
  let clamped = false, unresolvable;
2415
2417
  if (mode === "light") {
2416
- if (inkLane(sp.group)) {
2417
- if (!sp.require) throw new Error(`light ink stop ${sp.stop} must declare a contrast require`);
2418
- placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false), INK_DEEPEN, sp.inkMaxC);
2418
+ if (textLane(sp.group)) {
2419
+ if (!sp.require) throw new Error(`light pen stop ${sp.stop} must declare a contrast require`);
2420
+ placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false), TEXT_DEEPEN, sp.textMaxC);
2419
2421
  clamped = true;
2420
2422
  } else if (sp.produce.L === "fixed") {
2421
2423
  const chromaAt = lightScaleChromaAt(ctx, sp.baseC ?? 0, sp.satFraction ?? 1);
@@ -2427,7 +2429,7 @@ function resolveRamp(hex, mode, spec, opts) {
2427
2429
  placed = placeLightScale(ctx, sp.rootL, chromaAt, contrastReq ? maxLForOf(contrastReq, sp.stop, true) : void 0, targetChromaAt);
2428
2430
  clamped = !!contrastReq;
2429
2431
  if (sp.require?.metric === "min-separation") {
2430
- const refStop = sp.require.against === "paper-99" ? 1 : sp.stop - 1;
2432
+ const refStop = sp.require.against === "paper-1" ? 1 : sp.stop - 1;
2431
2433
  const ref = stops.find((s) => s.stop === refStop);
2432
2434
  if (!ref) throw new Error(`stop ${sp.stop}: min-separation against stop ${refStop} but it is not resolved yet`);
2433
2435
  const before = placed.L;
@@ -2437,8 +2439,8 @@ function resolveRamp(hex, mode, spec, opts) {
2437
2439
  }
2438
2440
  } else {
2439
2441
  const d = dctx;
2440
- const inkTwin = inkLane(sp.group) && ctx.opts?.deltaCarry && ctx.opts?.chromaCurve ? ctx.opts.deltaLightStops?.find((s) => s.stop === sp.stop) : void 0;
2441
- const chromaAt = inkLane(sp.group) ? inkTwin ? ((_L) => inkTwin.C) : darkInkChromaAt(ctx, d, sp.chromaFloor ?? chromaFloorBase(sp.stop), sp.chromaMult ?? 1, sp.inkMaxC) : darkScaleChromaAt(ctx, d, Math.max(0, sp.stop - 1), sp.satFraction ?? 1);
2442
+ const textTwin = textLane(sp.group) && ctx.opts?.deltaCarry && ctx.opts?.chromaCurve ? ctx.opts.deltaLightStops?.find((s) => s.stop === sp.stop) : void 0;
2443
+ const chromaAt = textLane(sp.group) ? textTwin ? ((_L) => textTwin.C) : darkTextChromaAt(ctx, d, sp.chromaFloor ?? chromaFloorBase(sp.stop), sp.chromaMult ?? 1, sp.textMaxC) : darkScaleChromaAt(ctx, d, Math.max(0, sp.stop - 1), sp.satFraction ?? 1);
2442
2444
  let spineH = null;
2443
2445
  const dl = ctx.opts?.deltaLightStops;
2444
2446
  const ls = dl && sp.stop >= 1 && sp.stop <= 8 ? dl.find((s) => s.stop === sp.stop) : void 0;
@@ -2458,7 +2460,7 @@ function resolveRamp(hex, mode, spec, opts) {
2458
2460
  }
2459
2461
  if (ctx.opts.deltaHKPlace) L = perceptualRungL(sp.rootL, ls.C, ls.H);
2460
2462
  if (ctx.opts.deltaLiftFloor) L = Math.max(L, sp.rootL);
2461
- if (ctx.opts.deltaChromaEq && !inkLane(sp.group)) C2 = ctx.cAt("dark", L, perceptualDarkC(L, ls.H, ctx.brandC));
2463
+ if (ctx.opts.deltaChromaEq && !textLane(sp.group)) C2 = ctx.cAt("dark", L, perceptualDarkC(L, ls.H, ctx.brandC));
2462
2464
  placed = { L, C: C2, H: spineH ?? ls.H };
2463
2465
  } else if (sp.produce.L === "fixed") {
2464
2466
  placed = ls ? placeDarkDelta(d, sp.rootL, chromaAt, ls) : { L: sp.rootL, C: chromaAt(sp.rootL), H: d.darkHueAtL(sp.rootL) };
@@ -2494,18 +2496,25 @@ function resolveRamp(hex, mode, spec, opts) {
2494
2496
  if (sp.require?.metric === "wcag") {
2495
2497
  const anchor = groundOf(sp.require, sp.stop);
2496
2498
  const anchorShipY = shippedY(anchor.L, anchor.C, anchor.H);
2497
- const paperBound = sp.stop >= 8 && !(inkGround && sp.stop >= 9);
2499
+ const paperBound = sp.stop >= 8 && !(textGround && sp.stop >= 9);
2498
2500
  const crossBoundY = wcagAnchorStop(sp.require, sp.stop) === 7 ? NEUTRAL_W80_WORST_SHIP_Y[mode] : NEUTRAL_P3_WORST_SHIP_Y[mode];
2499
2501
  const shipRatio = (L) => {
2500
2502
  const y2 = shippedY(L, placed.C, placed.H);
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,13 +2525,15 @@ 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);
2522
2533
  const got = Math.abs(apcaLc(apcaYAt(placed.L, clampChromaToGamut(placed.L, placed.C, placed.H), placed.H), refA));
2523
2534
  if (got < sp.require.targetLc - APCA_TOL_LC) unresolvable = `stop ${sp.stop}: |Lc| ${got.toFixed(1)} < required ${sp.require.targetLc}`;
2524
2535
  } else if (sp.require?.metric === "min-separation") {
2525
- const ref = stops.find((s) => s.stop === (sp.require.against === "paper-99" ? 1 : sp.stop - 1));
2536
+ const ref = stops.find((s) => s.stop === (sp.require.against === "paper-1" ? 1 : sp.stop - 1));
2526
2537
  const rad2 = (h) => h * Math.PI / 180;
2527
2538
  const gC = clampChromaToGamut(placed.L, placed.C, placed.H);
2528
2539
  const got = Math.sqrt((placed.L - ref.L) ** 2 + (gC * Math.cos(rad2(placed.H)) - ref.C * Math.cos(rad2(ref.H))) ** 2 + (gC * Math.sin(rad2(placed.H)) - ref.C * Math.sin(rad2(ref.H))) ** 2);
@@ -2678,7 +2689,7 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
2678
2689
  const dark = darkRamp.stops.filter((s) => s.stop >= 1).map(toStop).sort((a, b) => a.stop - b.stop);
2679
2690
  const p0Light = lightRamp.stops.find((s) => s.stop === 0);
2680
2691
  const p0Dark = darkRamp.stops.find((s) => s.stop === 0);
2681
- const inkPole = (arr, L) => arr.find((s) => s.stop === 11) ? makeStop(12, L, 0, arr.find((s) => s.stop === 11).H) : void 0;
2692
+ const penPole = (arr, L) => arr.find((s) => s.stop === 11) ? makeStop(12, L, 0, arr.find((s) => s.stop === 11).H) : void 0;
2682
2693
  const cta = makeStop(9, lightRamp.roles.cta.L, lightRamp.roles.cta.C, lightRamp.roles.cta.H);
2683
2694
  const ctaDark = makeStop(9, darkRamp.roles.cta.L, darkRamp.roles.cta.C, darkRamp.roles.cta.H);
2684
2695
  const roleStop = (r, stop) => makeStop(stop, r.L, r.C, r.H);
@@ -2702,8 +2713,8 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
2702
2713
  identityHex: hex.toUpperCase(),
2703
2714
  paper0: p0Light ? makeStop(0, p0Light.L, p0Light.C, p0Light.H) : void 0,
2704
2715
  paper0Dark: p0Dark ? makeStop(0, p0Dark.L, p0Dark.C, p0Dark.H) : void 0,
2705
- ink0: inkPole(light, 0),
2706
- ink0Dark: inkPole(dark, 1)
2716
+ pen100: penPole(light, 0),
2717
+ pen100Dark: penPole(dark, 1)
2707
2718
  };
2708
2719
  }
2709
2720
  function generateNeutralScale(brandH, level = "default", contrastProfile) {
@@ -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]);
@@ -2775,8 +2790,7 @@ function generateSubtleSecondary(hex, opts) {
2775
2790
 
2776
2791
  // src/engine/tokenDescriptions.ts
2777
2792
  var AA_LARGE = "AA large text and UI elements";
2778
- var AA_BODY = "AA standard body text & Level AAA large text";
2779
- var AAA_BODY = "AAA standard body text";
2793
+ var AA_BODY = "AA standard body text";
2780
2794
  var FAMILY = {
2781
2795
  neutral: "neutral",
2782
2796
  brandPrimary: "brand",
@@ -2812,7 +2826,7 @@ var COLOR_WORD = {
2812
2826
  [FAMILY.info]: " (blue)"
2813
2827
  };
2814
2828
  var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
2815
- var WASH = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2829
+ var HIGHLIGHTER = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2816
2830
  var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
2817
2831
  var OVERLAY = {
2818
2832
  req: "translucent backgrounds that hold their reading on any paper",
@@ -2820,25 +2834,25 @@ var OVERLAY = {
2820
2834
  collides: true
2821
2835
  };
2822
2836
  var SCALE = {
2823
- "paper-99": PAPER,
2837
+ "paper-1": PAPER,
2824
2838
  "paper-99-overlay": OVERLAY,
2825
- "paper-97": PAPER,
2839
+ "paper-3": PAPER,
2826
2840
  "paper-97-overlay": OVERLAY,
2827
- "paper-95": { 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 },
2828
2842
  "paper-95-overlay": OVERLAY,
2829
- "wash-92": WASH,
2830
- "wash-89": WASH,
2831
- "wash-85": WASH,
2832
- "wash-80": WASH,
2843
+ "highlighter-8": HIGHLIGHTER,
2844
+ "highlighter-11": HIGHLIGHTER,
2845
+ "highlighter-15": HIGHLIGHTER,
2846
+ "highlighter-20": HIGHLIGHTER,
2833
2847
  // ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
2834
- // landing on the wax rows is worth its ic-ON-s noise — the one surviving body carrier)
2835
- "wax-74": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
2836
- "lead-53": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2837
- "ink-42": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2848
+ // landing on the crayon rows is worth its ic-ON-s noise — the one surviving body carrier)
2849
+ "crayon-26": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
2850
+ "pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2851
+ "pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2838
2852
  // ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
2839
2853
  // words, and a body carrying either floods that token's picker search — the C50 law;
2840
2854
  // "strong-emphasis" reworded 2026-08-28: str-ON-g fed the "on" flood)
2841
- "ink-30": { req: "heavy-emphasis text, inverted backgrounds", contrast: AAA_BODY, theming: solved, collides: true },
2855
+ "pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2842
2856
  // the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
2843
2857
  // these bodies on purpose: it stopped being a token label, so it floods nothing,
2844
2858
  // and a designer's "cta" query still lands on these rows.
@@ -2867,7 +2881,7 @@ var LINK_INVERSE = (state, contrast) => ({
2867
2881
  var SYSTEM = {
2868
2882
  "system/abs-black": ABS,
2869
2883
  "system/abs-white": ABS,
2870
- // (the community system/ink-0 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
2884
+ // (the community system/pen-100 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
2871
2885
  // the anchor is engine-resolved and rides the neutral in BOTH plugins now —
2872
2886
  // NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
2873
2887
  "system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
@@ -2883,7 +2897,7 @@ var SYSTEM = {
2883
2897
  "system/alpha/away-from-bg/08": OFFSET,
2884
2898
  "system/alpha/away-from-bg/16": OFFSET,
2885
2899
  // the inverse ladder: the offset rungs with the pole flipped per mode, for
2886
- // washes and edges over inverted backgrounds ("interaction" avoided in the
2900
+ // highlighters and edges over inverted backgrounds ("interaction" avoided in the
2887
2901
  // bodies: acti-ON would feed the "on" flood)
2888
2902
  "system/alpha/toward-bg/06": OFFSET_INVERSE,
2889
2903
  "system/alpha/toward-bg/08": OFFSET_INVERSE,
@@ -2900,10 +2914,10 @@ var SYSTEM = {
2900
2914
  "system/surface/high": PLANE("topmost plane \u2014 modals, dialogs"),
2901
2915
  "system/link/default/enabled": LINK("", AA_BODY),
2902
2916
  "system/link/default/hover": LINK(" pointer-over", AA_BODY),
2903
- "system/link/default/pressed": LINK(" pressed", AAA_BODY),
2917
+ "system/link/default/pressed": LINK(" pressed", AA_BODY),
2904
2918
  "system/link/inverse/enabled": LINK_INVERSE("", AA_BODY),
2905
2919
  "system/link/inverse/hover": LINK_INVERSE(" pointer-over", AA_BODY),
2906
- "system/link/inverse/pressed": LINK_INVERSE(" pressed", AAA_BODY)
2920
+ "system/link/inverse/pressed": LINK_INVERSE(" pressed", AA_BODY)
2907
2921
  };
2908
2922
  var PREFIXES = [
2909
2923
  // community spellings FIRST: the 2026-08-21 family rename made the ext word a
@@ -2950,15 +2964,15 @@ function checkCollision(brand, signalScale, signalDef, mode) {
2950
2964
  collides: hueGate && deltaE <= threshold
2951
2965
  };
2952
2966
  }
2953
- var HUE_COLLISION_WASH_DEG = 15;
2967
+ var HUE_COLLISION_HIGHLIGHTER_DEG = 15;
2954
2968
  var HUE_COLLISION_MIN_V = 0.5;
2955
2969
  var SECONDARY_NOTE_MIN_V = HUE_NOISE_C / VIVID_C;
2956
- var WASH_STOPS = [3, 4, 5, 6, 7];
2957
- function washProximity(brand, sig, mode) {
2970
+ var HIGHLIGHTER_STOPS = [3, 4, 5, 6, 7];
2971
+ function highlighterProximity(brand, sig, mode) {
2958
2972
  const b = mode === "light" ? brand.light : brand.dark;
2959
2973
  const s = mode === "light" ? sig.light : sig.dark;
2960
2974
  let dH = Infinity, dE = Infinity;
2961
- for (const k of WASH_STOPS) {
2975
+ for (const k of HIGHLIGHTER_STOPS) {
2962
2976
  const bs = b.find((x2) => x2.stop === k), ss = s.find((x2) => x2.stop === k);
2963
2977
  if (!bs || !ss) continue;
2964
2978
  dH = Math.min(dH, hueDistance(bs.H, ss.H));
@@ -2967,15 +2981,15 @@ function washProximity(brand, sig, mode) {
2967
2981
  return { dH, dE };
2968
2982
  }
2969
2983
  function checkHueCollision(brand, signalScale, signalDef, opts) {
2970
- const l = washProximity(brand, signalScale, "light");
2971
- const d = washProximity(brand, signalScale, "dark");
2984
+ const l = highlighterProximity(brand, signalScale, "light");
2985
+ const d = highlighterProximity(brand, signalScale, "dark");
2972
2986
  const vividness = Math.min(1, brand.brandC / VIVID_C);
2973
2987
  return {
2974
2988
  signal: signalDef.name,
2975
- dHueWash: { light: l.dH, dark: d.dH },
2976
- washDeltaE: { light: l.dE, dark: d.dE },
2989
+ dHueHighlighter: { light: l.dH, dark: d.dH },
2990
+ highlighterDeltaE: { light: l.dE, dark: d.dE },
2977
2991
  vividness,
2978
- collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_WASH_DEG
2992
+ collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_HIGHLIGHTER_DEG
2979
2993
  };
2980
2994
  }
2981
2995
  var YELLOW_SPLIT_H = 96;
@@ -3168,7 +3182,7 @@ function resolveBrand(hex, name, opts) {
3168
3182
  // (buildSignalScales/signalShift) — neutral alone stays outside; exact has enforce off.
3169
3183
  apcaClearance: opts?.apcaClearance ?? true,
3170
3184
  darkCtaFlatApp: opts?.darkCtaFlatApp,
3171
- inkGround: opts?.inkGround
3185
+ textGround: opts?.textGround
3172
3186
  };
3173
3187
  let scale = generateScale(hex, name, void 0, floor);
3174
3188
  let pending = [];
@@ -3251,7 +3265,7 @@ function resolveLinkTrio(linkHex, contrastProfile) {
3251
3265
  const s = resolveBrand(linkHex, "link", { skipCollisionRules: true, contrastProfile }).scale;
3252
3266
  const at = (arr, n) => {
3253
3267
  const st = arr.find((x2) => x2.stop === n);
3254
- if (!st) throw new Error(`resolveLinkTrio: the link resolve has no ink stop ${n}`);
3268
+ if (!st) throw new Error(`resolveLinkTrio: the link resolve has no pen stop ${n}`);
3255
3269
  return st;
3256
3270
  };
3257
3271
  return {
@@ -3264,16 +3278,16 @@ function resolveLinkTrio(linkHex, contrastProfile) {
3264
3278
  };
3265
3279
  }
3266
3280
  function resolveLinkInverseTrio(seedHex, contrastProfile) {
3267
- const ground = INK_30_GROUND[contrastProfile === "apca" ? "apca" : "wcag"];
3281
+ const ground = PEN_70_GROUND[contrastProfile === "apca" ? "apca" : "wcag"];
3268
3282
  const s = resolveBrand(seedHex, "link-inverse", {
3269
3283
  skipCollisionRules: true,
3270
3284
  contrastProfile,
3271
3285
  // the cross — ramp keyed, ground mode-swapped
3272
- inkGround: { light: ground.dark, dark: ground.light }
3286
+ textGround: { light: ground.dark, dark: ground.light }
3273
3287
  }).scale;
3274
3288
  const at = (arr, n) => {
3275
3289
  const st = arr.find((x2) => x2.stop === n);
3276
- if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no ink stop ${n}`);
3290
+ if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no pen stop ${n}`);
3277
3291
  return st;
3278
3292
  };
3279
3293
  return {
@@ -3287,12 +3301,12 @@ function resolveLinkInverseTrio(seedHex, contrastProfile) {
3287
3301
  };
3288
3302
  }
3289
3303
  function escapeCtaFamily(nScale, mode, contrastProfile) {
3290
- const strongInk = (mode === "light" ? nScale.light : nScale.dark).find((s) => s.stop === 11);
3291
- if (!strongInk) throw new Error("escapeCtaFamily: the neutral scale has no ink-11 stop");
3292
- const mk = (stop, L) => makeStop(stop, L, strongInk.C, strongInk.H);
3293
- const cta = mk(9, strongInk.L);
3294
- const ctaHover = mk(10, stateFillL(strongInk.L, mode, 1));
3295
- const ctaPressed = mk(11, stateFillL(strongInk.L, mode, 2));
3304
+ const strongText = (mode === "light" ? nScale.light : nScale.dark).find((s) => s.stop === 11);
3305
+ if (!strongText) throw new Error("escapeCtaFamily: the neutral scale has no ink-11 stop");
3306
+ const mk = (stop, L) => makeStop(stop, L, strongText.C, strongText.H);
3307
+ const cta = mk(9, strongText.L);
3308
+ const ctaHover = mk(10, stateFillL(strongText.L, mode, 1));
3309
+ const ctaPressed = mk(11, stateFillL(strongText.L, mode, 2));
3296
3310
  const onEnforce = contrastProfile !== "apca";
3297
3311
  const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
3298
3312
  const onFillIsWhite = onTextIsWhite(apcaY(cta.r, cta.g, cta.b), cta.L, cta.C, cta.H, onEnforce, onFloor);
@@ -3368,7 +3382,7 @@ function resolveTheme(input) {
3368
3382
  for (const def of SIGNALS2) {
3369
3383
  if (adoptedForSecondary.has(def.name)) continue;
3370
3384
  const h = checkHueCollision(scale2, effectiveOf(def.name), def, { minV: SECONDARY_NOTE_MIN_V });
3371
- if (h.collides) out.push(wording(def.name, Math.min(h.washDeltaE.light, h.washDeltaE.dark)));
3385
+ if (h.collides) out.push(wording(def.name, Math.min(h.highlighterDeltaE.light, h.highlighterDeltaE.dark)));
3372
3386
  }
3373
3387
  return out;
3374
3388
  };
@@ -3418,7 +3432,7 @@ function resolveTheme(input) {
3418
3432
  derived: true,
3419
3433
  notes: [
3420
3434
  `secondary derived from the brand color (default model, seed ${liftedHex})`,
3421
- ...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (wash \u0394E ${dE.toFixed(3)}) \u2014 it tracks the brand color`)
3435
+ ...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (highlighter \u0394E ${dE.toFixed(3)}) \u2014 it tracks the brand color`)
3422
3436
  ],
3423
3437
  distinctness: ctaDistinctness(primary.scale, scale2)
3424
3438
  },
@@ -3443,7 +3457,7 @@ function resolveTheme(input) {
3443
3457
  derived: false,
3444
3458
  notes: [
3445
3459
  `secondary keeps your color through the ramp; the cta is a tint of it (tint seed ${tintedHex})`,
3446
- ...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (wash \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
3460
+ ...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (highlighter \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
3447
3461
  ],
3448
3462
  distinctness: distinctness2
3449
3463
  },
@@ -3455,7 +3469,7 @@ function resolveTheme(input) {
3455
3469
  const scale = rSec.scale;
3456
3470
  const level = "standard";
3457
3471
  mergeSecondarySignals(scale, input.secondaryHex);
3458
- const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (wash \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
3472
+ const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (highlighter \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
3459
3473
  const distinctness = ctaDistinctness(primary.scale, scale);
3460
3474
  if (distinctness.close)
3461
3475
  notes.push(`secondary reads close to the primary (\u0394E ${Math.min(distinctness.light, distinctness.dark).toFixed(2)}) \u2014 consider a more distinct color`);
@@ -3514,7 +3528,7 @@ var alphaRootVars = (mode) => [
3514
3528
  ...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`),
3515
3529
  // the toward-bg ladder (the 2026-08-29 inverse ladder, renamed 2026-08-31): the
3516
3530
  // same rungs, pole flipped per mode — state layers for INVERTED grounds (never
3517
- // called a "wash" — that word is the tinted band's). One spelling with the Figma
3531
+ // called a "highlighter" — that word is the tinted band's). One spelling with the Figma
3518
3532
  // row: --alpha-toward-bg-06 ↔ system/alpha/toward-bg/06.
3519
3533
  ...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` --alpha-toward-bg-${offsetLeafName(r)}: ${offsetRgba(r, mode === "light" ? "dark" : "light")};`)
3520
3534
  ];
@@ -3541,7 +3555,7 @@ function brandKindBody(prefix, s, mode, page) {
3541
3555
  ` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
3542
3556
  ` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
3543
3557
  // the SOFT on-cta (owner 2026-08-04: "neutral cta on's should also be the alpha"): the
3544
- // neutral's cta is the scale-fed WASH-level fill, the system's other quiet cta, so its
3558
+ // neutral's cta is the scale-fed HIGHLIGHTER-level fill, the system's other quiet cta, so its
3545
3559
  // button text takes the pole AT ALPHA like the default-model secondary's — composited
3546
3560
  // over whatever state the fill is in, so hover/pressed carry their own legibility. Same
3547
3561
  // register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
@@ -3571,16 +3585,16 @@ function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBor
3571
3585
  const p3Dark = brandKindP3Body(CSS_NEUTRAL, s, "dark");
3572
3586
  return [
3573
3587
  `${selector} {`,
3574
- ` --${PAPER_100}: ${p02(s.paper0, "#ffffff")};`,
3575
- ` --${INK_0}: ${p02(s.ink0, "#000000")};`,
3588
+ ` --${PAPER_0}: ${p02(s.paper0, "#ffffff")};`,
3589
+ ` --${PEN_100}: ${p02(s.pen100, "#000000")};`,
3576
3590
  // the neutral IS the page, so it is judged against its own paper stop
3577
3591
  // the neutral's overlays never read the visibility bar (the exempt family), so
3578
3592
  // the brandless chrome context passes 0
3579
3593
  ...brandKindBody(CSS_NEUTRAL, s, "light", nPage("light")),
3580
3594
  `}`,
3581
3595
  `${selector}[data-theme="dark"] {`,
3582
- ` --${PAPER_100}: ${p02(s.paper0Dark, "#000000")};`,
3583
- ` --${INK_0}: ${p02(s.ink0Dark, "#ffffff")};`,
3596
+ ` --${PAPER_0}: ${p02(s.paper0Dark, "#000000")};`,
3597
+ ` --${PEN_100}: ${p02(s.pen100Dark, "#ffffff")};`,
3584
3598
  ...brandKindBody(CSS_NEUTRAL, s, "dark", nPage("dark")),
3585
3599
  `}`,
3586
3600
  ...p3Light.length || p3Dark.length ? [
@@ -3669,8 +3683,8 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
3669
3683
  const brandIdentity = ` --${CSS_BRAND}-identity: ${scale.identityHex};`;
3670
3684
  const secondaryIdentity = secondary ? ` --${CSS_SECONDARY}-identity: ${secondary.identityHex};` : ` --${CSS_SECONDARY}-identity: var(--${CSS_BRAND}-identity);`;
3671
3685
  const p0hex = (s, fallback) => s ? stopHex(s) : fallback;
3672
- const lightAnchors = [` --${PAPER_100}: ${p0hex(nScale.paper0, "#ffffff")};`, ` --${INK_0}: ${p0hex(nScale.ink0, "#000000")};`];
3673
- const darkAnchors = [` --${PAPER_100}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` --${INK_0}: ${p0hex(nScale.ink0Dark, "#ffffff")};`];
3686
+ const lightAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0, "#ffffff")};`, ` --${PEN_100}: ${p0hex(nScale.pen100, "#000000")};`];
3687
+ const darkAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` --${PEN_100}: ${p0hex(nScale.pen100Dark, "#ffffff")};`];
3674
3688
  const linkTrio = linkHex ? resolveLinkTrio(linkHex, contrastProfile) : null;
3675
3689
  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)};`] : [
3676
3690
  ` --link: var(--${CSS_BRAND}-${stopTokenName(9)});`,
@@ -3858,17 +3872,17 @@ function themeToFigma(r, input) {
3858
3872
  const build = (mode) => {
3859
3873
  const p02 = mode === "light" ? nScale.paper0 : nScale.paper0Dark;
3860
3874
  const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, neutralExtra(mode));
3861
- const i02 = mode === "light" ? nScale.ink0 : nScale.ink0Dark;
3862
- const spliceInk0 = (g3) => {
3875
+ const i02 = mode === "light" ? nScale.pen100 : nScale.pen100Dark;
3876
+ const splicePen100 = (g3) => {
3863
3877
  if (!i02) return g3;
3864
3878
  const out = {};
3865
3879
  for (const [k, v2] of Object.entries(g3)) {
3866
3880
  out[k] = v2;
3867
- if (k === stopTokenName(11)) out[INK_0] = colorFromStop(i02);
3881
+ if (k === stopTokenName(11)) out[PEN_100] = colorFromStop(i02);
3868
3882
  }
3869
3883
  return out;
3870
3884
  };
3871
- const neutralGroup = spliceInk0(p02 ? { [PAPER_100]: colorFromStop(p02), ...ramp } : ramp);
3885
+ const neutralGroup = splicePen100(p02 ? { [PAPER_0]: colorFromStop(p02), ...ramp } : ramp);
3872
3886
  const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, brandExtra(secondary, mode, CSS_FAMILY.brandSecondary));
3873
3887
  if (input.secondaryStyle === "outline" && input.secondary) {
3874
3888
  const s8 = secondary[mode].find((s) => s.stop === 8);
@@ -3906,12 +3920,12 @@ function themeToFigma(r, input) {
3906
3920
  putLeaf(g3, STAMP_ON, colorFromHex(esc.onFillIsWhite));
3907
3921
  }
3908
3922
  }
3909
- const scaleInkAt = (n) => {
3923
+ const scaleTextAt = (n) => {
3910
3924
  const s = scale[mode].find((x2) => x2.stop === n);
3911
- if (!s) throw new Error(`themeToFigma link: the brand scale has no ink stop ${n}`);
3925
+ if (!s) throw new Error(`themeToFigma link: the brand scale has no pen stop ${n}`);
3912
3926
  return s;
3913
3927
  };
3914
- 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(scaleInkAt(9)), "link-hover": colorFromStop(scaleInkAt(10)), "link-pressed": colorFromStop(scaleInkAt(11)) };
3928
+ 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)) };
3915
3929
  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) };
3916
3930
  const g2 = {
3917
3931
  brand: brandGroup,