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.mjs
CHANGED
|
@@ -203,12 +203,12 @@ var SCALE_C_LIGHT = {
|
|
|
203
203
|
6: { base: 0.068, sat: 0.85 },
|
|
204
204
|
7: { base: 0.086, sat: 0.78 },
|
|
205
205
|
8: { base: 0.142, sat: 0.78 },
|
|
206
|
-
9: {
|
|
206
|
+
9: { textMult: 0.95, textMaxC: 0.15, chromaFloor: chromaFloorBase(10) },
|
|
207
207
|
// ink-10 keeps the FIRST-text register (ink-9's row, C49): the retired hover law
|
|
208
208
|
// evaluated ink-9's chroma register at the between L, so the same params reproduce it;
|
|
209
209
|
// the damped strong register below belongs to the top text stop alone.
|
|
210
|
-
10: {
|
|
211
|
-
11: {
|
|
210
|
+
10: { textMult: 0.95, textMaxC: 0.15, chromaFloor: chromaFloorBase(10) },
|
|
211
|
+
11: { textMult: 0.5, textMaxC: 0.08, chromaFloor: chromaFloorBase(11) }
|
|
212
212
|
};
|
|
213
213
|
var SCALE_C_DARK = {
|
|
214
214
|
0: { sat: 0.4 },
|
|
@@ -220,9 +220,9 @@ var SCALE_C_DARK = {
|
|
|
220
220
|
6: { sat: 0.76 },
|
|
221
221
|
7: { sat: 0.8 },
|
|
222
222
|
8: { sat: 0.84 },
|
|
223
|
-
9: {
|
|
224
|
-
10: {
|
|
225
|
-
11: {
|
|
223
|
+
9: { textMult: 0.95, textMaxC: 0.12, chromaFloor: chromaFloorBase(10) },
|
|
224
|
+
10: { textMult: 0.95, textMaxC: 0.12, chromaFloor: chromaFloorBase(10) },
|
|
225
|
+
11: { textMult: 0.62, textMaxC: 0.045, chromaFloor: chromaFloorBase(11) }
|
|
226
226
|
};
|
|
227
227
|
var DARK_CTA_C = {
|
|
228
228
|
brand: {
|
|
@@ -239,10 +239,10 @@ var DARK_CTA_C = {
|
|
|
239
239
|
};
|
|
240
240
|
var STOP_8_NONTEXT_CONTRAST = 3;
|
|
241
241
|
var DARK_BAND_TOP_LIFT = 1.19;
|
|
242
|
-
var
|
|
243
|
-
var
|
|
244
|
-
var
|
|
245
|
-
var
|
|
242
|
+
var PENCIL_9_CONTRAST = 4.5;
|
|
243
|
+
var PEN_10_CONTRAST = 4.5;
|
|
244
|
+
var PEN_11_CONTRAST_FLOOR = 7;
|
|
245
|
+
var PEN_70_GROUND = {
|
|
246
246
|
wcag: {
|
|
247
247
|
light: { L: 0.3334966864733433, C: 0.07399966383476321, H: 104.26776675496032 },
|
|
248
248
|
dark: { L: 0.907688606079988, C: 0.045, H: 293.66431934911327 }
|
|
@@ -318,7 +318,7 @@ var NEUTRAL_TINT_POINTS = {
|
|
|
318
318
|
[0.95, 0.895],
|
|
319
319
|
// paper-3 ─┘
|
|
320
320
|
[0.924, 1],
|
|
321
|
-
// wash-4 ─┐ peak: the mid-
|
|
321
|
+
// wash-4 ─┐ peak: the mid-highlighter band, the neutral's most-branded moment
|
|
322
322
|
[0.892, 1],
|
|
323
323
|
// wash-5 ─┘
|
|
324
324
|
[0.852, 0.842],
|
|
@@ -326,7 +326,7 @@ var NEUTRAL_TINT_POINTS = {
|
|
|
326
326
|
[0.801, 0.653],
|
|
327
327
|
// wash-7 │
|
|
328
328
|
[0.738, 0.505],
|
|
329
|
-
//
|
|
329
|
+
// crayon-26
|
|
330
330
|
[0.6, 0.4],
|
|
331
331
|
// knee (ex-scaffold) the drop-off: hue fades as you climb
|
|
332
332
|
[0.56, 0.347],
|
|
@@ -352,7 +352,7 @@ var NEUTRAL_TINT_POINTS = {
|
|
|
352
352
|
[0.42, 0.653],
|
|
353
353
|
// wash-7
|
|
354
354
|
[0.55, 0.505],
|
|
355
|
-
//
|
|
355
|
+
// crayon-26
|
|
356
356
|
[0.6, 0.4],
|
|
357
357
|
// knee (ex-scaffold)
|
|
358
358
|
[0.64, 0.347],
|
|
@@ -723,23 +723,23 @@ function perceptualDarkC(L, H2, nativeC, keep = KEEP_DARK, gamut = MASTER_GAMUT)
|
|
|
723
723
|
|
|
724
724
|
// src/engine/tokenNames.ts
|
|
725
725
|
var SHARED_NAMES = {
|
|
726
|
-
1: "paper-
|
|
727
|
-
2: "paper-
|
|
726
|
+
1: "paper-1",
|
|
727
|
+
2: "paper-3",
|
|
728
728
|
// stop 3 renamed wash-3 → paper-3 (owner 2026-07-24, elevation round): it is a
|
|
729
729
|
// PLANE in both themes (light sink / dark pop), so it belongs to the surface band.
|
|
730
|
-
// NAME ONLY — generation is index-keyed and unchanged; the
|
|
731
|
-
// (collision.ts
|
|
730
|
+
// NAME ONLY — generation is index-keyed and unchanged; the highlighter-collision machinery
|
|
731
|
+
// (collision.ts HIGHLIGHTER_STOPS) deliberately still spans stops 3–7. (The requirement
|
|
732
732
|
// spec's `group` label followed the band to 'paper' on 2026-08-31 — labels only,
|
|
733
733
|
// the spans never moved; see spec.ts groupOf.)
|
|
734
734
|
// Stage B (owner 2026-08-07, names only): relabeled to the ROOT_L-derived digit,
|
|
735
|
-
// paper-
|
|
736
|
-
3: "paper-
|
|
737
|
-
4: "
|
|
738
|
-
5: "
|
|
739
|
-
6: "
|
|
740
|
-
7: "
|
|
741
|
-
8: "
|
|
742
|
-
// THE
|
|
735
|
+
// paper-5 — same band, same index.
|
|
736
|
+
3: "paper-5",
|
|
737
|
+
4: "highlighter-8",
|
|
738
|
+
5: "highlighter-11",
|
|
739
|
+
6: "highlighter-15",
|
|
740
|
+
7: "highlighter-20",
|
|
741
|
+
8: "crayon-26",
|
|
742
|
+
// THE PEN BAND (C49, owner 2026-08-05): the first text stop + emphasis fill (the
|
|
743
743
|
// 2026-07-29 highlight collapse — the old highlight-9 and on-highlight are
|
|
744
744
|
// deleted; this stop carries both jobs, its on-color a paper token), the between
|
|
745
745
|
// text stop (a value the retired text-cta hover state used to generate bespokely,
|
|
@@ -748,9 +748,9 @@ var SHARED_NAMES = {
|
|
|
748
748
|
// two pre-collapse stop INDICES (11, and the off-scale anchor's 12 — emitted as a
|
|
749
749
|
// literal in cssRender, not through this table) with the between stop taking the
|
|
750
750
|
// vacated index 10; the indices (9/10/11) are unchanged since.
|
|
751
|
-
9: "
|
|
752
|
-
10: "
|
|
753
|
-
11: "
|
|
751
|
+
9: "pencil-47",
|
|
752
|
+
10: "pen-58",
|
|
753
|
+
11: "pen-70"
|
|
754
754
|
};
|
|
755
755
|
var SCALE_STOP_COUNT = Object.keys(SHARED_NAMES).length;
|
|
756
756
|
function stopTokenName(stop) {
|
|
@@ -758,8 +758,8 @@ function stopTokenName(stop) {
|
|
|
758
758
|
if (!name) throw new Error(`stopTokenName: unexpected stop ${stop}`);
|
|
759
759
|
return name;
|
|
760
760
|
}
|
|
761
|
-
var
|
|
762
|
-
var
|
|
761
|
+
var PAPER_0 = "paper-0";
|
|
762
|
+
var PEN_100 = "pen-100";
|
|
763
763
|
var STAMP_FILL = "stamp-fill";
|
|
764
764
|
var STAMP_FILL_HOVER = "stamp-fill-hover";
|
|
765
765
|
var STAMP_FILL_PRESSED = "stamp-fill-pressed";
|
|
@@ -796,7 +796,7 @@ var SYSTEM_LEAF = {
|
|
|
796
796
|
// and the 2026-08-29 inverse ladder): away-from-bg is the page-polarity pole
|
|
797
797
|
// (black in light, white in dark), toward-bg the flipped pole — STATE LAYERS for
|
|
798
798
|
// INVERTED grounds, whose polarity is opposite the page's. (Never call toward-bg
|
|
799
|
-
// a "
|
|
799
|
+
// a "highlighter": that word belongs to the tinted band stops.) The reversal lives in
|
|
800
800
|
// the engine: a consumer on an inverted ground names one token and never picks a
|
|
801
801
|
// pole. Colored fills never ride these; their states stay engine-solved stops.
|
|
802
802
|
// Rungs are two-digit percent, harmonizing with shadow-04/08/12.
|
|
@@ -812,10 +812,10 @@ var SYSTEM_LEAF = {
|
|
|
812
812
|
}
|
|
813
813
|
};
|
|
814
814
|
var SURFACE_PLANE_LAW = {
|
|
815
|
-
[SYSTEM_LEAF.SURFACE.DIM]: { light: SHARED_NAMES[3], dark:
|
|
815
|
+
[SYSTEM_LEAF.SURFACE.DIM]: { light: SHARED_NAMES[3], dark: PAPER_0 },
|
|
816
816
|
[SYSTEM_LEAF.SURFACE.LOW]: { light: SHARED_NAMES[2], dark: SHARED_NAMES[1] },
|
|
817
817
|
[SYSTEM_LEAF.SURFACE.MID]: { light: SHARED_NAMES[1], dark: SHARED_NAMES[2] },
|
|
818
|
-
[SYSTEM_LEAF.SURFACE.HIGH]: { light:
|
|
818
|
+
[SYSTEM_LEAF.SURFACE.HIGH]: { light: PAPER_0, dark: SHARED_NAMES[3] }
|
|
819
819
|
};
|
|
820
820
|
var TOKEN_ORDER = [
|
|
821
821
|
...Object.values(SHARED_NAMES),
|
|
@@ -831,26 +831,26 @@ function tokenOrder(name) {
|
|
|
831
831
|
}
|
|
832
832
|
|
|
833
833
|
// src/engine/requirements/spec.ts
|
|
834
|
-
var
|
|
835
|
-
var groupOf = (stop) => (stop === 0 ?
|
|
834
|
+
var textLane = (g2) => g2 === "pencil" || g2 === "pen" || g2 === "lead" || g2 === "ink";
|
|
835
|
+
var groupOf = (stop) => (stop === 0 ? PAPER_0 : stopTokenName(stop)).split("-")[0];
|
|
836
836
|
var PAPER0_DARK_ROOT_L = 0.16;
|
|
837
837
|
var PL_LADDER = { hue: "warm-drift", L: "perceptual", chroma: "ladder" };
|
|
838
838
|
var PL_TEXT = { hue: "warm-drift", L: "perceptual", chroma: "brand" };
|
|
839
839
|
var P_LIFT = { hue: "warm-torsion", L: "perceptual-lift", chroma: "ladder" };
|
|
840
840
|
var P_FIXED = { hue: "warm-torsion", L: "fixed", chroma: "ladder" };
|
|
841
841
|
var P_TEXT = { hue: "warm-torsion", L: "perceptual", chroma: "brand" };
|
|
842
|
-
var S8 = { metric: "wcag", against: "paper-
|
|
843
|
-
var T9 = { metric: "wcag", against: "paper-
|
|
844
|
-
var T10 = { metric: "wcag", against: "
|
|
845
|
-
var T11 = { metric: "wcag", against: "paper-
|
|
842
|
+
var S8 = { metric: "wcag", against: "paper-5", target: STOP_8_NONTEXT_CONTRAST, level: "AA" };
|
|
843
|
+
var T9 = { metric: "wcag", against: "paper-3", target: PENCIL_9_CONTRAST, level: "AA" };
|
|
844
|
+
var T10 = { metric: "wcag", against: "highlighter-20", target: PEN_10_CONTRAST, level: "AA" };
|
|
845
|
+
var T11 = { metric: "wcag", against: "paper-3", target: PEN_11_CONTRAST_FLOOR, level: "AAA" };
|
|
846
846
|
var ONS = { onFill: { metric: "apca-pole", enforce: true, ratioFloor: 4.5, coEnforceLc: 65 } };
|
|
847
847
|
var LIGHT = {
|
|
848
848
|
stops: [
|
|
849
|
-
// paper-
|
|
849
|
+
// paper-0 (paper-0 pre-Stage-B): the resolved ladder extreme — in light it genuinely is white (rootL 1.0, zero chroma)
|
|
850
850
|
{ 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 },
|
|
851
|
-
// paper/
|
|
851
|
+
// paper/highlighter/crayon-26: perceptual ladder/envelope blend on the geometric ROOT_L_LIGHT scaffold. Separation
|
|
852
852
|
// falls out of the shape (see above) — no min-separation require. Only stop 8 carries a require: the WCAG
|
|
853
|
-
// 3:1 vs the resolved paper-
|
|
853
|
+
// 3:1 vs the resolved paper-3 (paper-2) (re-solves automatically since it references paper-3 (paper-2)).
|
|
854
854
|
...[1, 2, 3, 4, 5, 6, 7, 8].map((stop) => ({
|
|
855
855
|
stop,
|
|
856
856
|
rootL: ROOT_L_LIGHT[stop],
|
|
@@ -860,12 +860,12 @@ var LIGHT = {
|
|
|
860
860
|
baseC: SCALE_C_LIGHT[stop].base,
|
|
861
861
|
require: stop === 8 ? S8 : void 0
|
|
862
862
|
})),
|
|
863
|
-
//
|
|
864
|
-
// highlight-9 collapse, owner 2026-07-29).
|
|
863
|
+
// pen text: perceptual + contrast-required. pencil-47 (ink-9) is ALSO the emphasis fill (the
|
|
864
|
+
// highlight-9 collapse, owner 2026-07-29). pen-58 (ink-10) is the between text stop (C49) —
|
|
865
865
|
// a normal stop like its neighbors; the three together are the text-register cta.
|
|
866
|
-
{ stop: 9, rootL: ROOT_L_LIGHT[9], group: groupOf(9), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[9].
|
|
867
|
-
{ stop: 10, rootL: ROOT_L_LIGHT[10], group: groupOf(10), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[10].
|
|
868
|
-
{ stop: 11, rootL: ROOT_L_LIGHT[11], group: groupOf(11), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[11].
|
|
866
|
+
{ 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 },
|
|
867
|
+
{ 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 },
|
|
868
|
+
{ 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 }
|
|
869
869
|
],
|
|
870
870
|
roles: [
|
|
871
871
|
{ role: "cta", produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: 0, chromaMult: 1 },
|
|
@@ -876,10 +876,10 @@ var LIGHT = {
|
|
|
876
876
|
};
|
|
877
877
|
var DARK = {
|
|
878
878
|
stops: [
|
|
879
|
-
// paper-
|
|
879
|
+
// paper-0 (paper-0 pre-Stage-B): the resolved ladder extreme — one seam BELOW paper-1 (paper-1), deep and brand-tinted, never the
|
|
880
880
|
// absolute void (the old hard-coded #000000 was "too much"). Lift applies like the rest of the scale.
|
|
881
881
|
{ stop: 0, rootL: PAPER0_DARK_ROOT_L, group: groupOf(0), produce: P_LIFT, satFraction: SCALE_C_DARK[0].sat },
|
|
882
|
-
// paper/
|
|
882
|
+
// paper/highlighter 1–7: perceptual on the dark scaffold. stop 8: FIXED at the hand-placed scaffold BUT with the
|
|
883
883
|
// 3:1 non-text require DECLARED (the Stage-5 flip, owner-approved) — the blue-recede failure is prevented
|
|
884
884
|
// BY RULE, not by patch. The require now genuinely PLACES the stop rather than catching a few low-luminance
|
|
885
885
|
// hues: on the delta-carry path it solves from the sentinel every time, and the old claim that "most hues
|
|
@@ -893,11 +893,11 @@ var DARK = {
|
|
|
893
893
|
satFraction: SCALE_C_DARK[stop].sat,
|
|
894
894
|
require: stop === 8 ? S8 : void 0
|
|
895
895
|
})),
|
|
896
|
-
//
|
|
896
|
+
// pen text: perceptual + the contrast requires DECLARED in dark too (Stage-5 flip): the scaffold already
|
|
897
897
|
// clears them for every hue (the gate proves it), so values don't move — but the guarantee is now a rule.
|
|
898
|
-
{ stop: 9, rootL: ROOT_L_DARK[9], group: groupOf(9), produce: P_TEXT, chromaMult: SCALE_C_DARK[9].
|
|
899
|
-
{ stop: 10, rootL: ROOT_L_DARK[10], group: groupOf(10), produce: P_TEXT, chromaMult: SCALE_C_DARK[10].
|
|
900
|
-
{ stop: 11, rootL: ROOT_L_DARK[11], group: groupOf(11), produce: P_TEXT, chromaMult: SCALE_C_DARK[11].
|
|
898
|
+
{ 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 },
|
|
899
|
+
{ 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 },
|
|
900
|
+
{ 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 }
|
|
901
901
|
],
|
|
902
902
|
roles: [
|
|
903
903
|
{ role: "cta", produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
|
|
@@ -908,7 +908,7 @@ var DARK = {
|
|
|
908
908
|
};
|
|
909
909
|
var MODE_SPECS = { light: LIGHT, dark: DARK };
|
|
910
910
|
|
|
911
|
-
//
|
|
911
|
+
// node_modules/helmlab/dist/index.js
|
|
912
912
|
function o0(n) {
|
|
913
913
|
let t = new Float64Array(9);
|
|
914
914
|
for (let e = 0; e < 3; e++) for (let s = 0; s < 3; s++) t[e * 3 + s] = n[e][s];
|
|
@@ -1824,7 +1824,7 @@ function p2Diff(a, b) {
|
|
|
1824
1824
|
var DEFAULT_APCA_LC_MAP = { 3: 30, 4.5: 75, 6.5: 85, 7: 90 };
|
|
1825
1825
|
function toApca(req, lcMap) {
|
|
1826
1826
|
if (req.metric !== "wcag") return req;
|
|
1827
|
-
if (req.against === "
|
|
1827
|
+
if (req.against === "highlighter-20") return { metric: "apca", against: "paper-3", targetLc: 85 };
|
|
1828
1828
|
const targetLc = lcMap[req.target];
|
|
1829
1829
|
if (targetLc === void 0) throw new Error(`apca profile: no Lc mapping for wcag target ${req.target}`);
|
|
1830
1830
|
return { metric: "apca", against: req.against, targetLc };
|
|
@@ -1838,7 +1838,7 @@ function withProfile(spec, profile, lcMap = DEFAULT_APCA_LC_MAP) {
|
|
|
1838
1838
|
// (The stop-9 CARVE-OUT is gone with the stop, owner 2026-07-29. It existed because
|
|
1839
1839
|
// highlight-9's require was a fill-vs-its-own-plane bar with no honest slot in a map
|
|
1840
1840
|
// of TEXT bars — 3 → 30 non-text, 4.5 → 75 body, 7 → 90 — so the apca lane kept its
|
|
1841
|
-
// own hl-9 placement instead. Today's stop 9 is
|
|
1841
|
+
// own hl-9 placement instead. Today's stop 9 is a PEN stop: 4.5 is a text bar, the
|
|
1842
1842
|
// 75 slot is its correct translation, and it maps like every other require.)
|
|
1843
1843
|
stops: spec.stops.map((s) => s.require ? { ...s, require: toApca(s.require, lcMap) } : s),
|
|
1844
1844
|
ons: {
|
|
@@ -2000,16 +2000,16 @@ function separationClampLight(ctx, placed, chromaAt, ref, target) {
|
|
|
2000
2000
|
return { L: pass, C: ceilingAt(pass), H: ctx.lightHueAt(pass) };
|
|
2001
2001
|
}
|
|
2002
2002
|
function placeLightText(ctx, rootL, cMult, maxLFor, deepen, maxC = Infinity) {
|
|
2003
|
-
const
|
|
2004
|
-
const anchorL = perceptualRungL(rootL, clampChromaToGamut(rootL,
|
|
2003
|
+
const textC = Math.min(cMult * ctx.brandC, maxC);
|
|
2004
|
+
const anchorL = perceptualRungL(rootL, clampChromaToGamut(rootL, textC, ctx.lightHueAt(rootL)), ctx.lightHueAt(rootL));
|
|
2005
2005
|
let H2 = ctx.lightHueAt(anchorL);
|
|
2006
|
-
let C2 = clampChromaToGamut(anchorL,
|
|
2006
|
+
let C2 = clampChromaToGamut(anchorL, textC, H2);
|
|
2007
2007
|
let L = Math.min(anchorL, maxLFor(C2, H2));
|
|
2008
2008
|
H2 = ctx.lightHueAt(L);
|
|
2009
|
-
C2 = clampChromaToGamut(L,
|
|
2009
|
+
C2 = clampChromaToGamut(L, textC, H2);
|
|
2010
2010
|
L = Math.min(anchorL, maxLFor(C2, H2));
|
|
2011
2011
|
L = Math.min(L - deepen, maxLFor(C2, ctx.lightHueAt(L - deepen)));
|
|
2012
|
-
return { L, C: ctx.cAt("light", L,
|
|
2012
|
+
return { L, C: ctx.cAt("light", L, textC), H: ctx.lightHueAt(L) };
|
|
2013
2013
|
}
|
|
2014
2014
|
function onFillIsWhiteLight(ctx, enforce, ratioFloor) {
|
|
2015
2015
|
const fill9ApcaY = apcaYAt(ctx.scaleL, clampChromaToGamut(ctx.scaleL, ctx.cAt("light", ctx.scaleL, ctx.brandC), ctx.brandH), ctx.brandH);
|
|
@@ -2023,7 +2023,7 @@ function buildDarkContext(ctx, specFloorL = DARK_CTA_MIN_L) {
|
|
|
2023
2023
|
return { dark9L, darkC9, darkHueAtL };
|
|
2024
2024
|
}
|
|
2025
2025
|
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));
|
|
2026
|
-
var
|
|
2026
|
+
var darkTextChromaAt = (ctx, dctx, floorBase, multiplier, maxC = Infinity) => (L) => ctx.cAt("dark", L, Math.min(applyChromaFloor(ctx.brandC, multiplier, floorBase, ctx.darkFloorStrength), maxC));
|
|
2027
2027
|
function placeDark(dctx, rootL, chromaAt, lift = false) {
|
|
2028
2028
|
let L = perceptualRungL(rootL, chromaAt(rootL), dctx.darkHueAtL(rootL));
|
|
2029
2029
|
if (lift) L = Math.max(L, rootL);
|
|
@@ -2279,12 +2279,14 @@ function solveDarkCtaExit(cur, cFor, darkH, redDark, enforceLc, coLc) {
|
|
|
2279
2279
|
var oklchToSrgb = (L, C2, H2) => Object.values(srgbEmitChannels({ L, C: C2, H: H2 })).map((c) => Math.max(0, Math.min(1, c)));
|
|
2280
2280
|
var toHex = (rgb) => "#" + rgb.map((c) => Math.round(c * 255).toString(16).padStart(2, "0")).join("");
|
|
2281
2281
|
var CTA_CLEARANCE_CAPS = [0.05, 0.92];
|
|
2282
|
+
var CHROMATIC_W80_WORST_SHIP_Y = { light: 0.459357, dark: 0.074936 };
|
|
2283
|
+
var CHROMATIC_P3_WORST_SHIP_Y = { light: 0.836007, dark: 0.016041 };
|
|
2282
2284
|
function resolveRamp(hex, mode, spec, opts) {
|
|
2283
2285
|
const o = hexToOklch(hex);
|
|
2284
2286
|
const seed = { hex, ...o };
|
|
2285
2287
|
const ctx = buildContext(hex, opts);
|
|
2286
2288
|
spec ?? (spec = MODE_SPECS[mode]);
|
|
2287
|
-
if (ctx.opts?.
|
|
2289
|
+
if (ctx.opts?.textGround) {
|
|
2288
2290
|
spec = {
|
|
2289
2291
|
...spec,
|
|
2290
2292
|
stops: spec.stops.map((s) => s.stop === 10 && s.require?.metric === "wcag" ? { ...s, require: { ...s.require, target: 6.5 } } : s)
|
|
@@ -2306,12 +2308,12 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2306
2308
|
if (!ref) throw new Error(`stop ${forWhom}: require against stop ${stopNum} but it is not resolved yet`);
|
|
2307
2309
|
return ref;
|
|
2308
2310
|
};
|
|
2309
|
-
const AGAINST_STOP = { "paper-
|
|
2311
|
+
const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "highlighter-20": 7 };
|
|
2310
2312
|
const declaredAnchor = (req) => req.metric === "min-separation" ? 1 : AGAINST_STOP[req.against] ?? 2;
|
|
2311
2313
|
const wcagAnchorStop = (req, stop) => stop >= 9 && declaredAnchor(req) <= 3 ? 3 : declaredAnchor(req);
|
|
2312
|
-
const
|
|
2313
|
-
const groundOf = (req, stop) =>
|
|
2314
|
-
const apcaGroundOf = (req, stop) =>
|
|
2314
|
+
const textGround = ctx.opts?.textGround?.[mode];
|
|
2315
|
+
const groundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(wcagAnchorStop(req, stop), stop);
|
|
2316
|
+
const apcaGroundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(declaredAnchor(req), stop);
|
|
2315
2317
|
const NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
|
|
2316
2318
|
const NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
|
|
2317
2319
|
const maxLForOf = (req, forWhom, withMargin) => {
|
|
@@ -2329,14 +2331,14 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2329
2331
|
}
|
|
2330
2332
|
throw new Error(`stop ${forWhom}: ${req.metric} is not a contrast require`);
|
|
2331
2333
|
};
|
|
2332
|
-
const
|
|
2334
|
+
const TEXT_DEEPEN = 0;
|
|
2333
2335
|
for (const sp of spec.stops) {
|
|
2334
2336
|
let placed;
|
|
2335
2337
|
let clamped = false, unresolvable;
|
|
2336
2338
|
if (mode === "light") {
|
|
2337
|
-
if (
|
|
2338
|
-
if (!sp.require) throw new Error(`light
|
|
2339
|
-
placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false),
|
|
2339
|
+
if (textLane(sp.group)) {
|
|
2340
|
+
if (!sp.require) throw new Error(`light pen stop ${sp.stop} must declare a contrast require`);
|
|
2341
|
+
placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false), TEXT_DEEPEN, sp.textMaxC);
|
|
2340
2342
|
clamped = true;
|
|
2341
2343
|
} else if (sp.produce.L === "fixed") {
|
|
2342
2344
|
const chromaAt = lightScaleChromaAt(ctx, sp.baseC ?? 0, sp.satFraction ?? 1);
|
|
@@ -2348,7 +2350,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2348
2350
|
placed = placeLightScale(ctx, sp.rootL, chromaAt, contrastReq ? maxLForOf(contrastReq, sp.stop, true) : void 0, targetChromaAt);
|
|
2349
2351
|
clamped = !!contrastReq;
|
|
2350
2352
|
if (sp.require?.metric === "min-separation") {
|
|
2351
|
-
const refStop = sp.require.against === "paper-
|
|
2353
|
+
const refStop = sp.require.against === "paper-1" ? 1 : sp.stop - 1;
|
|
2352
2354
|
const ref = stops.find((s) => s.stop === refStop);
|
|
2353
2355
|
if (!ref) throw new Error(`stop ${sp.stop}: min-separation against stop ${refStop} but it is not resolved yet`);
|
|
2354
2356
|
const before = placed.L;
|
|
@@ -2358,8 +2360,8 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2358
2360
|
}
|
|
2359
2361
|
} else {
|
|
2360
2362
|
const d = dctx;
|
|
2361
|
-
const
|
|
2362
|
-
const chromaAt =
|
|
2363
|
+
const textTwin = textLane(sp.group) && ctx.opts?.deltaCarry && ctx.opts?.chromaCurve ? ctx.opts.deltaLightStops?.find((s) => s.stop === sp.stop) : void 0;
|
|
2364
|
+
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);
|
|
2363
2365
|
let spineH = null;
|
|
2364
2366
|
const dl = ctx.opts?.deltaLightStops;
|
|
2365
2367
|
const ls = dl && sp.stop >= 1 && sp.stop <= 8 ? dl.find((s) => s.stop === sp.stop) : void 0;
|
|
@@ -2379,7 +2381,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2379
2381
|
}
|
|
2380
2382
|
if (ctx.opts.deltaHKPlace) L = perceptualRungL(sp.rootL, ls.C, ls.H);
|
|
2381
2383
|
if (ctx.opts.deltaLiftFloor) L = Math.max(L, sp.rootL);
|
|
2382
|
-
if (ctx.opts.deltaChromaEq && !
|
|
2384
|
+
if (ctx.opts.deltaChromaEq && !textLane(sp.group)) C2 = ctx.cAt("dark", L, perceptualDarkC(L, ls.H, ctx.brandC));
|
|
2383
2385
|
placed = { L, C: C2, H: spineH ?? ls.H };
|
|
2384
2386
|
} else if (sp.produce.L === "fixed") {
|
|
2385
2387
|
placed = ls ? placeDarkDelta(d, sp.rootL, chromaAt, ls) : { L: sp.rootL, C: chromaAt(sp.rootL), H: d.darkHueAtL(sp.rootL) };
|
|
@@ -2415,18 +2417,25 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2415
2417
|
if (sp.require?.metric === "wcag") {
|
|
2416
2418
|
const anchor = groundOf(sp.require, sp.stop);
|
|
2417
2419
|
const anchorShipY = shippedY(anchor.L, anchor.C, anchor.H);
|
|
2418
|
-
const paperBound = sp.stop >= 8 && !(
|
|
2420
|
+
const paperBound = sp.stop >= 8 && !(textGround && sp.stop >= 9);
|
|
2419
2421
|
const crossBoundY = wcagAnchorStop(sp.require, sp.stop) === 7 ? NEUTRAL_W80_WORST_SHIP_Y[mode] : NEUTRAL_P3_WORST_SHIP_Y[mode];
|
|
2420
2422
|
const shipRatio = (L) => {
|
|
2421
2423
|
const y2 = shippedY(L, placed.C, placed.H);
|
|
2422
2424
|
const own = contrastRatio(y2, anchorShipY);
|
|
2423
2425
|
return paperBound ? Math.min(own, contrastRatio(y2, crossBoundY)) : own;
|
|
2424
2426
|
};
|
|
2425
|
-
|
|
2427
|
+
const highlighterBoundY = ctx.opts?.crossHighlighterBoundY && sp.stop >= 10 && !textGround ? ctx.opts.crossHighlighterBoundY[mode] : void 0;
|
|
2428
|
+
const highlighterRatio = (L) => highlighterBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), highlighterBoundY);
|
|
2429
|
+
const paperBoundY = ctx.opts?.crossPaperBoundY && sp.stop >= 8 && !textGround ? ctx.opts.crossPaperBoundY[mode] : void 0;
|
|
2430
|
+
const paperBar = sp.stop === 8 ? STOP_8_NONTEXT_CONTRAST : sp.stop === 9 ? PENCIL_9_CONTRAST : PEN_10_CONTRAST;
|
|
2431
|
+
const paperRatio = (L) => paperBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), paperBoundY);
|
|
2432
|
+
const target = sp.require.target;
|
|
2433
|
+
const clears = (L) => shipRatio(L) >= target && highlighterRatio(L) >= PEN_10_CONTRAST && paperRatio(L) >= paperBar;
|
|
2434
|
+
if (!clears(placed.L)) {
|
|
2426
2435
|
const away = shippedY(placed.L, placed.C, placed.H) < anchorShipY ? -1 : 1;
|
|
2427
2436
|
let L2 = placed.L;
|
|
2428
|
-
for (let i = 0; i < 100 &&
|
|
2429
|
-
if (
|
|
2437
|
+
for (let i = 0; i < 100 && !clears(L2); i++) L2 += away * 1e-3;
|
|
2438
|
+
if (clears(L2)) {
|
|
2430
2439
|
placed = { ...placed, L: L2 };
|
|
2431
2440
|
clamped = true;
|
|
2432
2441
|
}
|
|
@@ -2437,13 +2446,15 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2437
2446
|
shipRatio(placed.L)
|
|
2438
2447
|
);
|
|
2439
2448
|
if (got < sp.require.target - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${got.toFixed(2)} < required ${sp.require.target}`;
|
|
2449
|
+
else if (highlighterRatio(placed.L) < PEN_10_CONTRAST - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${highlighterRatio(placed.L).toFixed(2)} < required ${PEN_10_CONTRAST} on the worst chromatic highlighter-20`;
|
|
2450
|
+
else if (paperRatio(placed.L) < paperBar - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${paperRatio(placed.L).toFixed(2)} < required ${paperBar} on the worst chromatic paper-5`;
|
|
2440
2451
|
} else if (sp.require?.metric === "apca") {
|
|
2441
2452
|
const ga = apcaGroundOf(sp.require, sp.stop);
|
|
2442
2453
|
const refA = apcaYAt(ga.L, ga.C, ga.H);
|
|
2443
2454
|
const got = Math.abs(apcaLc(apcaYAt(placed.L, clampChromaToGamut(placed.L, placed.C, placed.H), placed.H), refA));
|
|
2444
2455
|
if (got < sp.require.targetLc - APCA_TOL_LC) unresolvable = `stop ${sp.stop}: |Lc| ${got.toFixed(1)} < required ${sp.require.targetLc}`;
|
|
2445
2456
|
} else if (sp.require?.metric === "min-separation") {
|
|
2446
|
-
const ref = stops.find((s) => s.stop === (sp.require.against === "paper-
|
|
2457
|
+
const ref = stops.find((s) => s.stop === (sp.require.against === "paper-1" ? 1 : sp.stop - 1));
|
|
2447
2458
|
const rad2 = (h) => h * Math.PI / 180;
|
|
2448
2459
|
const gC = clampChromaToGamut(placed.L, placed.C, placed.H);
|
|
2449
2460
|
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);
|
|
@@ -2599,7 +2610,7 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
|
|
|
2599
2610
|
const dark = darkRamp.stops.filter((s) => s.stop >= 1).map(toStop).sort((a, b) => a.stop - b.stop);
|
|
2600
2611
|
const p0Light = lightRamp.stops.find((s) => s.stop === 0);
|
|
2601
2612
|
const p0Dark = darkRamp.stops.find((s) => s.stop === 0);
|
|
2602
|
-
const
|
|
2613
|
+
const penPole = (arr, L) => arr.find((s) => s.stop === 11) ? makeStop(12, L, 0, arr.find((s) => s.stop === 11).H) : void 0;
|
|
2603
2614
|
const cta = makeStop(9, lightRamp.roles.cta.L, lightRamp.roles.cta.C, lightRamp.roles.cta.H);
|
|
2604
2615
|
const ctaDark = makeStop(9, darkRamp.roles.cta.L, darkRamp.roles.cta.C, darkRamp.roles.cta.H);
|
|
2605
2616
|
const roleStop = (r, stop) => makeStop(stop, r.L, r.C, r.H);
|
|
@@ -2623,8 +2634,8 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
|
|
|
2623
2634
|
identityHex: hex.toUpperCase(),
|
|
2624
2635
|
paper0: p0Light ? makeStop(0, p0Light.L, p0Light.C, p0Light.H) : void 0,
|
|
2625
2636
|
paper0Dark: p0Dark ? makeStop(0, p0Dark.L, p0Dark.C, p0Dark.H) : void 0,
|
|
2626
|
-
|
|
2627
|
-
|
|
2637
|
+
pen100: penPole(light, 0),
|
|
2638
|
+
pen100Dark: penPole(dark, 1)
|
|
2628
2639
|
};
|
|
2629
2640
|
}
|
|
2630
2641
|
function generateNeutralScale(brandH, level = "default", contrastProfile) {
|
|
@@ -2636,7 +2647,11 @@ function generateNeutralScale(brandH, level = "default", contrastProfile) {
|
|
|
2636
2647
|
const scale = generateScale(grayHex, "neutral", "light", {
|
|
2637
2648
|
chromaCurve: curve,
|
|
2638
2649
|
enforceOnFillContrast: true,
|
|
2639
|
-
contrastProfile
|
|
2650
|
+
contrastProfile,
|
|
2651
|
+
// T13: the neutral's pens clear every chromatic family's highlighter-20 (the symmetric
|
|
2652
|
+
// pen guarantee); the chromatic ramps clear the neutral's through NEUTRAL_W80_WORST_SHIP_Y
|
|
2653
|
+
crossHighlighterBoundY: CHROMATIC_W80_WORST_SHIP_Y,
|
|
2654
|
+
crossPaperBoundY: CHROMATIC_P3_WORST_SHIP_Y
|
|
2640
2655
|
});
|
|
2641
2656
|
const asCta = (stop, src) => makeStop(stop, src.L, src.C, src.H);
|
|
2642
2657
|
scale.cta = asCta(9, scale.light[3]);
|
|
@@ -2696,8 +2711,7 @@ function generateSubtleSecondary(hex, opts) {
|
|
|
2696
2711
|
|
|
2697
2712
|
// src/engine/tokenDescriptions.ts
|
|
2698
2713
|
var AA_LARGE = "AA large text and UI elements";
|
|
2699
|
-
var AA_BODY = "AA standard body text
|
|
2700
|
-
var AAA_BODY = "AAA standard body text";
|
|
2714
|
+
var AA_BODY = "AA standard body text";
|
|
2701
2715
|
var FAMILY = {
|
|
2702
2716
|
neutral: "neutral",
|
|
2703
2717
|
brandPrimary: "brand",
|
|
@@ -2733,7 +2747,7 @@ var COLOR_WORD = {
|
|
|
2733
2747
|
[FAMILY.info]: " (blue)"
|
|
2734
2748
|
};
|
|
2735
2749
|
var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
|
|
2736
|
-
var
|
|
2750
|
+
var HIGHLIGHTER = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
|
|
2737
2751
|
var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
|
|
2738
2752
|
var OVERLAY = {
|
|
2739
2753
|
req: "translucent backgrounds that hold their reading on any paper",
|
|
@@ -2741,25 +2755,25 @@ var OVERLAY = {
|
|
|
2741
2755
|
collides: true
|
|
2742
2756
|
};
|
|
2743
2757
|
var SCALE = {
|
|
2744
|
-
"paper-
|
|
2758
|
+
"paper-1": PAPER,
|
|
2745
2759
|
"paper-99-overlay": OVERLAY,
|
|
2746
|
-
"paper-
|
|
2760
|
+
"paper-3": PAPER,
|
|
2747
2761
|
"paper-97-overlay": OVERLAY,
|
|
2748
|
-
"paper-
|
|
2762
|
+
"paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
|
|
2749
2763
|
"paper-95-overlay": OVERLAY,
|
|
2750
|
-
"
|
|
2751
|
-
"
|
|
2752
|
-
"
|
|
2753
|
-
"
|
|
2764
|
+
"highlighter-8": HIGHLIGHTER,
|
|
2765
|
+
"highlighter-11": HIGHLIGHTER,
|
|
2766
|
+
"highlighter-15": HIGHLIGHTER,
|
|
2767
|
+
"highlighter-20": HIGHLIGHTER,
|
|
2754
2768
|
// ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
|
|
2755
|
-
// landing on the
|
|
2756
|
-
"
|
|
2757
|
-
"
|
|
2758
|
-
"
|
|
2769
|
+
// landing on the crayon rows is worth its ic-ON-s noise — the one surviving body carrier)
|
|
2770
|
+
"crayon-26": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
|
|
2771
|
+
"pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2772
|
+
"pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2759
2773
|
// ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
|
|
2760
2774
|
// words, and a body carrying either floods that token's picker search — the C50 law;
|
|
2761
2775
|
// "strong-emphasis" reworded 2026-08-28: str-ON-g fed the "on" flood)
|
|
2762
|
-
"
|
|
2776
|
+
"pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2763
2777
|
// the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
|
|
2764
2778
|
// these bodies on purpose: it stopped being a token label, so it floods nothing,
|
|
2765
2779
|
// and a designer's "cta" query still lands on these rows.
|
|
@@ -2788,7 +2802,7 @@ var LINK_INVERSE = (state, contrast) => ({
|
|
|
2788
2802
|
var SYSTEM = {
|
|
2789
2803
|
"system/abs-black": ABS,
|
|
2790
2804
|
"system/abs-white": ABS,
|
|
2791
|
-
// (the community system/
|
|
2805
|
+
// (the community system/pen-100 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
|
|
2792
2806
|
// the anchor is engine-resolved and rides the neutral in BOTH plugins now —
|
|
2793
2807
|
// NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
|
|
2794
2808
|
"system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
|
|
@@ -2804,7 +2818,7 @@ var SYSTEM = {
|
|
|
2804
2818
|
"system/alpha/away-from-bg/08": OFFSET,
|
|
2805
2819
|
"system/alpha/away-from-bg/16": OFFSET,
|
|
2806
2820
|
// the inverse ladder: the offset rungs with the pole flipped per mode, for
|
|
2807
|
-
//
|
|
2821
|
+
// highlighters and edges over inverted backgrounds ("interaction" avoided in the
|
|
2808
2822
|
// bodies: acti-ON would feed the "on" flood)
|
|
2809
2823
|
"system/alpha/toward-bg/06": OFFSET_INVERSE,
|
|
2810
2824
|
"system/alpha/toward-bg/08": OFFSET_INVERSE,
|
|
@@ -2821,10 +2835,10 @@ var SYSTEM = {
|
|
|
2821
2835
|
"system/surface/high": PLANE("topmost plane \u2014 modals, dialogs"),
|
|
2822
2836
|
"system/link/default/enabled": LINK("", AA_BODY),
|
|
2823
2837
|
"system/link/default/hover": LINK(" pointer-over", AA_BODY),
|
|
2824
|
-
"system/link/default/pressed": LINK(" pressed",
|
|
2838
|
+
"system/link/default/pressed": LINK(" pressed", AA_BODY),
|
|
2825
2839
|
"system/link/inverse/enabled": LINK_INVERSE("", AA_BODY),
|
|
2826
2840
|
"system/link/inverse/hover": LINK_INVERSE(" pointer-over", AA_BODY),
|
|
2827
|
-
"system/link/inverse/pressed": LINK_INVERSE(" pressed",
|
|
2841
|
+
"system/link/inverse/pressed": LINK_INVERSE(" pressed", AA_BODY)
|
|
2828
2842
|
};
|
|
2829
2843
|
var PREFIXES = [
|
|
2830
2844
|
// community spellings FIRST: the 2026-08-21 family rename made the ext word a
|
|
@@ -2871,15 +2885,15 @@ function checkCollision(brand, signalScale, signalDef, mode) {
|
|
|
2871
2885
|
collides: hueGate && deltaE <= threshold
|
|
2872
2886
|
};
|
|
2873
2887
|
}
|
|
2874
|
-
var
|
|
2888
|
+
var HUE_COLLISION_HIGHLIGHTER_DEG = 15;
|
|
2875
2889
|
var HUE_COLLISION_MIN_V = 0.5;
|
|
2876
2890
|
var SECONDARY_NOTE_MIN_V = HUE_NOISE_C / VIVID_C;
|
|
2877
|
-
var
|
|
2878
|
-
function
|
|
2891
|
+
var HIGHLIGHTER_STOPS = [3, 4, 5, 6, 7];
|
|
2892
|
+
function highlighterProximity(brand, sig, mode) {
|
|
2879
2893
|
const b = mode === "light" ? brand.light : brand.dark;
|
|
2880
2894
|
const s = mode === "light" ? sig.light : sig.dark;
|
|
2881
2895
|
let dH = Infinity, dE = Infinity;
|
|
2882
|
-
for (const k of
|
|
2896
|
+
for (const k of HIGHLIGHTER_STOPS) {
|
|
2883
2897
|
const bs = b.find((x2) => x2.stop === k), ss = s.find((x2) => x2.stop === k);
|
|
2884
2898
|
if (!bs || !ss) continue;
|
|
2885
2899
|
dH = Math.min(dH, hueDistance(bs.H, ss.H));
|
|
@@ -2888,15 +2902,15 @@ function washProximity(brand, sig, mode) {
|
|
|
2888
2902
|
return { dH, dE };
|
|
2889
2903
|
}
|
|
2890
2904
|
function checkHueCollision(brand, signalScale, signalDef, opts) {
|
|
2891
|
-
const l =
|
|
2892
|
-
const d =
|
|
2905
|
+
const l = highlighterProximity(brand, signalScale, "light");
|
|
2906
|
+
const d = highlighterProximity(brand, signalScale, "dark");
|
|
2893
2907
|
const vividness = Math.min(1, brand.brandC / VIVID_C);
|
|
2894
2908
|
return {
|
|
2895
2909
|
signal: signalDef.name,
|
|
2896
|
-
|
|
2897
|
-
|
|
2910
|
+
dHueHighlighter: { light: l.dH, dark: d.dH },
|
|
2911
|
+
highlighterDeltaE: { light: l.dE, dark: d.dE },
|
|
2898
2912
|
vividness,
|
|
2899
|
-
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <=
|
|
2913
|
+
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_HIGHLIGHTER_DEG
|
|
2900
2914
|
};
|
|
2901
2915
|
}
|
|
2902
2916
|
var YELLOW_SPLIT_H = 96;
|
|
@@ -3089,7 +3103,7 @@ function resolveBrand(hex, name, opts) {
|
|
|
3089
3103
|
// (buildSignalScales/signalShift) — neutral alone stays outside; exact has enforce off.
|
|
3090
3104
|
apcaClearance: opts?.apcaClearance ?? true,
|
|
3091
3105
|
darkCtaFlatApp: opts?.darkCtaFlatApp,
|
|
3092
|
-
|
|
3106
|
+
textGround: opts?.textGround
|
|
3093
3107
|
};
|
|
3094
3108
|
let scale = generateScale(hex, name, void 0, floor);
|
|
3095
3109
|
let pending = [];
|
|
@@ -3172,7 +3186,7 @@ function resolveLinkTrio(linkHex, contrastProfile) {
|
|
|
3172
3186
|
const s = resolveBrand(linkHex, "link", { skipCollisionRules: true, contrastProfile }).scale;
|
|
3173
3187
|
const at = (arr, n) => {
|
|
3174
3188
|
const st = arr.find((x2) => x2.stop === n);
|
|
3175
|
-
if (!st) throw new Error(`resolveLinkTrio: the link resolve has no
|
|
3189
|
+
if (!st) throw new Error(`resolveLinkTrio: the link resolve has no pen stop ${n}`);
|
|
3176
3190
|
return st;
|
|
3177
3191
|
};
|
|
3178
3192
|
return {
|
|
@@ -3185,16 +3199,16 @@ function resolveLinkTrio(linkHex, contrastProfile) {
|
|
|
3185
3199
|
};
|
|
3186
3200
|
}
|
|
3187
3201
|
function resolveLinkInverseTrio(seedHex, contrastProfile) {
|
|
3188
|
-
const ground =
|
|
3202
|
+
const ground = PEN_70_GROUND[contrastProfile === "apca" ? "apca" : "wcag"];
|
|
3189
3203
|
const s = resolveBrand(seedHex, "link-inverse", {
|
|
3190
3204
|
skipCollisionRules: true,
|
|
3191
3205
|
contrastProfile,
|
|
3192
3206
|
// the cross — ramp keyed, ground mode-swapped
|
|
3193
|
-
|
|
3207
|
+
textGround: { light: ground.dark, dark: ground.light }
|
|
3194
3208
|
}).scale;
|
|
3195
3209
|
const at = (arr, n) => {
|
|
3196
3210
|
const st = arr.find((x2) => x2.stop === n);
|
|
3197
|
-
if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no
|
|
3211
|
+
if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no pen stop ${n}`);
|
|
3198
3212
|
return st;
|
|
3199
3213
|
};
|
|
3200
3214
|
return {
|
|
@@ -3208,12 +3222,12 @@ function resolveLinkInverseTrio(seedHex, contrastProfile) {
|
|
|
3208
3222
|
};
|
|
3209
3223
|
}
|
|
3210
3224
|
function escapeCtaFamily(nScale, mode, contrastProfile) {
|
|
3211
|
-
const
|
|
3212
|
-
if (!
|
|
3213
|
-
const mk = (stop, L) => makeStop(stop, L,
|
|
3214
|
-
const cta = mk(9,
|
|
3215
|
-
const ctaHover = mk(10, stateFillL(
|
|
3216
|
-
const ctaPressed = mk(11, stateFillL(
|
|
3225
|
+
const strongText = (mode === "light" ? nScale.light : nScale.dark).find((s) => s.stop === 11);
|
|
3226
|
+
if (!strongText) throw new Error("escapeCtaFamily: the neutral scale has no ink-11 stop");
|
|
3227
|
+
const mk = (stop, L) => makeStop(stop, L, strongText.C, strongText.H);
|
|
3228
|
+
const cta = mk(9, strongText.L);
|
|
3229
|
+
const ctaHover = mk(10, stateFillL(strongText.L, mode, 1));
|
|
3230
|
+
const ctaPressed = mk(11, stateFillL(strongText.L, mode, 2));
|
|
3217
3231
|
const onEnforce = contrastProfile !== "apca";
|
|
3218
3232
|
const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
|
|
3219
3233
|
const onFillIsWhite = onTextIsWhite(apcaY(cta.r, cta.g, cta.b), cta.L, cta.C, cta.H, onEnforce, onFloor);
|
|
@@ -3289,7 +3303,7 @@ function resolveTheme(input) {
|
|
|
3289
3303
|
for (const def of SIGNALS2) {
|
|
3290
3304
|
if (adoptedForSecondary.has(def.name)) continue;
|
|
3291
3305
|
const h = checkHueCollision(scale2, effectiveOf(def.name), def, { minV: SECONDARY_NOTE_MIN_V });
|
|
3292
|
-
if (h.collides) out.push(wording(def.name, Math.min(h.
|
|
3306
|
+
if (h.collides) out.push(wording(def.name, Math.min(h.highlighterDeltaE.light, h.highlighterDeltaE.dark)));
|
|
3293
3307
|
}
|
|
3294
3308
|
return out;
|
|
3295
3309
|
};
|
|
@@ -3339,7 +3353,7 @@ function resolveTheme(input) {
|
|
|
3339
3353
|
derived: true,
|
|
3340
3354
|
notes: [
|
|
3341
3355
|
`secondary derived from the brand color (default model, seed ${liftedHex})`,
|
|
3342
|
-
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (
|
|
3356
|
+
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (highlighter \u0394E ${dE.toFixed(3)}) \u2014 it tracks the brand color`)
|
|
3343
3357
|
],
|
|
3344
3358
|
distinctness: ctaDistinctness(primary.scale, scale2)
|
|
3345
3359
|
},
|
|
@@ -3364,7 +3378,7 @@ function resolveTheme(input) {
|
|
|
3364
3378
|
derived: false,
|
|
3365
3379
|
notes: [
|
|
3366
3380
|
`secondary keeps your color through the ramp; the cta is a tint of it (tint seed ${tintedHex})`,
|
|
3367
|
-
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (
|
|
3381
|
+
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (highlighter \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
|
|
3368
3382
|
],
|
|
3369
3383
|
distinctness: distinctness2
|
|
3370
3384
|
},
|
|
@@ -3376,7 +3390,7 @@ function resolveTheme(input) {
|
|
|
3376
3390
|
const scale = rSec.scale;
|
|
3377
3391
|
const level = "standard";
|
|
3378
3392
|
mergeSecondarySignals(scale, input.secondaryHex);
|
|
3379
|
-
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (
|
|
3393
|
+
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (highlighter \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
|
|
3380
3394
|
const distinctness = ctaDistinctness(primary.scale, scale);
|
|
3381
3395
|
if (distinctness.close)
|
|
3382
3396
|
notes.push(`secondary reads close to the primary (\u0394E ${Math.min(distinctness.light, distinctness.dark).toFixed(2)}) \u2014 consider a more distinct color`);
|
|
@@ -3435,7 +3449,7 @@ var alphaRootVars = (mode) => [
|
|
|
3435
3449
|
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`),
|
|
3436
3450
|
// the toward-bg ladder (the 2026-08-29 inverse ladder, renamed 2026-08-31): the
|
|
3437
3451
|
// same rungs, pole flipped per mode — state layers for INVERTED grounds (never
|
|
3438
|
-
// called a "
|
|
3452
|
+
// called a "highlighter" — that word is the tinted band's). One spelling with the Figma
|
|
3439
3453
|
// row: --alpha-toward-bg-06 ↔ system/alpha/toward-bg/06.
|
|
3440
3454
|
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` --alpha-toward-bg-${offsetLeafName(r)}: ${offsetRgba(r, mode === "light" ? "dark" : "light")};`)
|
|
3441
3455
|
];
|
|
@@ -3462,7 +3476,7 @@ function brandKindBody(prefix, s, mode, page) {
|
|
|
3462
3476
|
` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
|
|
3463
3477
|
` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
|
|
3464
3478
|
// the SOFT on-cta (owner 2026-08-04: "neutral cta on's should also be the alpha"): the
|
|
3465
|
-
// neutral's cta is the scale-fed
|
|
3479
|
+
// neutral's cta is the scale-fed HIGHLIGHTER-level fill, the system's other quiet cta, so its
|
|
3466
3480
|
// button text takes the pole AT ALPHA like the default-model secondary's — composited
|
|
3467
3481
|
// over whatever state the fill is in, so hover/pressed carry their own legibility. Same
|
|
3468
3482
|
// register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
|
|
@@ -3492,16 +3506,16 @@ function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBor
|
|
|
3492
3506
|
const p3Dark = brandKindP3Body(CSS_NEUTRAL, s, "dark");
|
|
3493
3507
|
return [
|
|
3494
3508
|
`${selector} {`,
|
|
3495
|
-
` --${
|
|
3496
|
-
` --${
|
|
3509
|
+
` --${PAPER_0}: ${p02(s.paper0, "#ffffff")};`,
|
|
3510
|
+
` --${PEN_100}: ${p02(s.pen100, "#000000")};`,
|
|
3497
3511
|
// the neutral IS the page, so it is judged against its own paper stop
|
|
3498
3512
|
// the neutral's overlays never read the visibility bar (the exempt family), so
|
|
3499
3513
|
// the brandless chrome context passes 0
|
|
3500
3514
|
...brandKindBody(CSS_NEUTRAL, s, "light", nPage("light")),
|
|
3501
3515
|
`}`,
|
|
3502
3516
|
`${selector}[data-theme="dark"] {`,
|
|
3503
|
-
` --${
|
|
3504
|
-
` --${
|
|
3517
|
+
` --${PAPER_0}: ${p02(s.paper0Dark, "#000000")};`,
|
|
3518
|
+
` --${PEN_100}: ${p02(s.pen100Dark, "#ffffff")};`,
|
|
3505
3519
|
...brandKindBody(CSS_NEUTRAL, s, "dark", nPage("dark")),
|
|
3506
3520
|
`}`,
|
|
3507
3521
|
...p3Light.length || p3Dark.length ? [
|
|
@@ -3590,8 +3604,8 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
|
|
|
3590
3604
|
const brandIdentity = ` --${CSS_BRAND}-identity: ${scale.identityHex};`;
|
|
3591
3605
|
const secondaryIdentity = secondary ? ` --${CSS_SECONDARY}-identity: ${secondary.identityHex};` : ` --${CSS_SECONDARY}-identity: var(--${CSS_BRAND}-identity);`;
|
|
3592
3606
|
const p0hex = (s, fallback) => s ? stopHex(s) : fallback;
|
|
3593
|
-
const lightAnchors = [` --${
|
|
3594
|
-
const darkAnchors = [` --${
|
|
3607
|
+
const lightAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0, "#ffffff")};`, ` --${PEN_100}: ${p0hex(nScale.pen100, "#000000")};`];
|
|
3608
|
+
const darkAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` --${PEN_100}: ${p0hex(nScale.pen100Dark, "#ffffff")};`];
|
|
3595
3609
|
const linkTrio = linkHex ? resolveLinkTrio(linkHex, contrastProfile) : null;
|
|
3596
3610
|
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)};`] : [
|
|
3597
3611
|
` --link: var(--${CSS_BRAND}-${stopTokenName(9)});`,
|
|
@@ -3779,17 +3793,17 @@ function themeToFigma(r, input) {
|
|
|
3779
3793
|
const build = (mode) => {
|
|
3780
3794
|
const p02 = mode === "light" ? nScale.paper0 : nScale.paper0Dark;
|
|
3781
3795
|
const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, neutralExtra(mode));
|
|
3782
|
-
const i02 = mode === "light" ? nScale.
|
|
3783
|
-
const
|
|
3796
|
+
const i02 = mode === "light" ? nScale.pen100 : nScale.pen100Dark;
|
|
3797
|
+
const splicePen100 = (g3) => {
|
|
3784
3798
|
if (!i02) return g3;
|
|
3785
3799
|
const out = {};
|
|
3786
3800
|
for (const [k, v2] of Object.entries(g3)) {
|
|
3787
3801
|
out[k] = v2;
|
|
3788
|
-
if (k === stopTokenName(11)) out[
|
|
3802
|
+
if (k === stopTokenName(11)) out[PEN_100] = colorFromStop(i02);
|
|
3789
3803
|
}
|
|
3790
3804
|
return out;
|
|
3791
3805
|
};
|
|
3792
|
-
const neutralGroup =
|
|
3806
|
+
const neutralGroup = splicePen100(p02 ? { [PAPER_0]: colorFromStop(p02), ...ramp } : ramp);
|
|
3793
3807
|
const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, brandExtra(secondary, mode, CSS_FAMILY.brandSecondary));
|
|
3794
3808
|
if (input.secondaryStyle === "outline" && input.secondary) {
|
|
3795
3809
|
const s8 = secondary[mode].find((s) => s.stop === 8);
|
|
@@ -3827,12 +3841,12 @@ function themeToFigma(r, input) {
|
|
|
3827
3841
|
putLeaf(g3, STAMP_ON, colorFromHex(esc.onFillIsWhite));
|
|
3828
3842
|
}
|
|
3829
3843
|
}
|
|
3830
|
-
const
|
|
3844
|
+
const scaleTextAt = (n) => {
|
|
3831
3845
|
const s = scale[mode].find((x2) => x2.stop === n);
|
|
3832
|
-
if (!s) throw new Error(`themeToFigma link: the brand scale has no
|
|
3846
|
+
if (!s) throw new Error(`themeToFigma link: the brand scale has no pen stop ${n}`);
|
|
3833
3847
|
return s;
|
|
3834
3848
|
};
|
|
3835
|
-
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(
|
|
3849
|
+
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)) };
|
|
3836
3850
|
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) };
|
|
3837
3851
|
const g2 = {
|
|
3838
3852
|
brand: brandGroup,
|
|
@@ -3948,11 +3962,11 @@ export {
|
|
|
3948
3962
|
DEFAULT_LINK_HEX,
|
|
3949
3963
|
DISABLED_OPACITY,
|
|
3950
3964
|
EXT_KEY,
|
|
3951
|
-
INK_0,
|
|
3952
3965
|
MODE_SPECS,
|
|
3953
3966
|
OFFSET_ALPHAS,
|
|
3954
3967
|
OUTLINE_HOVER_ALPHA,
|
|
3955
|
-
|
|
3968
|
+
PAPER_0,
|
|
3969
|
+
PEN_100,
|
|
3956
3970
|
RESOLVER_ID,
|
|
3957
3971
|
SCALE_STOP_COUNT,
|
|
3958
3972
|
SCRIM_ALPHA,
|