okchroma 0.1.7 → 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.
- package/README.md +4 -4
- package/dist-lib/index.cjs +139 -140
- package/dist-lib/index.mjs +139 -140
- 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 +3 -3
- package/dist-lib/types/engine/requirements/producers.d.ts +1 -1
- 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);
|
|
@@ -2363,7 +2363,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2363
2363
|
const seed = { hex, ...o };
|
|
2364
2364
|
const ctx = buildContext(hex, opts);
|
|
2365
2365
|
spec ?? (spec = MODE_SPECS[mode]);
|
|
2366
|
-
if (ctx.opts?.
|
|
2366
|
+
if (ctx.opts?.textGround) {
|
|
2367
2367
|
spec = {
|
|
2368
2368
|
...spec,
|
|
2369
2369
|
stops: spec.stops.map((s) => s.stop === 10 && s.require?.metric === "wcag" ? { ...s, require: { ...s.require, target: 6.5 } } : s)
|
|
@@ -2385,12 +2385,12 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2385
2385
|
if (!ref) throw new Error(`stop ${forWhom}: require against stop ${stopNum} but it is not resolved yet`);
|
|
2386
2386
|
return ref;
|
|
2387
2387
|
};
|
|
2388
|
-
const AGAINST_STOP = { "paper-
|
|
2388
|
+
const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "highlighter-20": 7 };
|
|
2389
2389
|
const declaredAnchor = (req) => req.metric === "min-separation" ? 1 : AGAINST_STOP[req.against] ?? 2;
|
|
2390
2390
|
const wcagAnchorStop = (req, stop) => stop >= 9 && declaredAnchor(req) <= 3 ? 3 : declaredAnchor(req);
|
|
2391
|
-
const
|
|
2392
|
-
const groundOf = (req, stop) =>
|
|
2393
|
-
const apcaGroundOf = (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);
|
|
2394
2394
|
const NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
|
|
2395
2395
|
const NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
|
|
2396
2396
|
const maxLForOf = (req, forWhom, withMargin) => {
|
|
@@ -2408,14 +2408,14 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2408
2408
|
}
|
|
2409
2409
|
throw new Error(`stop ${forWhom}: ${req.metric} is not a contrast require`);
|
|
2410
2410
|
};
|
|
2411
|
-
const
|
|
2411
|
+
const TEXT_DEEPEN = 0;
|
|
2412
2412
|
for (const sp of spec.stops) {
|
|
2413
2413
|
let placed;
|
|
2414
2414
|
let clamped = false, unresolvable;
|
|
2415
2415
|
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),
|
|
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);
|
|
2419
2419
|
clamped = true;
|
|
2420
2420
|
} else if (sp.produce.L === "fixed") {
|
|
2421
2421
|
const chromaAt = lightScaleChromaAt(ctx, sp.baseC ?? 0, sp.satFraction ?? 1);
|
|
@@ -2427,7 +2427,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2427
2427
|
placed = placeLightScale(ctx, sp.rootL, chromaAt, contrastReq ? maxLForOf(contrastReq, sp.stop, true) : void 0, targetChromaAt);
|
|
2428
2428
|
clamped = !!contrastReq;
|
|
2429
2429
|
if (sp.require?.metric === "min-separation") {
|
|
2430
|
-
const refStop = sp.require.against === "paper-
|
|
2430
|
+
const refStop = sp.require.against === "paper-1" ? 1 : sp.stop - 1;
|
|
2431
2431
|
const ref = stops.find((s) => s.stop === refStop);
|
|
2432
2432
|
if (!ref) throw new Error(`stop ${sp.stop}: min-separation against stop ${refStop} but it is not resolved yet`);
|
|
2433
2433
|
const before = placed.L;
|
|
@@ -2437,8 +2437,8 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2437
2437
|
}
|
|
2438
2438
|
} else {
|
|
2439
2439
|
const d = dctx;
|
|
2440
|
-
const
|
|
2441
|
-
const chromaAt =
|
|
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);
|
|
2442
2442
|
let spineH = null;
|
|
2443
2443
|
const dl = ctx.opts?.deltaLightStops;
|
|
2444
2444
|
const ls = dl && sp.stop >= 1 && sp.stop <= 8 ? dl.find((s) => s.stop === sp.stop) : void 0;
|
|
@@ -2458,7 +2458,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2458
2458
|
}
|
|
2459
2459
|
if (ctx.opts.deltaHKPlace) L = perceptualRungL(sp.rootL, ls.C, ls.H);
|
|
2460
2460
|
if (ctx.opts.deltaLiftFloor) L = Math.max(L, sp.rootL);
|
|
2461
|
-
if (ctx.opts.deltaChromaEq && !
|
|
2461
|
+
if (ctx.opts.deltaChromaEq && !textLane(sp.group)) C2 = ctx.cAt("dark", L, perceptualDarkC(L, ls.H, ctx.brandC));
|
|
2462
2462
|
placed = { L, C: C2, H: spineH ?? ls.H };
|
|
2463
2463
|
} else if (sp.produce.L === "fixed") {
|
|
2464
2464
|
placed = ls ? placeDarkDelta(d, sp.rootL, chromaAt, ls) : { L: sp.rootL, C: chromaAt(sp.rootL), H: d.darkHueAtL(sp.rootL) };
|
|
@@ -2494,7 +2494,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2494
2494
|
if (sp.require?.metric === "wcag") {
|
|
2495
2495
|
const anchor = groundOf(sp.require, sp.stop);
|
|
2496
2496
|
const anchorShipY = shippedY(anchor.L, anchor.C, anchor.H);
|
|
2497
|
-
const paperBound = sp.stop >= 8 && !(
|
|
2497
|
+
const paperBound = sp.stop >= 8 && !(textGround && sp.stop >= 9);
|
|
2498
2498
|
const crossBoundY = wcagAnchorStop(sp.require, sp.stop) === 7 ? NEUTRAL_W80_WORST_SHIP_Y[mode] : NEUTRAL_P3_WORST_SHIP_Y[mode];
|
|
2499
2499
|
const shipRatio = (L) => {
|
|
2500
2500
|
const y2 = shippedY(L, placed.C, placed.H);
|
|
@@ -2522,7 +2522,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2522
2522
|
const got = Math.abs(apcaLc(apcaYAt(placed.L, clampChromaToGamut(placed.L, placed.C, placed.H), placed.H), refA));
|
|
2523
2523
|
if (got < sp.require.targetLc - APCA_TOL_LC) unresolvable = `stop ${sp.stop}: |Lc| ${got.toFixed(1)} < required ${sp.require.targetLc}`;
|
|
2524
2524
|
} else if (sp.require?.metric === "min-separation") {
|
|
2525
|
-
const ref = stops.find((s) => s.stop === (sp.require.against === "paper-
|
|
2525
|
+
const ref = stops.find((s) => s.stop === (sp.require.against === "paper-1" ? 1 : sp.stop - 1));
|
|
2526
2526
|
const rad2 = (h) => h * Math.PI / 180;
|
|
2527
2527
|
const gC = clampChromaToGamut(placed.L, placed.C, placed.H);
|
|
2528
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);
|
|
@@ -2678,7 +2678,7 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
|
|
|
2678
2678
|
const dark = darkRamp.stops.filter((s) => s.stop >= 1).map(toStop).sort((a, b) => a.stop - b.stop);
|
|
2679
2679
|
const p0Light = lightRamp.stops.find((s) => s.stop === 0);
|
|
2680
2680
|
const p0Dark = darkRamp.stops.find((s) => s.stop === 0);
|
|
2681
|
-
const
|
|
2681
|
+
const penPole = (arr, L) => arr.find((s) => s.stop === 11) ? makeStop(12, L, 0, arr.find((s) => s.stop === 11).H) : void 0;
|
|
2682
2682
|
const cta = makeStop(9, lightRamp.roles.cta.L, lightRamp.roles.cta.C, lightRamp.roles.cta.H);
|
|
2683
2683
|
const ctaDark = makeStop(9, darkRamp.roles.cta.L, darkRamp.roles.cta.C, darkRamp.roles.cta.H);
|
|
2684
2684
|
const roleStop = (r, stop) => makeStop(stop, r.L, r.C, r.H);
|
|
@@ -2702,8 +2702,8 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
|
|
|
2702
2702
|
identityHex: hex.toUpperCase(),
|
|
2703
2703
|
paper0: p0Light ? makeStop(0, p0Light.L, p0Light.C, p0Light.H) : void 0,
|
|
2704
2704
|
paper0Dark: p0Dark ? makeStop(0, p0Dark.L, p0Dark.C, p0Dark.H) : void 0,
|
|
2705
|
-
|
|
2706
|
-
|
|
2705
|
+
pen100: penPole(light, 0),
|
|
2706
|
+
pen100Dark: penPole(dark, 1)
|
|
2707
2707
|
};
|
|
2708
2708
|
}
|
|
2709
2709
|
function generateNeutralScale(brandH, level = "default", contrastProfile) {
|
|
@@ -2775,8 +2775,7 @@ function generateSubtleSecondary(hex, opts) {
|
|
|
2775
2775
|
|
|
2776
2776
|
// src/engine/tokenDescriptions.ts
|
|
2777
2777
|
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";
|
|
2778
|
+
var AA_BODY = "AA standard body text";
|
|
2780
2779
|
var FAMILY = {
|
|
2781
2780
|
neutral: "neutral",
|
|
2782
2781
|
brandPrimary: "brand",
|
|
@@ -2812,7 +2811,7 @@ var COLOR_WORD = {
|
|
|
2812
2811
|
[FAMILY.info]: " (blue)"
|
|
2813
2812
|
};
|
|
2814
2813
|
var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
|
|
2815
|
-
var
|
|
2814
|
+
var HIGHLIGHTER = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
|
|
2816
2815
|
var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
|
|
2817
2816
|
var OVERLAY = {
|
|
2818
2817
|
req: "translucent backgrounds that hold their reading on any paper",
|
|
@@ -2820,25 +2819,25 @@ var OVERLAY = {
|
|
|
2820
2819
|
collides: true
|
|
2821
2820
|
};
|
|
2822
2821
|
var SCALE = {
|
|
2823
|
-
"paper-
|
|
2822
|
+
"paper-1": PAPER,
|
|
2824
2823
|
"paper-99-overlay": OVERLAY,
|
|
2825
|
-
"paper-
|
|
2824
|
+
"paper-3": PAPER,
|
|
2826
2825
|
"paper-97-overlay": OVERLAY,
|
|
2827
|
-
"paper-
|
|
2826
|
+
"paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
|
|
2828
2827
|
"paper-95-overlay": OVERLAY,
|
|
2829
|
-
"
|
|
2830
|
-
"
|
|
2831
|
-
"
|
|
2832
|
-
"
|
|
2828
|
+
"highlighter-8": HIGHLIGHTER,
|
|
2829
|
+
"highlighter-11": HIGHLIGHTER,
|
|
2830
|
+
"highlighter-15": HIGHLIGHTER,
|
|
2831
|
+
"highlighter-20": HIGHLIGHTER,
|
|
2833
2832
|
// ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
|
|
2834
|
-
// landing on the
|
|
2835
|
-
"
|
|
2836
|
-
"
|
|
2837
|
-
"
|
|
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 },
|
|
2838
2837
|
// ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
|
|
2839
2838
|
// words, and a body carrying either floods that token's picker search — the C50 law;
|
|
2840
2839
|
// "strong-emphasis" reworded 2026-08-28: str-ON-g fed the "on" flood)
|
|
2841
|
-
"
|
|
2840
|
+
"pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2842
2841
|
// the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
|
|
2843
2842
|
// these bodies on purpose: it stopped being a token label, so it floods nothing,
|
|
2844
2843
|
// and a designer's "cta" query still lands on these rows.
|
|
@@ -2867,7 +2866,7 @@ var LINK_INVERSE = (state, contrast) => ({
|
|
|
2867
2866
|
var SYSTEM = {
|
|
2868
2867
|
"system/abs-black": ABS,
|
|
2869
2868
|
"system/abs-white": ABS,
|
|
2870
|
-
// (the community system/
|
|
2869
|
+
// (the community system/pen-100 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
|
|
2871
2870
|
// the anchor is engine-resolved and rides the neutral in BOTH plugins now —
|
|
2872
2871
|
// NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
|
|
2873
2872
|
"system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
|
|
@@ -2883,7 +2882,7 @@ var SYSTEM = {
|
|
|
2883
2882
|
"system/alpha/away-from-bg/08": OFFSET,
|
|
2884
2883
|
"system/alpha/away-from-bg/16": OFFSET,
|
|
2885
2884
|
// the inverse ladder: the offset rungs with the pole flipped per mode, for
|
|
2886
|
-
//
|
|
2885
|
+
// highlighters and edges over inverted backgrounds ("interaction" avoided in the
|
|
2887
2886
|
// bodies: acti-ON would feed the "on" flood)
|
|
2888
2887
|
"system/alpha/toward-bg/06": OFFSET_INVERSE,
|
|
2889
2888
|
"system/alpha/toward-bg/08": OFFSET_INVERSE,
|
|
@@ -2900,10 +2899,10 @@ var SYSTEM = {
|
|
|
2900
2899
|
"system/surface/high": PLANE("topmost plane \u2014 modals, dialogs"),
|
|
2901
2900
|
"system/link/default/enabled": LINK("", AA_BODY),
|
|
2902
2901
|
"system/link/default/hover": LINK(" pointer-over", AA_BODY),
|
|
2903
|
-
"system/link/default/pressed": LINK(" pressed",
|
|
2902
|
+
"system/link/default/pressed": LINK(" pressed", AA_BODY),
|
|
2904
2903
|
"system/link/inverse/enabled": LINK_INVERSE("", AA_BODY),
|
|
2905
2904
|
"system/link/inverse/hover": LINK_INVERSE(" pointer-over", AA_BODY),
|
|
2906
|
-
"system/link/inverse/pressed": LINK_INVERSE(" pressed",
|
|
2905
|
+
"system/link/inverse/pressed": LINK_INVERSE(" pressed", AA_BODY)
|
|
2907
2906
|
};
|
|
2908
2907
|
var PREFIXES = [
|
|
2909
2908
|
// community spellings FIRST: the 2026-08-21 family rename made the ext word a
|
|
@@ -2950,15 +2949,15 @@ function checkCollision(brand, signalScale, signalDef, mode) {
|
|
|
2950
2949
|
collides: hueGate && deltaE <= threshold
|
|
2951
2950
|
};
|
|
2952
2951
|
}
|
|
2953
|
-
var
|
|
2952
|
+
var HUE_COLLISION_HIGHLIGHTER_DEG = 15;
|
|
2954
2953
|
var HUE_COLLISION_MIN_V = 0.5;
|
|
2955
2954
|
var SECONDARY_NOTE_MIN_V = HUE_NOISE_C / VIVID_C;
|
|
2956
|
-
var
|
|
2957
|
-
function
|
|
2955
|
+
var HIGHLIGHTER_STOPS = [3, 4, 5, 6, 7];
|
|
2956
|
+
function highlighterProximity(brand, sig, mode) {
|
|
2958
2957
|
const b = mode === "light" ? brand.light : brand.dark;
|
|
2959
2958
|
const s = mode === "light" ? sig.light : sig.dark;
|
|
2960
2959
|
let dH = Infinity, dE = Infinity;
|
|
2961
|
-
for (const k of
|
|
2960
|
+
for (const k of HIGHLIGHTER_STOPS) {
|
|
2962
2961
|
const bs = b.find((x2) => x2.stop === k), ss = s.find((x2) => x2.stop === k);
|
|
2963
2962
|
if (!bs || !ss) continue;
|
|
2964
2963
|
dH = Math.min(dH, hueDistance(bs.H, ss.H));
|
|
@@ -2967,15 +2966,15 @@ function washProximity(brand, sig, mode) {
|
|
|
2967
2966
|
return { dH, dE };
|
|
2968
2967
|
}
|
|
2969
2968
|
function checkHueCollision(brand, signalScale, signalDef, opts) {
|
|
2970
|
-
const l =
|
|
2971
|
-
const d =
|
|
2969
|
+
const l = highlighterProximity(brand, signalScale, "light");
|
|
2970
|
+
const d = highlighterProximity(brand, signalScale, "dark");
|
|
2972
2971
|
const vividness = Math.min(1, brand.brandC / VIVID_C);
|
|
2973
2972
|
return {
|
|
2974
2973
|
signal: signalDef.name,
|
|
2975
|
-
|
|
2976
|
-
|
|
2974
|
+
dHueHighlighter: { light: l.dH, dark: d.dH },
|
|
2975
|
+
highlighterDeltaE: { light: l.dE, dark: d.dE },
|
|
2977
2976
|
vividness,
|
|
2978
|
-
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <=
|
|
2977
|
+
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_HIGHLIGHTER_DEG
|
|
2979
2978
|
};
|
|
2980
2979
|
}
|
|
2981
2980
|
var YELLOW_SPLIT_H = 96;
|
|
@@ -3168,7 +3167,7 @@ function resolveBrand(hex, name, opts) {
|
|
|
3168
3167
|
// (buildSignalScales/signalShift) — neutral alone stays outside; exact has enforce off.
|
|
3169
3168
|
apcaClearance: opts?.apcaClearance ?? true,
|
|
3170
3169
|
darkCtaFlatApp: opts?.darkCtaFlatApp,
|
|
3171
|
-
|
|
3170
|
+
textGround: opts?.textGround
|
|
3172
3171
|
};
|
|
3173
3172
|
let scale = generateScale(hex, name, void 0, floor);
|
|
3174
3173
|
let pending = [];
|
|
@@ -3251,7 +3250,7 @@ function resolveLinkTrio(linkHex, contrastProfile) {
|
|
|
3251
3250
|
const s = resolveBrand(linkHex, "link", { skipCollisionRules: true, contrastProfile }).scale;
|
|
3252
3251
|
const at = (arr, n) => {
|
|
3253
3252
|
const st = arr.find((x2) => x2.stop === n);
|
|
3254
|
-
if (!st) throw new Error(`resolveLinkTrio: the link resolve has no
|
|
3253
|
+
if (!st) throw new Error(`resolveLinkTrio: the link resolve has no pen stop ${n}`);
|
|
3255
3254
|
return st;
|
|
3256
3255
|
};
|
|
3257
3256
|
return {
|
|
@@ -3264,16 +3263,16 @@ function resolveLinkTrio(linkHex, contrastProfile) {
|
|
|
3264
3263
|
};
|
|
3265
3264
|
}
|
|
3266
3265
|
function resolveLinkInverseTrio(seedHex, contrastProfile) {
|
|
3267
|
-
const ground =
|
|
3266
|
+
const ground = PEN_70_GROUND[contrastProfile === "apca" ? "apca" : "wcag"];
|
|
3268
3267
|
const s = resolveBrand(seedHex, "link-inverse", {
|
|
3269
3268
|
skipCollisionRules: true,
|
|
3270
3269
|
contrastProfile,
|
|
3271
3270
|
// the cross — ramp keyed, ground mode-swapped
|
|
3272
|
-
|
|
3271
|
+
textGround: { light: ground.dark, dark: ground.light }
|
|
3273
3272
|
}).scale;
|
|
3274
3273
|
const at = (arr, n) => {
|
|
3275
3274
|
const st = arr.find((x2) => x2.stop === n);
|
|
3276
|
-
if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no
|
|
3275
|
+
if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no pen stop ${n}`);
|
|
3277
3276
|
return st;
|
|
3278
3277
|
};
|
|
3279
3278
|
return {
|
|
@@ -3287,12 +3286,12 @@ function resolveLinkInverseTrio(seedHex, contrastProfile) {
|
|
|
3287
3286
|
};
|
|
3288
3287
|
}
|
|
3289
3288
|
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(
|
|
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));
|
|
3296
3295
|
const onEnforce = contrastProfile !== "apca";
|
|
3297
3296
|
const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
|
|
3298
3297
|
const onFillIsWhite = onTextIsWhite(apcaY(cta.r, cta.g, cta.b), cta.L, cta.C, cta.H, onEnforce, onFloor);
|
|
@@ -3368,7 +3367,7 @@ function resolveTheme(input) {
|
|
|
3368
3367
|
for (const def of SIGNALS2) {
|
|
3369
3368
|
if (adoptedForSecondary.has(def.name)) continue;
|
|
3370
3369
|
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.
|
|
3370
|
+
if (h.collides) out.push(wording(def.name, Math.min(h.highlighterDeltaE.light, h.highlighterDeltaE.dark)));
|
|
3372
3371
|
}
|
|
3373
3372
|
return out;
|
|
3374
3373
|
};
|
|
@@ -3418,7 +3417,7 @@ function resolveTheme(input) {
|
|
|
3418
3417
|
derived: true,
|
|
3419
3418
|
notes: [
|
|
3420
3419
|
`secondary derived from the brand color (default model, seed ${liftedHex})`,
|
|
3421
|
-
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (
|
|
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`)
|
|
3422
3421
|
],
|
|
3423
3422
|
distinctness: ctaDistinctness(primary.scale, scale2)
|
|
3424
3423
|
},
|
|
@@ -3443,7 +3442,7 @@ function resolveTheme(input) {
|
|
|
3443
3442
|
derived: false,
|
|
3444
3443
|
notes: [
|
|
3445
3444
|
`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 (
|
|
3445
|
+
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (highlighter \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
|
|
3447
3446
|
],
|
|
3448
3447
|
distinctness: distinctness2
|
|
3449
3448
|
},
|
|
@@ -3455,7 +3454,7 @@ function resolveTheme(input) {
|
|
|
3455
3454
|
const scale = rSec.scale;
|
|
3456
3455
|
const level = "standard";
|
|
3457
3456
|
mergeSecondarySignals(scale, input.secondaryHex);
|
|
3458
|
-
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (
|
|
3457
|
+
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (highlighter \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
|
|
3459
3458
|
const distinctness = ctaDistinctness(primary.scale, scale);
|
|
3460
3459
|
if (distinctness.close)
|
|
3461
3460
|
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 +3513,7 @@ var alphaRootVars = (mode) => [
|
|
|
3514
3513
|
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`),
|
|
3515
3514
|
// the toward-bg ladder (the 2026-08-29 inverse ladder, renamed 2026-08-31): the
|
|
3516
3515
|
// same rungs, pole flipped per mode — state layers for INVERTED grounds (never
|
|
3517
|
-
// called a "
|
|
3516
|
+
// called a "highlighter" — that word is the tinted band's). One spelling with the Figma
|
|
3518
3517
|
// row: --alpha-toward-bg-06 ↔ system/alpha/toward-bg/06.
|
|
3519
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")};`)
|
|
3520
3519
|
];
|
|
@@ -3541,7 +3540,7 @@ function brandKindBody(prefix, s, mode, page) {
|
|
|
3541
3540
|
` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
|
|
3542
3541
|
` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
|
|
3543
3542
|
// 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
|
|
3543
|
+
// neutral's cta is the scale-fed HIGHLIGHTER-level fill, the system's other quiet cta, so its
|
|
3545
3544
|
// button text takes the pole AT ALPHA like the default-model secondary's — composited
|
|
3546
3545
|
// over whatever state the fill is in, so hover/pressed carry their own legibility. Same
|
|
3547
3546
|
// register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
|
|
@@ -3571,16 +3570,16 @@ function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBor
|
|
|
3571
3570
|
const p3Dark = brandKindP3Body(CSS_NEUTRAL, s, "dark");
|
|
3572
3571
|
return [
|
|
3573
3572
|
`${selector} {`,
|
|
3574
|
-
` --${
|
|
3575
|
-
` --${
|
|
3573
|
+
` --${PAPER_0}: ${p02(s.paper0, "#ffffff")};`,
|
|
3574
|
+
` --${PEN_100}: ${p02(s.pen100, "#000000")};`,
|
|
3576
3575
|
// the neutral IS the page, so it is judged against its own paper stop
|
|
3577
3576
|
// the neutral's overlays never read the visibility bar (the exempt family), so
|
|
3578
3577
|
// the brandless chrome context passes 0
|
|
3579
3578
|
...brandKindBody(CSS_NEUTRAL, s, "light", nPage("light")),
|
|
3580
3579
|
`}`,
|
|
3581
3580
|
`${selector}[data-theme="dark"] {`,
|
|
3582
|
-
` --${
|
|
3583
|
-
` --${
|
|
3581
|
+
` --${PAPER_0}: ${p02(s.paper0Dark, "#000000")};`,
|
|
3582
|
+
` --${PEN_100}: ${p02(s.pen100Dark, "#ffffff")};`,
|
|
3584
3583
|
...brandKindBody(CSS_NEUTRAL, s, "dark", nPage("dark")),
|
|
3585
3584
|
`}`,
|
|
3586
3585
|
...p3Light.length || p3Dark.length ? [
|
|
@@ -3669,8 +3668,8 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
|
|
|
3669
3668
|
const brandIdentity = ` --${CSS_BRAND}-identity: ${scale.identityHex};`;
|
|
3670
3669
|
const secondaryIdentity = secondary ? ` --${CSS_SECONDARY}-identity: ${secondary.identityHex};` : ` --${CSS_SECONDARY}-identity: var(--${CSS_BRAND}-identity);`;
|
|
3671
3670
|
const p0hex = (s, fallback) => s ? stopHex(s) : fallback;
|
|
3672
|
-
const lightAnchors = [` --${
|
|
3673
|
-
const darkAnchors = [` --${
|
|
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")};`];
|
|
3674
3673
|
const linkTrio = linkHex ? resolveLinkTrio(linkHex, contrastProfile) : null;
|
|
3675
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)};`] : [
|
|
3676
3675
|
` --link: var(--${CSS_BRAND}-${stopTokenName(9)});`,
|
|
@@ -3858,17 +3857,17 @@ function themeToFigma(r, input) {
|
|
|
3858
3857
|
const build = (mode) => {
|
|
3859
3858
|
const p02 = mode === "light" ? nScale.paper0 : nScale.paper0Dark;
|
|
3860
3859
|
const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, neutralExtra(mode));
|
|
3861
|
-
const i02 = mode === "light" ? nScale.
|
|
3862
|
-
const
|
|
3860
|
+
const i02 = mode === "light" ? nScale.pen100 : nScale.pen100Dark;
|
|
3861
|
+
const splicePen100 = (g3) => {
|
|
3863
3862
|
if (!i02) return g3;
|
|
3864
3863
|
const out = {};
|
|
3865
3864
|
for (const [k, v2] of Object.entries(g3)) {
|
|
3866
3865
|
out[k] = v2;
|
|
3867
|
-
if (k === stopTokenName(11)) out[
|
|
3866
|
+
if (k === stopTokenName(11)) out[PEN_100] = colorFromStop(i02);
|
|
3868
3867
|
}
|
|
3869
3868
|
return out;
|
|
3870
3869
|
};
|
|
3871
|
-
const neutralGroup =
|
|
3870
|
+
const neutralGroup = splicePen100(p02 ? { [PAPER_0]: colorFromStop(p02), ...ramp } : ramp);
|
|
3872
3871
|
const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, brandExtra(secondary, mode, CSS_FAMILY.brandSecondary));
|
|
3873
3872
|
if (input.secondaryStyle === "outline" && input.secondary) {
|
|
3874
3873
|
const s8 = secondary[mode].find((s) => s.stop === 8);
|
|
@@ -3906,12 +3905,12 @@ function themeToFigma(r, input) {
|
|
|
3906
3905
|
putLeaf(g3, STAMP_ON, colorFromHex(esc.onFillIsWhite));
|
|
3907
3906
|
}
|
|
3908
3907
|
}
|
|
3909
|
-
const
|
|
3908
|
+
const scaleTextAt = (n) => {
|
|
3910
3909
|
const s = scale[mode].find((x2) => x2.stop === n);
|
|
3911
|
-
if (!s) throw new Error(`themeToFigma link: the brand scale has no
|
|
3910
|
+
if (!s) throw new Error(`themeToFigma link: the brand scale has no pen stop ${n}`);
|
|
3912
3911
|
return s;
|
|
3913
3912
|
};
|
|
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(
|
|
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)) };
|
|
3915
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) };
|
|
3916
3915
|
const g2 = {
|
|
3917
3916
|
brand: brandGroup,
|