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.
- package/README.md +4 -4
- package/dist-lib/index.cjs +158 -144
- package/dist-lib/index.mjs +158 -144
- package/dist-lib/types/engine/alphaPapers.d.ts +1 -1
- package/dist-lib/types/engine/collision.d.ts +3 -3
- package/dist-lib/types/engine/colorEngine.d.ts +11 -3
- package/dist-lib/types/engine/requirements/producers.d.ts +1 -1
- package/dist-lib/types/engine/requirements/resolve.d.ts +8 -0
- package/dist-lib/types/engine/requirements/spec.d.ts +6 -6
- package/dist-lib/types/engine/resolve.d.ts +1 -1
- package/dist-lib/types/engine/stopTable.d.ts +6 -6
- package/dist-lib/types/engine/tokenNames.d.ts +2 -2
- package/dist-lib/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/tokens/semantic.css +113 -113
package/dist-lib/index.cjs
CHANGED
|
@@ -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
|
-
|
|
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: {
|
|
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: {
|
|
290
|
-
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: {
|
|
303
|
-
10: {
|
|
304
|
-
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
|
|
322
|
-
var
|
|
323
|
-
var
|
|
324
|
-
var
|
|
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-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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-
|
|
806
|
-
2: "paper-
|
|
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
|
|
810
|
-
// (collision.ts
|
|
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-
|
|
815
|
-
3: "paper-
|
|
816
|
-
4: "
|
|
817
|
-
5: "
|
|
818
|
-
6: "
|
|
819
|
-
7: "
|
|
820
|
-
8: "
|
|
821
|
-
// 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: "
|
|
831
|
-
10: "
|
|
832
|
-
11: "
|
|
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
|
|
841
|
-
var
|
|
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 "
|
|
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:
|
|
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:
|
|
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
|
|
914
|
-
var groupOf = (stop) => (stop === 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-
|
|
922
|
-
var T9 = { metric: "wcag", against: "paper-
|
|
923
|
-
var T10 = { metric: "wcag", against: "
|
|
924
|
-
var T11 = { metric: "wcag", against: "paper-
|
|
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-
|
|
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/
|
|
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-
|
|
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
|
-
//
|
|
943
|
-
// highlight-9 collapse, owner 2026-07-29).
|
|
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].
|
|
946
|
-
{ stop: 10, rootL: ROOT_L_LIGHT[10], group: groupOf(10), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[10].
|
|
947
|
-
{ stop: 11, rootL: ROOT_L_LIGHT[11], group: groupOf(11), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[11].
|
|
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-
|
|
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/
|
|
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
|
-
//
|
|
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].
|
|
978
|
-
{ stop: 10, rootL: ROOT_L_DARK[10], group: groupOf(10), produce: P_TEXT, chromaMult: SCALE_C_DARK[10].
|
|
979
|
-
{ stop: 11, rootL: ROOT_L_DARK[11], group: groupOf(11), produce: P_TEXT, chromaMult: SCALE_C_DARK[11].
|
|
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
|
-
//
|
|
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 === "
|
|
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
|
|
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
|
|
2083
|
-
const anchorL = perceptualRungL(rootL, clampChromaToGamut(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,
|
|
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,
|
|
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,
|
|
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
|
|
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?.
|
|
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-
|
|
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
|
|
2392
|
-
const groundOf = (req, stop) =>
|
|
2393
|
-
const apcaGroundOf = (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
|
|
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 (
|
|
2417
|
-
if (!sp.require) throw new Error(`light
|
|
2418
|
-
placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false),
|
|
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-
|
|
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
|
|
2441
|
-
const chromaAt =
|
|
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 && !
|
|
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 && !(
|
|
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
|
-
|
|
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 &&
|
|
2508
|
-
if (
|
|
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-
|
|
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
|
|
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
|
-
|
|
2706
|
-
|
|
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
|
|
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
|
|
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-
|
|
2837
|
+
"paper-1": PAPER,
|
|
2824
2838
|
"paper-99-overlay": OVERLAY,
|
|
2825
|
-
"paper-
|
|
2839
|
+
"paper-3": PAPER,
|
|
2826
2840
|
"paper-97-overlay": OVERLAY,
|
|
2827
|
-
"paper-
|
|
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
|
-
"
|
|
2830
|
-
"
|
|
2831
|
-
"
|
|
2832
|
-
"
|
|
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
|
|
2835
|
-
"
|
|
2836
|
-
"
|
|
2837
|
-
"
|
|
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
|
-
"
|
|
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/
|
|
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
|
-
//
|
|
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",
|
|
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",
|
|
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
|
|
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
|
|
2957
|
-
function
|
|
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
|
|
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 =
|
|
2971
|
-
const d =
|
|
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
|
-
|
|
2976
|
-
|
|
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) <=
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
3291
|
-
if (!
|
|
3292
|
-
const mk = (stop, L) => makeStop(stop, L,
|
|
3293
|
-
const cta = mk(9,
|
|
3294
|
-
const ctaHover = mk(10, stateFillL(
|
|
3295
|
-
const ctaPressed = mk(11, stateFillL(
|
|
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.
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 "
|
|
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
|
|
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
|
-
` --${
|
|
3575
|
-
` --${
|
|
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
|
-
` --${
|
|
3583
|
-
` --${
|
|
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 = [` --${
|
|
3673
|
-
const darkAnchors = [` --${
|
|
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.
|
|
3862
|
-
const
|
|
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[
|
|
3881
|
+
if (k === stopTokenName(11)) out[PEN_100] = colorFromStop(i02);
|
|
3868
3882
|
}
|
|
3869
3883
|
return out;
|
|
3870
3884
|
};
|
|
3871
|
-
const neutralGroup =
|
|
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
|
|
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
|
|
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(
|
|
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,
|