okchroma 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- // highlight-8
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
- // highlight-8
434
+ // crayon-26
435
435
  [0.6, 0.4],
436
436
  // knee (ex-scaffold)
437
437
  [0.64, 0.347],
@@ -800,26 +800,136 @@ function perceptualDarkC(L, H2, nativeC, keep = KEEP_DARK, gamut = MASTER_GAMUT)
800
800
  return solveCForApparent(L, H2, grayApparentL(L, gamut) + keep * meanBoost(L, nativeC, gamut), gamut);
801
801
  }
802
802
 
803
+ // src/engine/tokenNames.ts
804
+ var SHARED_NAMES = {
805
+ 1: "paper-1",
806
+ 2: "paper-3",
807
+ // stop 3 renamed wash-3 → paper-3 (owner 2026-07-24, elevation round): it is a
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 highlighter-collision machinery
810
+ // (collision.ts HIGHLIGHTER_STOPS) deliberately still spans stops 3–7. (The requirement
811
+ // spec's `group` label followed the band to 'paper' on 2026-08-31 — labels only,
812
+ // the spans never moved; see spec.ts groupOf.)
813
+ // Stage B (owner 2026-08-07, names only): relabeled to the ROOT_L-derived digit,
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
+ // 2026-07-29 highlight collapse — the old highlight-9 and on-highlight are
823
+ // deleted; this stop carries both jobs, its on-color a paper token), the between
824
+ // text stop (a value the retired text-cta hover state used to generate bespokely,
825
+ // promoted to a normal stop), and the strong text stop. The three read as states
826
+ // ARE the text-style cta (owner 2026-08-12). C49 restored the top
827
+ // two pre-collapse stop INDICES (11, and the off-scale anchor's 12 — emitted as a
828
+ // literal in cssRender, not through this table) with the between stop taking the
829
+ // vacated index 10; the indices (9/10/11) are unchanged since.
830
+ 9: "pencil-47",
831
+ 10: "pen-58",
832
+ 11: "pen-70"
833
+ };
834
+ var SCALE_STOP_COUNT = Object.keys(SHARED_NAMES).length;
835
+ function stopTokenName(stop) {
836
+ const name = SHARED_NAMES[stop];
837
+ if (!name) throw new Error(`stopTokenName: unexpected stop ${stop}`);
838
+ return name;
839
+ }
840
+ var PAPER_0 = "paper-0";
841
+ var PEN_100 = "pen-100";
842
+ var STAMP_FILL = "stamp-fill";
843
+ var STAMP_FILL_HOVER = "stamp-fill-hover";
844
+ var STAMP_FILL_PRESSED = "stamp-fill-pressed";
845
+ var STAMP_EDGE = "stamp-edge";
846
+ var STAMP_ON = "stamp-on";
847
+ var STAMP_STATE_LEAVES = {
848
+ [STAMP_FILL]: "stamp/fill",
849
+ [STAMP_FILL_HOVER]: "stamp/fill-hover",
850
+ [STAMP_FILL_PRESSED]: "stamp/fill-pressed",
851
+ [STAMP_EDGE]: "stamp/edge",
852
+ [STAMP_ON]: "stamp/on"
853
+ };
854
+ var STAMP_LEAF = {
855
+ FILL: STAMP_STATE_LEAVES[STAMP_FILL],
856
+ FILL_HOVER: STAMP_STATE_LEAVES[STAMP_FILL_HOVER],
857
+ FILL_PRESSED: STAMP_STATE_LEAVES[STAMP_FILL_PRESSED],
858
+ EDGE: STAMP_STATE_LEAVES[STAMP_EDGE],
859
+ ON: STAMP_STATE_LEAVES[STAMP_ON]
860
+ };
861
+ var SYSTEM_LEAF = {
862
+ ABS_BLACK: "system/abs-black",
863
+ ABS_WHITE: "system/abs-white",
864
+ SURFACE: {
865
+ DIM: "system/surface/dim",
866
+ LOW: "system/surface/low",
867
+ MID: "system/surface/mid",
868
+ HIGH: "system/surface/high"
869
+ },
870
+ ALPHA: {
871
+ TRANSPARENT: "system/alpha/transparent",
872
+ SCRIM: "system/alpha/abs-black-060",
873
+ INK: "system/alpha/ink",
874
+ // the DIRECTIONAL ladders (owner 2026-08-31, renaming the 2026-08-18 bare rungs
875
+ // and the 2026-08-29 inverse ladder): away-from-bg is the page-polarity pole
876
+ // (black in light, white in dark), toward-bg the flipped pole — STATE LAYERS for
877
+ // INVERTED grounds, whose polarity is opposite the page's. (Never call toward-bg
878
+ // a "highlighter": that word belongs to the tinted band stops.) The reversal lives in
879
+ // the engine: a consumer on an inverted ground names one token and never picks a
880
+ // pole. Colored fills never ride these; their states stay engine-solved stops.
881
+ // Rungs are two-digit percent, harmonizing with shadow-04/08/12.
882
+ AWAY_FROM_BG_06: "system/alpha/away-from-bg/06",
883
+ AWAY_FROM_BG_08: "system/alpha/away-from-bg/08",
884
+ AWAY_FROM_BG_16: "system/alpha/away-from-bg/16",
885
+ TOWARD_BG_06: "system/alpha/toward-bg/06",
886
+ TOWARD_BG_08: "system/alpha/toward-bg/08",
887
+ TOWARD_BG_16: "system/alpha/toward-bg/16",
888
+ SHADOW_04: "system/alpha/shadow-04",
889
+ SHADOW_08: "system/alpha/shadow-08",
890
+ SHADOW_12: "system/alpha/shadow-12"
891
+ }
892
+ };
893
+ var SURFACE_PLANE_LAW = {
894
+ [SYSTEM_LEAF.SURFACE.DIM]: { light: SHARED_NAMES[3], dark: PAPER_0 },
895
+ [SYSTEM_LEAF.SURFACE.LOW]: { light: SHARED_NAMES[2], dark: SHARED_NAMES[1] },
896
+ [SYSTEM_LEAF.SURFACE.MID]: { light: SHARED_NAMES[1], dark: SHARED_NAMES[2] },
897
+ [SYSTEM_LEAF.SURFACE.HIGH]: { light: PAPER_0, dark: SHARED_NAMES[3] }
898
+ };
899
+ var TOKEN_ORDER = [
900
+ ...Object.values(SHARED_NAMES),
901
+ STAMP_FILL,
902
+ STAMP_FILL_HOVER,
903
+ STAMP_FILL_PRESSED,
904
+ STAMP_ON,
905
+ "identity"
906
+ ];
907
+ function tokenOrder(name) {
908
+ const i = TOKEN_ORDER.indexOf(name);
909
+ return i === -1 ? TOKEN_ORDER.length : i;
910
+ }
911
+
803
912
  // src/engine/requirements/spec.ts
804
- var groupOf = (stop) => stop <= 2 ? "paper" : stop <= 7 ? "wash" : stop === 8 ? "highlight" : "ink";
913
+ var textLane = (g2) => g2 === "pencil" || g2 === "pen" || g2 === "lead" || g2 === "ink";
914
+ var groupOf = (stop) => (stop === 0 ? PAPER_0 : stopTokenName(stop)).split("-")[0];
805
915
  var PAPER0_DARK_ROOT_L = 0.16;
806
916
  var PL_LADDER = { hue: "warm-drift", L: "perceptual", chroma: "ladder" };
807
917
  var PL_TEXT = { hue: "warm-drift", L: "perceptual", chroma: "brand" };
808
918
  var P_LIFT = { hue: "warm-torsion", L: "perceptual-lift", chroma: "ladder" };
809
919
  var P_FIXED = { hue: "warm-torsion", L: "fixed", chroma: "ladder" };
810
920
  var P_TEXT = { hue: "warm-torsion", L: "perceptual", chroma: "brand" };
811
- var S8 = { metric: "wcag", against: "paper-95", target: STOP_8_NONTEXT_CONTRAST, level: "AA" };
812
- var T9 = { metric: "wcag", against: "paper-97", target: INK_9_CONTRAST, level: "AA" };
813
- var T10 = { metric: "wcag", against: "wash-80", target: INK_10_CONTRAST, level: "AA" };
814
- 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" };
815
925
  var ONS = { onFill: { metric: "apca-pole", enforce: true, ratioFloor: 4.5, coEnforceLc: 65 } };
816
926
  var LIGHT = {
817
927
  stops: [
818
- // paper-100 (paper-0 pre-Stage-B): the resolved ladder extreme — in light it genuinely is white (rootL 1.0, zero chroma)
819
- { stop: 0, rootL: 1, group: "paper", produce: { hue: "warm-drift", L: "fixed", chroma: "ladder" }, satFraction: SCALE_C_LIGHT[0].sat, baseC: SCALE_C_LIGHT[0].base },
820
- // paper/wash/wax-74: perceptual ladder/envelope blend on the geometric ROOT_L_LIGHT scaffold. Separation
928
+ // paper-0 (paper-0 pre-Stage-B): the resolved ladder extreme — in light it genuinely is white (rootL 1.0, zero chroma)
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/highlighter/crayon-26: perceptual ladder/envelope blend on the geometric ROOT_L_LIGHT scaffold. Separation
821
931
  // falls out of the shape (see above) — no min-separation require. Only stop 8 carries a require: the WCAG
822
- // 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)).
823
933
  ...[1, 2, 3, 4, 5, 6, 7, 8].map((stop) => ({
824
934
  stop,
825
935
  rootL: ROOT_L_LIGHT[stop],
@@ -829,12 +939,12 @@ var LIGHT = {
829
939
  baseC: SCALE_C_LIGHT[stop].base,
830
940
  require: stop === 8 ? S8 : void 0
831
941
  })),
832
- // ink text: perceptual + contrast-required. lead-53 (ink-9) is ALSO the emphasis fill (the
833
- // 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) —
834
944
  // a normal stop like its neighbors; the three together are the text-register cta.
835
- { stop: 9, rootL: ROOT_L_LIGHT[9], group: "ink", produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[9].inkMult, inkMaxC: SCALE_C_LIGHT[9].inkMaxC, chromaFloor: SCALE_C_LIGHT[9].chromaFloor, require: T9 },
836
- { stop: 10, rootL: ROOT_L_LIGHT[10], group: "ink", produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[10].inkMult, inkMaxC: SCALE_C_LIGHT[10].inkMaxC, chromaFloor: SCALE_C_LIGHT[10].chromaFloor, require: T10 },
837
- { stop: 11, rootL: ROOT_L_LIGHT[11], group: "ink", 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 }
838
948
  ],
839
949
  roles: [
840
950
  { role: "cta", produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: 0, chromaMult: 1 },
@@ -845,10 +955,10 @@ var LIGHT = {
845
955
  };
846
956
  var DARK = {
847
957
  stops: [
848
- // 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
849
959
  // absolute void (the old hard-coded #000000 was "too much"). Lift applies like the rest of the scale.
850
- { stop: 0, rootL: PAPER0_DARK_ROOT_L, group: "paper", produce: P_LIFT, satFraction: SCALE_C_DARK[0].sat },
851
- // paper/wash 1–7: perceptual on the dark scaffold. stop 8: FIXED at the hand-placed scaffold BUT with the
960
+ { stop: 0, rootL: PAPER0_DARK_ROOT_L, group: groupOf(0), produce: P_LIFT, satFraction: SCALE_C_DARK[0].sat },
961
+ // paper/highlighter 1–7: perceptual on the dark scaffold. stop 8: FIXED at the hand-placed scaffold BUT with the
852
962
  // 3:1 non-text require DECLARED (the Stage-5 flip, owner-approved) — the blue-recede failure is prevented
853
963
  // BY RULE, not by patch. The require now genuinely PLACES the stop rather than catching a few low-luminance
854
964
  // hues: on the delta-carry path it solves from the sentinel every time, and the old claim that "most hues
@@ -862,11 +972,11 @@ var DARK = {
862
972
  satFraction: SCALE_C_DARK[stop].sat,
863
973
  require: stop === 8 ? S8 : void 0
864
974
  })),
865
- // 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
866
976
  // clears them for every hue (the gate proves it), so values don't move — but the guarantee is now a rule.
867
- { stop: 9, rootL: ROOT_L_DARK[9], group: "ink", produce: P_TEXT, chromaMult: SCALE_C_DARK[9].inkMult, inkMaxC: SCALE_C_DARK[9].inkMaxC, chromaFloor: SCALE_C_DARK[9].chromaFloor, require: T9 },
868
- { stop: 10, rootL: ROOT_L_DARK[10], group: "ink", produce: P_TEXT, chromaMult: SCALE_C_DARK[10].inkMult, inkMaxC: SCALE_C_DARK[10].inkMaxC, chromaFloor: SCALE_C_DARK[10].chromaFloor, require: T10 },
869
- { stop: 11, rootL: ROOT_L_DARK[11], group: "ink", 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 }
870
980
  ],
871
981
  roles: [
872
982
  { role: "cta", produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
@@ -877,7 +987,7 @@ var DARK = {
877
987
  };
878
988
  var MODE_SPECS = { light: LIGHT, dark: DARK };
879
989
 
880
- // ../../../Users/emilygerrity/okchroma/node_modules/helmlab/dist/index.js
990
+ // node_modules/helmlab/dist/index.js
881
991
  function o0(n) {
882
992
  let t = new Float64Array(9);
883
993
  for (let e = 0; e < 3; e++) for (let s = 0; s < 3; s++) t[e * 3 + s] = n[e][s];
@@ -1793,7 +1903,7 @@ function p2Diff(a, b) {
1793
1903
  var DEFAULT_APCA_LC_MAP = { 3: 30, 4.5: 75, 6.5: 85, 7: 90 };
1794
1904
  function toApca(req, lcMap) {
1795
1905
  if (req.metric !== "wcag") return req;
1796
- 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 };
1797
1907
  const targetLc = lcMap[req.target];
1798
1908
  if (targetLc === void 0) throw new Error(`apca profile: no Lc mapping for wcag target ${req.target}`);
1799
1909
  return { metric: "apca", against: req.against, targetLc };
@@ -1807,7 +1917,7 @@ function withProfile(spec, profile, lcMap = DEFAULT_APCA_LC_MAP) {
1807
1917
  // (The stop-9 CARVE-OUT is gone with the stop, owner 2026-07-29. It existed because
1808
1918
  // highlight-9's require was a fill-vs-its-own-plane bar with no honest slot in a map
1809
1919
  // of TEXT bars — 3 → 30 non-text, 4.5 → 75 body, 7 → 90 — so the apca lane kept its
1810
- // 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
1811
1921
  // 75 slot is its correct translation, and it maps like every other require.)
1812
1922
  stops: spec.stops.map((s) => s.require ? { ...s, require: toApca(s.require, lcMap) } : s),
1813
1923
  ons: {
@@ -1969,16 +2079,16 @@ function separationClampLight(ctx, placed, chromaAt, ref, target) {
1969
2079
  return { L: pass, C: ceilingAt(pass), H: ctx.lightHueAt(pass) };
1970
2080
  }
1971
2081
  function placeLightText(ctx, rootL, cMult, maxLFor, deepen, maxC = Infinity) {
1972
- const inkC = Math.min(cMult * ctx.brandC, maxC);
1973
- 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));
1974
2084
  let H2 = ctx.lightHueAt(anchorL);
1975
- let C2 = clampChromaToGamut(anchorL, inkC, H2);
2085
+ let C2 = clampChromaToGamut(anchorL, textC, H2);
1976
2086
  let L = Math.min(anchorL, maxLFor(C2, H2));
1977
2087
  H2 = ctx.lightHueAt(L);
1978
- C2 = clampChromaToGamut(L, inkC, H2);
2088
+ C2 = clampChromaToGamut(L, textC, H2);
1979
2089
  L = Math.min(anchorL, maxLFor(C2, H2));
1980
2090
  L = Math.min(L - deepen, maxLFor(C2, ctx.lightHueAt(L - deepen)));
1981
- 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) };
1982
2092
  }
1983
2093
  function onFillIsWhiteLight(ctx, enforce, ratioFloor) {
1984
2094
  const fill9ApcaY = apcaYAt(ctx.scaleL, clampChromaToGamut(ctx.scaleL, ctx.cAt("light", ctx.scaleL, ctx.brandC), ctx.brandH), ctx.brandH);
@@ -1992,7 +2102,7 @@ function buildDarkContext(ctx, specFloorL = DARK_CTA_MIN_L) {
1992
2102
  return { dark9L, darkC9, darkHueAtL };
1993
2103
  }
1994
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));
1995
- 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));
1996
2106
  function placeDark(dctx, rootL, chromaAt, lift = false) {
1997
2107
  let L = perceptualRungL(rootL, chromaAt(rootL), dctx.darkHueAtL(rootL));
1998
2108
  if (lift) L = Math.max(L, rootL);
@@ -2253,7 +2363,7 @@ function resolveRamp(hex, mode, spec, opts) {
2253
2363
  const seed = { hex, ...o };
2254
2364
  const ctx = buildContext(hex, opts);
2255
2365
  spec ?? (spec = MODE_SPECS[mode]);
2256
- if (ctx.opts?.inkGround) {
2366
+ if (ctx.opts?.textGround) {
2257
2367
  spec = {
2258
2368
  ...spec,
2259
2369
  stops: spec.stops.map((s) => s.stop === 10 && s.require?.metric === "wcag" ? { ...s, require: { ...s.require, target: 6.5 } } : s)
@@ -2275,12 +2385,12 @@ function resolveRamp(hex, mode, spec, opts) {
2275
2385
  if (!ref) throw new Error(`stop ${forWhom}: require against stop ${stopNum} but it is not resolved yet`);
2276
2386
  return ref;
2277
2387
  };
2278
- const AGAINST_STOP = { "paper-99": 1, "paper-97": 2, "paper-95": 3, "wash-80": 7 };
2388
+ const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "highlighter-20": 7 };
2279
2389
  const declaredAnchor = (req) => req.metric === "min-separation" ? 1 : AGAINST_STOP[req.against] ?? 2;
2280
2390
  const wcagAnchorStop = (req, stop) => stop >= 9 && declaredAnchor(req) <= 3 ? 3 : declaredAnchor(req);
2281
- const inkGround = ctx.opts?.inkGround?.[mode];
2282
- const groundOf = (req, stop) => inkGround && stop >= 9 ? inkGround : refOf(wcagAnchorStop(req, stop), stop);
2283
- const apcaGroundOf = (req, stop) => inkGround && stop >= 9 ? inkGround : refOf(declaredAnchor(req), stop);
2391
+ const textGround = ctx.opts?.textGround?.[mode];
2392
+ const groundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(wcagAnchorStop(req, stop), stop);
2393
+ const apcaGroundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(declaredAnchor(req), stop);
2284
2394
  const NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
2285
2395
  const NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
2286
2396
  const maxLForOf = (req, forWhom, withMargin) => {
@@ -2298,14 +2408,14 @@ function resolveRamp(hex, mode, spec, opts) {
2298
2408
  }
2299
2409
  throw new Error(`stop ${forWhom}: ${req.metric} is not a contrast require`);
2300
2410
  };
2301
- const INK_DEEPEN = 0;
2411
+ const TEXT_DEEPEN = 0;
2302
2412
  for (const sp of spec.stops) {
2303
2413
  let placed;
2304
2414
  let clamped = false, unresolvable;
2305
2415
  if (mode === "light") {
2306
- if (sp.group === "ink") {
2307
- if (!sp.require) throw new Error(`light ink stop ${sp.stop} must declare a contrast require`);
2308
- placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false), INK_DEEPEN, sp.inkMaxC);
2416
+ if (textLane(sp.group)) {
2417
+ if (!sp.require) throw new Error(`light pen stop ${sp.stop} must declare a contrast require`);
2418
+ placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false), TEXT_DEEPEN, sp.textMaxC);
2309
2419
  clamped = true;
2310
2420
  } else if (sp.produce.L === "fixed") {
2311
2421
  const chromaAt = lightScaleChromaAt(ctx, sp.baseC ?? 0, sp.satFraction ?? 1);
@@ -2317,7 +2427,7 @@ function resolveRamp(hex, mode, spec, opts) {
2317
2427
  placed = placeLightScale(ctx, sp.rootL, chromaAt, contrastReq ? maxLForOf(contrastReq, sp.stop, true) : void 0, targetChromaAt);
2318
2428
  clamped = !!contrastReq;
2319
2429
  if (sp.require?.metric === "min-separation") {
2320
- const refStop = sp.require.against === "paper-99" ? 1 : sp.stop - 1;
2430
+ const refStop = sp.require.against === "paper-1" ? 1 : sp.stop - 1;
2321
2431
  const ref = stops.find((s) => s.stop === refStop);
2322
2432
  if (!ref) throw new Error(`stop ${sp.stop}: min-separation against stop ${refStop} but it is not resolved yet`);
2323
2433
  const before = placed.L;
@@ -2327,8 +2437,8 @@ function resolveRamp(hex, mode, spec, opts) {
2327
2437
  }
2328
2438
  } else {
2329
2439
  const d = dctx;
2330
- const inkTwin = sp.group === "ink" && ctx.opts?.deltaCarry && ctx.opts?.chromaCurve ? ctx.opts.deltaLightStops?.find((s) => s.stop === sp.stop) : void 0;
2331
- const chromaAt = sp.group === "ink" ? 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);
2440
+ const textTwin = textLane(sp.group) && ctx.opts?.deltaCarry && ctx.opts?.chromaCurve ? ctx.opts.deltaLightStops?.find((s) => s.stop === sp.stop) : void 0;
2441
+ 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);
2332
2442
  let spineH = null;
2333
2443
  const dl = ctx.opts?.deltaLightStops;
2334
2444
  const ls = dl && sp.stop >= 1 && sp.stop <= 8 ? dl.find((s) => s.stop === sp.stop) : void 0;
@@ -2348,7 +2458,7 @@ function resolveRamp(hex, mode, spec, opts) {
2348
2458
  }
2349
2459
  if (ctx.opts.deltaHKPlace) L = perceptualRungL(sp.rootL, ls.C, ls.H);
2350
2460
  if (ctx.opts.deltaLiftFloor) L = Math.max(L, sp.rootL);
2351
- if (ctx.opts.deltaChromaEq && sp.group !== "ink") C2 = ctx.cAt("dark", L, perceptualDarkC(L, ls.H, ctx.brandC));
2461
+ if (ctx.opts.deltaChromaEq && !textLane(sp.group)) C2 = ctx.cAt("dark", L, perceptualDarkC(L, ls.H, ctx.brandC));
2352
2462
  placed = { L, C: C2, H: spineH ?? ls.H };
2353
2463
  } else if (sp.produce.L === "fixed") {
2354
2464
  placed = ls ? placeDarkDelta(d, sp.rootL, chromaAt, ls) : { L: sp.rootL, C: chromaAt(sp.rootL), H: d.darkHueAtL(sp.rootL) };
@@ -2384,7 +2494,7 @@ function resolveRamp(hex, mode, spec, opts) {
2384
2494
  if (sp.require?.metric === "wcag") {
2385
2495
  const anchor = groundOf(sp.require, sp.stop);
2386
2496
  const anchorShipY = shippedY(anchor.L, anchor.C, anchor.H);
2387
- const paperBound = sp.stop >= 8 && !(inkGround && sp.stop >= 9);
2497
+ const paperBound = sp.stop >= 8 && !(textGround && sp.stop >= 9);
2388
2498
  const crossBoundY = wcagAnchorStop(sp.require, sp.stop) === 7 ? NEUTRAL_W80_WORST_SHIP_Y[mode] : NEUTRAL_P3_WORST_SHIP_Y[mode];
2389
2499
  const shipRatio = (L) => {
2390
2500
  const y2 = shippedY(L, placed.C, placed.H);
@@ -2412,7 +2522,7 @@ function resolveRamp(hex, mode, spec, opts) {
2412
2522
  const got = Math.abs(apcaLc(apcaYAt(placed.L, clampChromaToGamut(placed.L, placed.C, placed.H), placed.H), refA));
2413
2523
  if (got < sp.require.targetLc - APCA_TOL_LC) unresolvable = `stop ${sp.stop}: |Lc| ${got.toFixed(1)} < required ${sp.require.targetLc}`;
2414
2524
  } else if (sp.require?.metric === "min-separation") {
2415
- const ref = stops.find((s) => s.stop === (sp.require.against === "paper-99" ? 1 : sp.stop - 1));
2525
+ const ref = stops.find((s) => s.stop === (sp.require.against === "paper-1" ? 1 : sp.stop - 1));
2416
2526
  const rad2 = (h) => h * Math.PI / 180;
2417
2527
  const gC = clampChromaToGamut(placed.L, placed.C, placed.H);
2418
2528
  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);
@@ -2568,7 +2678,7 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
2568
2678
  const dark = darkRamp.stops.filter((s) => s.stop >= 1).map(toStop).sort((a, b) => a.stop - b.stop);
2569
2679
  const p0Light = lightRamp.stops.find((s) => s.stop === 0);
2570
2680
  const p0Dark = darkRamp.stops.find((s) => s.stop === 0);
2571
- const inkPole = (arr, L) => arr.find((s) => s.stop === 11) ? makeStop(12, L, 0, arr.find((s) => s.stop === 11).H) : void 0;
2681
+ const penPole = (arr, L) => arr.find((s) => s.stop === 11) ? makeStop(12, L, 0, arr.find((s) => s.stop === 11).H) : void 0;
2572
2682
  const cta = makeStop(9, lightRamp.roles.cta.L, lightRamp.roles.cta.C, lightRamp.roles.cta.H);
2573
2683
  const ctaDark = makeStop(9, darkRamp.roles.cta.L, darkRamp.roles.cta.C, darkRamp.roles.cta.H);
2574
2684
  const roleStop = (r, stop) => makeStop(stop, r.L, r.C, r.H);
@@ -2592,8 +2702,8 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
2592
2702
  identityHex: hex.toUpperCase(),
2593
2703
  paper0: p0Light ? makeStop(0, p0Light.L, p0Light.C, p0Light.H) : void 0,
2594
2704
  paper0Dark: p0Dark ? makeStop(0, p0Dark.L, p0Dark.C, p0Dark.H) : void 0,
2595
- ink0: inkPole(light, 0),
2596
- ink0Dark: inkPole(dark, 1)
2705
+ pen100: penPole(light, 0),
2706
+ pen100Dark: penPole(dark, 1)
2597
2707
  };
2598
2708
  }
2599
2709
  function generateNeutralScale(brandH, level = "default", contrastProfile) {
@@ -2665,8 +2775,7 @@ function generateSubtleSecondary(hex, opts) {
2665
2775
 
2666
2776
  // src/engine/tokenDescriptions.ts
2667
2777
  var AA_LARGE = "AA large text and UI elements";
2668
- var AA_BODY = "AA standard body text & Level AAA large text";
2669
- var AAA_BODY = "AAA standard body text";
2778
+ var AA_BODY = "AA standard body text";
2670
2779
  var FAMILY = {
2671
2780
  neutral: "neutral",
2672
2781
  brandPrimary: "brand",
@@ -2702,7 +2811,7 @@ var COLOR_WORD = {
2702
2811
  [FAMILY.info]: " (blue)"
2703
2812
  };
2704
2813
  var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
2705
- var WASH = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2814
+ var HIGHLIGHTER = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
2706
2815
  var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
2707
2816
  var OVERLAY = {
2708
2817
  req: "translucent backgrounds that hold their reading on any paper",
@@ -2710,25 +2819,25 @@ var OVERLAY = {
2710
2819
  collides: true
2711
2820
  };
2712
2821
  var SCALE = {
2713
- "paper-99": PAPER,
2822
+ "paper-1": PAPER,
2714
2823
  "paper-99-overlay": OVERLAY,
2715
- "paper-97": PAPER,
2824
+ "paper-3": PAPER,
2716
2825
  "paper-97-overlay": OVERLAY,
2717
- "paper-95": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
2826
+ "paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
2718
2827
  "paper-95-overlay": OVERLAY,
2719
- "wash-92": WASH,
2720
- "wash-89": WASH,
2721
- "wash-85": WASH,
2722
- "wash-80": WASH,
2828
+ "highlighter-8": HIGHLIGHTER,
2829
+ "highlighter-11": HIGHLIGHTER,
2830
+ "highlighter-15": HIGHLIGHTER,
2831
+ "highlighter-20": HIGHLIGHTER,
2723
2832
  // ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
2724
- // landing on the wax rows is worth its ic-ON-s noise — the one surviving body carrier)
2725
- "wax-74": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
2726
- "lead-53": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2727
- "ink-42": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2833
+ // landing on the crayon rows is worth its ic-ON-s noise — the one surviving body carrier)
2834
+ "crayon-26": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
2835
+ "pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2836
+ "pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2728
2837
  // ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
2729
2838
  // words, and a body carrying either floods that token's picker search — the C50 law;
2730
2839
  // "strong-emphasis" reworded 2026-08-28: str-ON-g fed the "on" flood)
2731
- "ink-30": { req: "heavy-emphasis text, inverted backgrounds", contrast: AAA_BODY, theming: solved, collides: true },
2840
+ "pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
2732
2841
  // the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
2733
2842
  // these bodies on purpose: it stopped being a token label, so it floods nothing,
2734
2843
  // and a designer's "cta" query still lands on these rows.
@@ -2757,7 +2866,7 @@ var LINK_INVERSE = (state, contrast) => ({
2757
2866
  var SYSTEM = {
2758
2867
  "system/abs-black": ABS,
2759
2868
  "system/abs-white": ABS,
2760
- // (the community system/ink-0 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
2869
+ // (the community system/pen-100 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
2761
2870
  // the anchor is engine-resolved and rides the neutral in BOTH plugins now —
2762
2871
  // NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
2763
2872
  "system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
@@ -2773,7 +2882,7 @@ var SYSTEM = {
2773
2882
  "system/alpha/away-from-bg/08": OFFSET,
2774
2883
  "system/alpha/away-from-bg/16": OFFSET,
2775
2884
  // the inverse ladder: the offset rungs with the pole flipped per mode, for
2776
- // washes and edges over inverted backgrounds ("interaction" avoided in the
2885
+ // highlighters and edges over inverted backgrounds ("interaction" avoided in the
2777
2886
  // bodies: acti-ON would feed the "on" flood)
2778
2887
  "system/alpha/toward-bg/06": OFFSET_INVERSE,
2779
2888
  "system/alpha/toward-bg/08": OFFSET_INVERSE,
@@ -2790,10 +2899,10 @@ var SYSTEM = {
2790
2899
  "system/surface/high": PLANE("topmost plane \u2014 modals, dialogs"),
2791
2900
  "system/link/default/enabled": LINK("", AA_BODY),
2792
2901
  "system/link/default/hover": LINK(" pointer-over", AA_BODY),
2793
- "system/link/default/pressed": LINK(" pressed", AAA_BODY),
2902
+ "system/link/default/pressed": LINK(" pressed", AA_BODY),
2794
2903
  "system/link/inverse/enabled": LINK_INVERSE("", AA_BODY),
2795
2904
  "system/link/inverse/hover": LINK_INVERSE(" pointer-over", AA_BODY),
2796
- "system/link/inverse/pressed": LINK_INVERSE(" pressed", AAA_BODY)
2905
+ "system/link/inverse/pressed": LINK_INVERSE(" pressed", AA_BODY)
2797
2906
  };
2798
2907
  var PREFIXES = [
2799
2908
  // community spellings FIRST: the 2026-08-21 family rename made the ext word a
@@ -2840,15 +2949,15 @@ function checkCollision(brand, signalScale, signalDef, mode) {
2840
2949
  collides: hueGate && deltaE <= threshold
2841
2950
  };
2842
2951
  }
2843
- var HUE_COLLISION_WASH_DEG = 15;
2952
+ var HUE_COLLISION_HIGHLIGHTER_DEG = 15;
2844
2953
  var HUE_COLLISION_MIN_V = 0.5;
2845
2954
  var SECONDARY_NOTE_MIN_V = HUE_NOISE_C / VIVID_C;
2846
- var WASH_STOPS = [3, 4, 5, 6, 7];
2847
- function washProximity(brand, sig, mode) {
2955
+ var HIGHLIGHTER_STOPS = [3, 4, 5, 6, 7];
2956
+ function highlighterProximity(brand, sig, mode) {
2848
2957
  const b = mode === "light" ? brand.light : brand.dark;
2849
2958
  const s = mode === "light" ? sig.light : sig.dark;
2850
2959
  let dH = Infinity, dE = Infinity;
2851
- for (const k of WASH_STOPS) {
2960
+ for (const k of HIGHLIGHTER_STOPS) {
2852
2961
  const bs = b.find((x2) => x2.stop === k), ss = s.find((x2) => x2.stop === k);
2853
2962
  if (!bs || !ss) continue;
2854
2963
  dH = Math.min(dH, hueDistance(bs.H, ss.H));
@@ -2857,15 +2966,15 @@ function washProximity(brand, sig, mode) {
2857
2966
  return { dH, dE };
2858
2967
  }
2859
2968
  function checkHueCollision(brand, signalScale, signalDef, opts) {
2860
- const l = washProximity(brand, signalScale, "light");
2861
- const d = washProximity(brand, signalScale, "dark");
2969
+ const l = highlighterProximity(brand, signalScale, "light");
2970
+ const d = highlighterProximity(brand, signalScale, "dark");
2862
2971
  const vividness = Math.min(1, brand.brandC / VIVID_C);
2863
2972
  return {
2864
2973
  signal: signalDef.name,
2865
- dHueWash: { light: l.dH, dark: d.dH },
2866
- washDeltaE: { light: l.dE, dark: d.dE },
2974
+ dHueHighlighter: { light: l.dH, dark: d.dH },
2975
+ highlighterDeltaE: { light: l.dE, dark: d.dE },
2867
2976
  vividness,
2868
- collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_WASH_DEG
2977
+ collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_HIGHLIGHTER_DEG
2869
2978
  };
2870
2979
  }
2871
2980
  var YELLOW_SPLIT_H = 96;
@@ -3058,7 +3167,7 @@ function resolveBrand(hex, name, opts) {
3058
3167
  // (buildSignalScales/signalShift) — neutral alone stays outside; exact has enforce off.
3059
3168
  apcaClearance: opts?.apcaClearance ?? true,
3060
3169
  darkCtaFlatApp: opts?.darkCtaFlatApp,
3061
- inkGround: opts?.inkGround
3170
+ textGround: opts?.textGround
3062
3171
  };
3063
3172
  let scale = generateScale(hex, name, void 0, floor);
3064
3173
  let pending = [];
@@ -3141,7 +3250,7 @@ function resolveLinkTrio(linkHex, contrastProfile) {
3141
3250
  const s = resolveBrand(linkHex, "link", { skipCollisionRules: true, contrastProfile }).scale;
3142
3251
  const at = (arr, n) => {
3143
3252
  const st = arr.find((x2) => x2.stop === n);
3144
- if (!st) throw new Error(`resolveLinkTrio: the link resolve has no ink stop ${n}`);
3253
+ if (!st) throw new Error(`resolveLinkTrio: the link resolve has no pen stop ${n}`);
3145
3254
  return st;
3146
3255
  };
3147
3256
  return {
@@ -3154,16 +3263,16 @@ function resolveLinkTrio(linkHex, contrastProfile) {
3154
3263
  };
3155
3264
  }
3156
3265
  function resolveLinkInverseTrio(seedHex, contrastProfile) {
3157
- const ground = INK_30_GROUND[contrastProfile === "apca" ? "apca" : "wcag"];
3266
+ const ground = PEN_70_GROUND[contrastProfile === "apca" ? "apca" : "wcag"];
3158
3267
  const s = resolveBrand(seedHex, "link-inverse", {
3159
3268
  skipCollisionRules: true,
3160
3269
  contrastProfile,
3161
3270
  // the cross — ramp keyed, ground mode-swapped
3162
- inkGround: { light: ground.dark, dark: ground.light }
3271
+ textGround: { light: ground.dark, dark: ground.light }
3163
3272
  }).scale;
3164
3273
  const at = (arr, n) => {
3165
3274
  const st = arr.find((x2) => x2.stop === n);
3166
- if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no ink stop ${n}`);
3275
+ if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no pen stop ${n}`);
3167
3276
  return st;
3168
3277
  };
3169
3278
  return {
@@ -3177,12 +3286,12 @@ function resolveLinkInverseTrio(seedHex, contrastProfile) {
3177
3286
  };
3178
3287
  }
3179
3288
  function escapeCtaFamily(nScale, mode, contrastProfile) {
3180
- const strongInk = (mode === "light" ? nScale.light : nScale.dark).find((s) => s.stop === 11);
3181
- if (!strongInk) throw new Error("escapeCtaFamily: the neutral scale has no ink-11 stop");
3182
- const mk = (stop, L) => makeStop(stop, L, strongInk.C, strongInk.H);
3183
- const cta = mk(9, strongInk.L);
3184
- const ctaHover = mk(10, stateFillL(strongInk.L, mode, 1));
3185
- const ctaPressed = mk(11, stateFillL(strongInk.L, mode, 2));
3289
+ const strongText = (mode === "light" ? nScale.light : nScale.dark).find((s) => s.stop === 11);
3290
+ if (!strongText) throw new Error("escapeCtaFamily: the neutral scale has no ink-11 stop");
3291
+ const mk = (stop, L) => makeStop(stop, L, strongText.C, strongText.H);
3292
+ const cta = mk(9, strongText.L);
3293
+ const ctaHover = mk(10, stateFillL(strongText.L, mode, 1));
3294
+ const ctaPressed = mk(11, stateFillL(strongText.L, mode, 2));
3186
3295
  const onEnforce = contrastProfile !== "apca";
3187
3296
  const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
3188
3297
  const onFillIsWhite = onTextIsWhite(apcaY(cta.r, cta.g, cta.b), cta.L, cta.C, cta.H, onEnforce, onFloor);
@@ -3258,7 +3367,7 @@ function resolveTheme(input) {
3258
3367
  for (const def of SIGNALS2) {
3259
3368
  if (adoptedForSecondary.has(def.name)) continue;
3260
3369
  const h = checkHueCollision(scale2, effectiveOf(def.name), def, { minV: SECONDARY_NOTE_MIN_V });
3261
- if (h.collides) out.push(wording(def.name, Math.min(h.washDeltaE.light, h.washDeltaE.dark)));
3370
+ if (h.collides) out.push(wording(def.name, Math.min(h.highlighterDeltaE.light, h.highlighterDeltaE.dark)));
3262
3371
  }
3263
3372
  return out;
3264
3373
  };
@@ -3308,7 +3417,7 @@ function resolveTheme(input) {
3308
3417
  derived: true,
3309
3418
  notes: [
3310
3419
  `secondary derived from the brand color (default model, seed ${liftedHex})`,
3311
- ...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (wash \u0394E ${dE.toFixed(3)}) \u2014 it tracks the brand color`)
3420
+ ...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (highlighter \u0394E ${dE.toFixed(3)}) \u2014 it tracks the brand color`)
3312
3421
  ],
3313
3422
  distinctness: ctaDistinctness(primary.scale, scale2)
3314
3423
  },
@@ -3333,7 +3442,7 @@ function resolveTheme(input) {
3333
3442
  derived: false,
3334
3443
  notes: [
3335
3444
  `secondary keeps your color through the ramp; the cta is a tint of it (tint seed ${tintedHex})`,
3336
- ...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (wash \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
3445
+ ...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (highlighter \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
3337
3446
  ],
3338
3447
  distinctness: distinctness2
3339
3448
  },
@@ -3345,7 +3454,7 @@ function resolveTheme(input) {
3345
3454
  const scale = rSec.scale;
3346
3455
  const level = "standard";
3347
3456
  mergeSecondarySignals(scale, input.secondaryHex);
3348
- const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (wash \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
3457
+ const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (highlighter \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
3349
3458
  const distinctness = ctaDistinctness(primary.scale, scale);
3350
3459
  if (distinctness.close)
3351
3460
  notes.push(`secondary reads close to the primary (\u0394E ${Math.min(distinctness.light, distinctness.dark).toFixed(2)}) \u2014 consider a more distinct color`);
@@ -3358,113 +3467,6 @@ function resolveTheme(input) {
3358
3467
  };
3359
3468
  }
3360
3469
 
3361
- // src/engine/tokenNames.ts
3362
- var SHARED_NAMES = {
3363
- 1: "paper-99",
3364
- 2: "paper-97",
3365
- // stop 3 renamed wash-3 → paper-3 (owner 2026-07-24, elevation round): it is a
3366
- // PLANE in both themes (light sink / dark pop), so it belongs to the surface band.
3367
- // NAME ONLY — generation is index-keyed and unchanged; the internal wash machinery
3368
- // (reqtoken groupOf, collision WASH_STOPS) deliberately still spans stops 3–7.
3369
- // Stage B (owner 2026-08-07, names only): relabeled to the ROOT_L-derived digit,
3370
- // paper-95 — same band, same index.
3371
- 3: "paper-95",
3372
- 4: "wash-92",
3373
- 5: "wash-89",
3374
- 6: "wash-85",
3375
- 7: "wash-80",
3376
- 8: "wax-74",
3377
- // THE INK BAND (C49, owner 2026-08-05): the first text stop + emphasis fill (the
3378
- // 2026-07-29 highlight collapse — the old highlight-9 and on-highlight are
3379
- // deleted; this stop carries both jobs, its on-color a paper token), the between
3380
- // text stop (a value the retired text-cta hover state used to generate bespokely,
3381
- // promoted to a normal stop), and the strong text stop. The three read as states
3382
- // ARE the text-style cta (owner 2026-08-12). C49 restored the top
3383
- // two pre-collapse stop INDICES (11, and the off-scale anchor's 12 — emitted as a
3384
- // literal in cssRender, not through this table) with the between stop taking the
3385
- // vacated index 10; the indices (9/10/11) are unchanged since.
3386
- 9: "lead-53",
3387
- 10: "ink-42",
3388
- 11: "ink-30"
3389
- };
3390
- var SCALE_STOP_COUNT = Object.keys(SHARED_NAMES).length;
3391
- function stopTokenName(stop) {
3392
- const name = SHARED_NAMES[stop];
3393
- if (!name) throw new Error(`stopTokenName: unexpected stop ${stop}`);
3394
- return name;
3395
- }
3396
- var PAPER_100 = "paper-100";
3397
- var INK_0 = "ink-0";
3398
- var STAMP_FILL = "stamp-fill";
3399
- var STAMP_FILL_HOVER = "stamp-fill-hover";
3400
- var STAMP_FILL_PRESSED = "stamp-fill-pressed";
3401
- var STAMP_EDGE = "stamp-edge";
3402
- var STAMP_ON = "stamp-on";
3403
- var STAMP_STATE_LEAVES = {
3404
- [STAMP_FILL]: "stamp/fill",
3405
- [STAMP_FILL_HOVER]: "stamp/fill-hover",
3406
- [STAMP_FILL_PRESSED]: "stamp/fill-pressed",
3407
- [STAMP_EDGE]: "stamp/edge",
3408
- [STAMP_ON]: "stamp/on"
3409
- };
3410
- var STAMP_LEAF = {
3411
- FILL: STAMP_STATE_LEAVES[STAMP_FILL],
3412
- FILL_HOVER: STAMP_STATE_LEAVES[STAMP_FILL_HOVER],
3413
- FILL_PRESSED: STAMP_STATE_LEAVES[STAMP_FILL_PRESSED],
3414
- EDGE: STAMP_STATE_LEAVES[STAMP_EDGE],
3415
- ON: STAMP_STATE_LEAVES[STAMP_ON]
3416
- };
3417
- var SYSTEM_LEAF = {
3418
- ABS_BLACK: "system/abs-black",
3419
- ABS_WHITE: "system/abs-white",
3420
- SURFACE: {
3421
- DIM: "system/surface/dim",
3422
- LOW: "system/surface/low",
3423
- MID: "system/surface/mid",
3424
- HIGH: "system/surface/high"
3425
- },
3426
- ALPHA: {
3427
- TRANSPARENT: "system/alpha/transparent",
3428
- SCRIM: "system/alpha/abs-black-060",
3429
- INK: "system/alpha/ink",
3430
- // the DIRECTIONAL ladders (owner 2026-08-31, renaming the 2026-08-18 bare rungs
3431
- // and the 2026-08-29 inverse ladder): away-from-bg is the page-polarity pole
3432
- // (black in light, white in dark), toward-bg the flipped pole — STATE LAYERS for
3433
- // INVERTED grounds, whose polarity is opposite the page's. (Never call toward-bg
3434
- // a "wash": that word belongs to the tinted band stops.) The reversal lives in
3435
- // the engine: a consumer on an inverted ground names one token and never picks a
3436
- // pole. Colored fills never ride these; their states stay engine-solved stops.
3437
- // Rungs are two-digit percent, harmonizing with shadow-04/08/12.
3438
- AWAY_FROM_BG_06: "system/alpha/away-from-bg/06",
3439
- AWAY_FROM_BG_08: "system/alpha/away-from-bg/08",
3440
- AWAY_FROM_BG_16: "system/alpha/away-from-bg/16",
3441
- TOWARD_BG_06: "system/alpha/toward-bg/06",
3442
- TOWARD_BG_08: "system/alpha/toward-bg/08",
3443
- TOWARD_BG_16: "system/alpha/toward-bg/16",
3444
- SHADOW_04: "system/alpha/shadow-04",
3445
- SHADOW_08: "system/alpha/shadow-08",
3446
- SHADOW_12: "system/alpha/shadow-12"
3447
- }
3448
- };
3449
- var SURFACE_PLANE_LAW = {
3450
- [SYSTEM_LEAF.SURFACE.DIM]: { light: SHARED_NAMES[3], dark: PAPER_100 },
3451
- [SYSTEM_LEAF.SURFACE.LOW]: { light: SHARED_NAMES[2], dark: SHARED_NAMES[1] },
3452
- [SYSTEM_LEAF.SURFACE.MID]: { light: SHARED_NAMES[1], dark: SHARED_NAMES[2] },
3453
- [SYSTEM_LEAF.SURFACE.HIGH]: { light: PAPER_100, dark: SHARED_NAMES[3] }
3454
- };
3455
- var TOKEN_ORDER = [
3456
- ...Object.values(SHARED_NAMES),
3457
- STAMP_FILL,
3458
- STAMP_FILL_HOVER,
3459
- STAMP_FILL_PRESSED,
3460
- STAMP_ON,
3461
- "identity"
3462
- ];
3463
- function tokenOrder(name) {
3464
- const i = TOKEN_ORDER.indexOf(name);
3465
- return i === -1 ? TOKEN_ORDER.length : i;
3466
- }
3467
-
3468
3470
  // src/engine/cssRender.ts
3469
3471
  var { brandPrimary: CSS_BRAND, brandSecondary: CSS_SECONDARY, neutral: CSS_NEUTRAL } = CSS_FAMILY;
3470
3472
  function toHex2(r, g2, b) {
@@ -3511,7 +3513,7 @@ var alphaRootVars = (mode) => [
3511
3513
  ...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`),
3512
3514
  // the toward-bg ladder (the 2026-08-29 inverse ladder, renamed 2026-08-31): the
3513
3515
  // same rungs, pole flipped per mode — state layers for INVERTED grounds (never
3514
- // called a "wash" — that word is the tinted band's). One spelling with the Figma
3516
+ // called a "highlighter" — that word is the tinted band's). One spelling with the Figma
3515
3517
  // row: --alpha-toward-bg-06 ↔ system/alpha/toward-bg/06.
3516
3518
  ...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` --alpha-toward-bg-${offsetLeafName(r)}: ${offsetRgba(r, mode === "light" ? "dark" : "light")};`)
3517
3519
  ];
@@ -3538,7 +3540,7 @@ function brandKindBody(prefix, s, mode, page) {
3538
3540
  ` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
3539
3541
  ` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
3540
3542
  // the SOFT on-cta (owner 2026-08-04: "neutral cta on's should also be the alpha"): the
3541
- // neutral's cta is the scale-fed WASH-level fill, the system's other quiet cta, so its
3543
+ // neutral's cta is the scale-fed HIGHLIGHTER-level fill, the system's other quiet cta, so its
3542
3544
  // button text takes the pole AT ALPHA like the default-model secondary's — composited
3543
3545
  // over whatever state the fill is in, so hover/pressed carry their own legibility. Same
3544
3546
  // register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
@@ -3568,16 +3570,16 @@ function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBor
3568
3570
  const p3Dark = brandKindP3Body(CSS_NEUTRAL, s, "dark");
3569
3571
  return [
3570
3572
  `${selector} {`,
3571
- ` --${PAPER_100}: ${p02(s.paper0, "#ffffff")};`,
3572
- ` --${INK_0}: ${p02(s.ink0, "#000000")};`,
3573
+ ` --${PAPER_0}: ${p02(s.paper0, "#ffffff")};`,
3574
+ ` --${PEN_100}: ${p02(s.pen100, "#000000")};`,
3573
3575
  // the neutral IS the page, so it is judged against its own paper stop
3574
3576
  // the neutral's overlays never read the visibility bar (the exempt family), so
3575
3577
  // the brandless chrome context passes 0
3576
3578
  ...brandKindBody(CSS_NEUTRAL, s, "light", nPage("light")),
3577
3579
  `}`,
3578
3580
  `${selector}[data-theme="dark"] {`,
3579
- ` --${PAPER_100}: ${p02(s.paper0Dark, "#000000")};`,
3580
- ` --${INK_0}: ${p02(s.ink0Dark, "#ffffff")};`,
3581
+ ` --${PAPER_0}: ${p02(s.paper0Dark, "#000000")};`,
3582
+ ` --${PEN_100}: ${p02(s.pen100Dark, "#ffffff")};`,
3581
3583
  ...brandKindBody(CSS_NEUTRAL, s, "dark", nPage("dark")),
3582
3584
  `}`,
3583
3585
  ...p3Light.length || p3Dark.length ? [
@@ -3666,8 +3668,8 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
3666
3668
  const brandIdentity = ` --${CSS_BRAND}-identity: ${scale.identityHex};`;
3667
3669
  const secondaryIdentity = secondary ? ` --${CSS_SECONDARY}-identity: ${secondary.identityHex};` : ` --${CSS_SECONDARY}-identity: var(--${CSS_BRAND}-identity);`;
3668
3670
  const p0hex = (s, fallback) => s ? stopHex(s) : fallback;
3669
- const lightAnchors = [` --${PAPER_100}: ${p0hex(nScale.paper0, "#ffffff")};`, ` --${INK_0}: ${p0hex(nScale.ink0, "#000000")};`];
3670
- const darkAnchors = [` --${PAPER_100}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` --${INK_0}: ${p0hex(nScale.ink0Dark, "#ffffff")};`];
3671
+ const lightAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0, "#ffffff")};`, ` --${PEN_100}: ${p0hex(nScale.pen100, "#000000")};`];
3672
+ const darkAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` --${PEN_100}: ${p0hex(nScale.pen100Dark, "#ffffff")};`];
3671
3673
  const linkTrio = linkHex ? resolveLinkTrio(linkHex, contrastProfile) : null;
3672
3674
  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)};`] : [
3673
3675
  ` --link: var(--${CSS_BRAND}-${stopTokenName(9)});`,
@@ -3855,17 +3857,17 @@ function themeToFigma(r, input) {
3855
3857
  const build = (mode) => {
3856
3858
  const p02 = mode === "light" ? nScale.paper0 : nScale.paper0Dark;
3857
3859
  const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, neutralExtra(mode));
3858
- const i02 = mode === "light" ? nScale.ink0 : nScale.ink0Dark;
3859
- const spliceInk0 = (g3) => {
3860
+ const i02 = mode === "light" ? nScale.pen100 : nScale.pen100Dark;
3861
+ const splicePen100 = (g3) => {
3860
3862
  if (!i02) return g3;
3861
3863
  const out = {};
3862
3864
  for (const [k, v2] of Object.entries(g3)) {
3863
3865
  out[k] = v2;
3864
- if (k === stopTokenName(11)) out[INK_0] = colorFromStop(i02);
3866
+ if (k === stopTokenName(11)) out[PEN_100] = colorFromStop(i02);
3865
3867
  }
3866
3868
  return out;
3867
3869
  };
3868
- const neutralGroup = spliceInk0(p02 ? { [PAPER_100]: colorFromStop(p02), ...ramp } : ramp);
3870
+ const neutralGroup = splicePen100(p02 ? { [PAPER_0]: colorFromStop(p02), ...ramp } : ramp);
3869
3871
  const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, brandExtra(secondary, mode, CSS_FAMILY.brandSecondary));
3870
3872
  if (input.secondaryStyle === "outline" && input.secondary) {
3871
3873
  const s8 = secondary[mode].find((s) => s.stop === 8);
@@ -3903,12 +3905,12 @@ function themeToFigma(r, input) {
3903
3905
  putLeaf(g3, STAMP_ON, colorFromHex(esc.onFillIsWhite));
3904
3906
  }
3905
3907
  }
3906
- const scaleInkAt = (n) => {
3908
+ const scaleTextAt = (n) => {
3907
3909
  const s = scale[mode].find((x2) => x2.stop === n);
3908
- if (!s) throw new Error(`themeToFigma link: the brand scale has no ink stop ${n}`);
3910
+ if (!s) throw new Error(`themeToFigma link: the brand scale has no pen stop ${n}`);
3909
3911
  return s;
3910
3912
  };
3911
- 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)) };
3913
+ 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)) };
3912
3914
  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) };
3913
3915
  const g2 = {
3914
3916
  brand: brandGroup,