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.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);
|
|
@@ -2284,7 +2284,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2284
2284
|
const seed = { hex, ...o };
|
|
2285
2285
|
const ctx = buildContext(hex, opts);
|
|
2286
2286
|
spec ?? (spec = MODE_SPECS[mode]);
|
|
2287
|
-
if (ctx.opts?.
|
|
2287
|
+
if (ctx.opts?.textGround) {
|
|
2288
2288
|
spec = {
|
|
2289
2289
|
...spec,
|
|
2290
2290
|
stops: spec.stops.map((s) => s.stop === 10 && s.require?.metric === "wcag" ? { ...s, require: { ...s.require, target: 6.5 } } : s)
|
|
@@ -2306,12 +2306,12 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2306
2306
|
if (!ref) throw new Error(`stop ${forWhom}: require against stop ${stopNum} but it is not resolved yet`);
|
|
2307
2307
|
return ref;
|
|
2308
2308
|
};
|
|
2309
|
-
const AGAINST_STOP = { "paper-
|
|
2309
|
+
const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "highlighter-20": 7 };
|
|
2310
2310
|
const declaredAnchor = (req) => req.metric === "min-separation" ? 1 : AGAINST_STOP[req.against] ?? 2;
|
|
2311
2311
|
const wcagAnchorStop = (req, stop) => stop >= 9 && declaredAnchor(req) <= 3 ? 3 : declaredAnchor(req);
|
|
2312
|
-
const
|
|
2313
|
-
const groundOf = (req, stop) =>
|
|
2314
|
-
const apcaGroundOf = (req, stop) =>
|
|
2312
|
+
const textGround = ctx.opts?.textGround?.[mode];
|
|
2313
|
+
const groundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(wcagAnchorStop(req, stop), stop);
|
|
2314
|
+
const apcaGroundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(declaredAnchor(req), stop);
|
|
2315
2315
|
const NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
|
|
2316
2316
|
const NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
|
|
2317
2317
|
const maxLForOf = (req, forWhom, withMargin) => {
|
|
@@ -2329,14 +2329,14 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2329
2329
|
}
|
|
2330
2330
|
throw new Error(`stop ${forWhom}: ${req.metric} is not a contrast require`);
|
|
2331
2331
|
};
|
|
2332
|
-
const
|
|
2332
|
+
const TEXT_DEEPEN = 0;
|
|
2333
2333
|
for (const sp of spec.stops) {
|
|
2334
2334
|
let placed;
|
|
2335
2335
|
let clamped = false, unresolvable;
|
|
2336
2336
|
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),
|
|
2337
|
+
if (textLane(sp.group)) {
|
|
2338
|
+
if (!sp.require) throw new Error(`light pen stop ${sp.stop} must declare a contrast require`);
|
|
2339
|
+
placed = placeLightText(ctx, sp.rootL, sp.chromaMult ?? 1, maxLForOf(sp.require, sp.stop, false), TEXT_DEEPEN, sp.textMaxC);
|
|
2340
2340
|
clamped = true;
|
|
2341
2341
|
} else if (sp.produce.L === "fixed") {
|
|
2342
2342
|
const chromaAt = lightScaleChromaAt(ctx, sp.baseC ?? 0, sp.satFraction ?? 1);
|
|
@@ -2348,7 +2348,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2348
2348
|
placed = placeLightScale(ctx, sp.rootL, chromaAt, contrastReq ? maxLForOf(contrastReq, sp.stop, true) : void 0, targetChromaAt);
|
|
2349
2349
|
clamped = !!contrastReq;
|
|
2350
2350
|
if (sp.require?.metric === "min-separation") {
|
|
2351
|
-
const refStop = sp.require.against === "paper-
|
|
2351
|
+
const refStop = sp.require.against === "paper-1" ? 1 : sp.stop - 1;
|
|
2352
2352
|
const ref = stops.find((s) => s.stop === refStop);
|
|
2353
2353
|
if (!ref) throw new Error(`stop ${sp.stop}: min-separation against stop ${refStop} but it is not resolved yet`);
|
|
2354
2354
|
const before = placed.L;
|
|
@@ -2358,8 +2358,8 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2358
2358
|
}
|
|
2359
2359
|
} else {
|
|
2360
2360
|
const d = dctx;
|
|
2361
|
-
const
|
|
2362
|
-
const chromaAt =
|
|
2361
|
+
const textTwin = textLane(sp.group) && ctx.opts?.deltaCarry && ctx.opts?.chromaCurve ? ctx.opts.deltaLightStops?.find((s) => s.stop === sp.stop) : void 0;
|
|
2362
|
+
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
2363
|
let spineH = null;
|
|
2364
2364
|
const dl = ctx.opts?.deltaLightStops;
|
|
2365
2365
|
const ls = dl && sp.stop >= 1 && sp.stop <= 8 ? dl.find((s) => s.stop === sp.stop) : void 0;
|
|
@@ -2379,7 +2379,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2379
2379
|
}
|
|
2380
2380
|
if (ctx.opts.deltaHKPlace) L = perceptualRungL(sp.rootL, ls.C, ls.H);
|
|
2381
2381
|
if (ctx.opts.deltaLiftFloor) L = Math.max(L, sp.rootL);
|
|
2382
|
-
if (ctx.opts.deltaChromaEq && !
|
|
2382
|
+
if (ctx.opts.deltaChromaEq && !textLane(sp.group)) C2 = ctx.cAt("dark", L, perceptualDarkC(L, ls.H, ctx.brandC));
|
|
2383
2383
|
placed = { L, C: C2, H: spineH ?? ls.H };
|
|
2384
2384
|
} else if (sp.produce.L === "fixed") {
|
|
2385
2385
|
placed = ls ? placeDarkDelta(d, sp.rootL, chromaAt, ls) : { L: sp.rootL, C: chromaAt(sp.rootL), H: d.darkHueAtL(sp.rootL) };
|
|
@@ -2415,7 +2415,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2415
2415
|
if (sp.require?.metric === "wcag") {
|
|
2416
2416
|
const anchor = groundOf(sp.require, sp.stop);
|
|
2417
2417
|
const anchorShipY = shippedY(anchor.L, anchor.C, anchor.H);
|
|
2418
|
-
const paperBound = sp.stop >= 8 && !(
|
|
2418
|
+
const paperBound = sp.stop >= 8 && !(textGround && sp.stop >= 9);
|
|
2419
2419
|
const crossBoundY = wcagAnchorStop(sp.require, sp.stop) === 7 ? NEUTRAL_W80_WORST_SHIP_Y[mode] : NEUTRAL_P3_WORST_SHIP_Y[mode];
|
|
2420
2420
|
const shipRatio = (L) => {
|
|
2421
2421
|
const y2 = shippedY(L, placed.C, placed.H);
|
|
@@ -2443,7 +2443,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2443
2443
|
const got = Math.abs(apcaLc(apcaYAt(placed.L, clampChromaToGamut(placed.L, placed.C, placed.H), placed.H), refA));
|
|
2444
2444
|
if (got < sp.require.targetLc - APCA_TOL_LC) unresolvable = `stop ${sp.stop}: |Lc| ${got.toFixed(1)} < required ${sp.require.targetLc}`;
|
|
2445
2445
|
} else if (sp.require?.metric === "min-separation") {
|
|
2446
|
-
const ref = stops.find((s) => s.stop === (sp.require.against === "paper-
|
|
2446
|
+
const ref = stops.find((s) => s.stop === (sp.require.against === "paper-1" ? 1 : sp.stop - 1));
|
|
2447
2447
|
const rad2 = (h) => h * Math.PI / 180;
|
|
2448
2448
|
const gC = clampChromaToGamut(placed.L, placed.C, placed.H);
|
|
2449
2449
|
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 +2599,7 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
|
|
|
2599
2599
|
const dark = darkRamp.stops.filter((s) => s.stop >= 1).map(toStop).sort((a, b) => a.stop - b.stop);
|
|
2600
2600
|
const p0Light = lightRamp.stops.find((s) => s.stop === 0);
|
|
2601
2601
|
const p0Dark = darkRamp.stops.find((s) => s.stop === 0);
|
|
2602
|
-
const
|
|
2602
|
+
const penPole = (arr, L) => arr.find((s) => s.stop === 11) ? makeStop(12, L, 0, arr.find((s) => s.stop === 11).H) : void 0;
|
|
2603
2603
|
const cta = makeStop(9, lightRamp.roles.cta.L, lightRamp.roles.cta.C, lightRamp.roles.cta.H);
|
|
2604
2604
|
const ctaDark = makeStop(9, darkRamp.roles.cta.L, darkRamp.roles.cta.C, darkRamp.roles.cta.H);
|
|
2605
2605
|
const roleStop = (r, stop) => makeStop(stop, r.L, r.C, r.H);
|
|
@@ -2623,8 +2623,8 @@ function generateScale(hex, scaleName, forcedArchetype, opts) {
|
|
|
2623
2623
|
identityHex: hex.toUpperCase(),
|
|
2624
2624
|
paper0: p0Light ? makeStop(0, p0Light.L, p0Light.C, p0Light.H) : void 0,
|
|
2625
2625
|
paper0Dark: p0Dark ? makeStop(0, p0Dark.L, p0Dark.C, p0Dark.H) : void 0,
|
|
2626
|
-
|
|
2627
|
-
|
|
2626
|
+
pen100: penPole(light, 0),
|
|
2627
|
+
pen100Dark: penPole(dark, 1)
|
|
2628
2628
|
};
|
|
2629
2629
|
}
|
|
2630
2630
|
function generateNeutralScale(brandH, level = "default", contrastProfile) {
|
|
@@ -2696,8 +2696,7 @@ function generateSubtleSecondary(hex, opts) {
|
|
|
2696
2696
|
|
|
2697
2697
|
// src/engine/tokenDescriptions.ts
|
|
2698
2698
|
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";
|
|
2699
|
+
var AA_BODY = "AA standard body text";
|
|
2701
2700
|
var FAMILY = {
|
|
2702
2701
|
neutral: "neutral",
|
|
2703
2702
|
brandPrimary: "brand",
|
|
@@ -2733,7 +2732,7 @@ var COLOR_WORD = {
|
|
|
2733
2732
|
[FAMILY.info]: " (blue)"
|
|
2734
2733
|
};
|
|
2735
2734
|
var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
|
|
2736
|
-
var
|
|
2735
|
+
var HIGHLIGHTER = { req: "subtle interactive states, decorative borders, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
|
|
2737
2736
|
var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
|
|
2738
2737
|
var OVERLAY = {
|
|
2739
2738
|
req: "translucent backgrounds that hold their reading on any paper",
|
|
@@ -2741,25 +2740,25 @@ var OVERLAY = {
|
|
|
2741
2740
|
collides: true
|
|
2742
2741
|
};
|
|
2743
2742
|
var SCALE = {
|
|
2744
|
-
"paper-
|
|
2743
|
+
"paper-1": PAPER,
|
|
2745
2744
|
"paper-99-overlay": OVERLAY,
|
|
2746
|
-
"paper-
|
|
2745
|
+
"paper-3": PAPER,
|
|
2747
2746
|
"paper-97-overlay": OVERLAY,
|
|
2748
|
-
"paper-
|
|
2747
|
+
"paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
|
|
2749
2748
|
"paper-95-overlay": OVERLAY,
|
|
2750
|
-
"
|
|
2751
|
-
"
|
|
2752
|
-
"
|
|
2753
|
-
"
|
|
2749
|
+
"highlighter-8": HIGHLIGHTER,
|
|
2750
|
+
"highlighter-11": HIGHLIGHTER,
|
|
2751
|
+
"highlighter-15": HIGHLIGHTER,
|
|
2752
|
+
"highlighter-20": HIGHLIGHTER,
|
|
2754
2753
|
// ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
|
|
2755
|
-
// landing on the
|
|
2756
|
-
"
|
|
2757
|
-
"
|
|
2758
|
-
"
|
|
2754
|
+
// landing on the crayon rows is worth its ic-ON-s noise — the one surviving body carrier)
|
|
2755
|
+
"crayon-26": { req: "focus rings, icons, large text", contrast: AA_LARGE, theming: solved, collides: true },
|
|
2756
|
+
"pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2757
|
+
"pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2759
2758
|
// ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
|
|
2760
2759
|
// words, and a body carrying either floods that token's picker search — the C50 law;
|
|
2761
2760
|
// "strong-emphasis" reworded 2026-08-28: str-ON-g fed the "on" flood)
|
|
2762
|
-
"
|
|
2761
|
+
"pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2763
2762
|
// the stamp family (cta words → solid 2026-08-18, solid → stamp 2026-08-27). "CTA" stays in
|
|
2764
2763
|
// these bodies on purpose: it stopped being a token label, so it floods nothing,
|
|
2765
2764
|
// and a designer's "cta" query still lands on these rows.
|
|
@@ -2788,7 +2787,7 @@ var LINK_INVERSE = (state, contrast) => ({
|
|
|
2788
2787
|
var SYSTEM = {
|
|
2789
2788
|
"system/abs-black": ABS,
|
|
2790
2789
|
"system/abs-white": ABS,
|
|
2791
|
-
// (the community system/
|
|
2790
|
+
// (the community system/pen-100 row RETIRED 2026-08-28 with its STATIC_UTILS entry:
|
|
2792
2791
|
// the anchor is engine-resolved and rides the neutral in BOTH plugins now —
|
|
2793
2792
|
// NEUTRAL_ONLY above. Old files' static rows orphan unwritten.)
|
|
2794
2793
|
"system/abs-primary": { req: "identity seed reference", theming: "the theme\u2019s own input, as given" },
|
|
@@ -2804,7 +2803,7 @@ var SYSTEM = {
|
|
|
2804
2803
|
"system/alpha/away-from-bg/08": OFFSET,
|
|
2805
2804
|
"system/alpha/away-from-bg/16": OFFSET,
|
|
2806
2805
|
// the inverse ladder: the offset rungs with the pole flipped per mode, for
|
|
2807
|
-
//
|
|
2806
|
+
// highlighters and edges over inverted backgrounds ("interaction" avoided in the
|
|
2808
2807
|
// bodies: acti-ON would feed the "on" flood)
|
|
2809
2808
|
"system/alpha/toward-bg/06": OFFSET_INVERSE,
|
|
2810
2809
|
"system/alpha/toward-bg/08": OFFSET_INVERSE,
|
|
@@ -2821,10 +2820,10 @@ var SYSTEM = {
|
|
|
2821
2820
|
"system/surface/high": PLANE("topmost plane \u2014 modals, dialogs"),
|
|
2822
2821
|
"system/link/default/enabled": LINK("", AA_BODY),
|
|
2823
2822
|
"system/link/default/hover": LINK(" pointer-over", AA_BODY),
|
|
2824
|
-
"system/link/default/pressed": LINK(" pressed",
|
|
2823
|
+
"system/link/default/pressed": LINK(" pressed", AA_BODY),
|
|
2825
2824
|
"system/link/inverse/enabled": LINK_INVERSE("", AA_BODY),
|
|
2826
2825
|
"system/link/inverse/hover": LINK_INVERSE(" pointer-over", AA_BODY),
|
|
2827
|
-
"system/link/inverse/pressed": LINK_INVERSE(" pressed",
|
|
2826
|
+
"system/link/inverse/pressed": LINK_INVERSE(" pressed", AA_BODY)
|
|
2828
2827
|
};
|
|
2829
2828
|
var PREFIXES = [
|
|
2830
2829
|
// community spellings FIRST: the 2026-08-21 family rename made the ext word a
|
|
@@ -2871,15 +2870,15 @@ function checkCollision(brand, signalScale, signalDef, mode) {
|
|
|
2871
2870
|
collides: hueGate && deltaE <= threshold
|
|
2872
2871
|
};
|
|
2873
2872
|
}
|
|
2874
|
-
var
|
|
2873
|
+
var HUE_COLLISION_HIGHLIGHTER_DEG = 15;
|
|
2875
2874
|
var HUE_COLLISION_MIN_V = 0.5;
|
|
2876
2875
|
var SECONDARY_NOTE_MIN_V = HUE_NOISE_C / VIVID_C;
|
|
2877
|
-
var
|
|
2878
|
-
function
|
|
2876
|
+
var HIGHLIGHTER_STOPS = [3, 4, 5, 6, 7];
|
|
2877
|
+
function highlighterProximity(brand, sig, mode) {
|
|
2879
2878
|
const b = mode === "light" ? brand.light : brand.dark;
|
|
2880
2879
|
const s = mode === "light" ? sig.light : sig.dark;
|
|
2881
2880
|
let dH = Infinity, dE = Infinity;
|
|
2882
|
-
for (const k of
|
|
2881
|
+
for (const k of HIGHLIGHTER_STOPS) {
|
|
2883
2882
|
const bs = b.find((x2) => x2.stop === k), ss = s.find((x2) => x2.stop === k);
|
|
2884
2883
|
if (!bs || !ss) continue;
|
|
2885
2884
|
dH = Math.min(dH, hueDistance(bs.H, ss.H));
|
|
@@ -2888,15 +2887,15 @@ function washProximity(brand, sig, mode) {
|
|
|
2888
2887
|
return { dH, dE };
|
|
2889
2888
|
}
|
|
2890
2889
|
function checkHueCollision(brand, signalScale, signalDef, opts) {
|
|
2891
|
-
const l =
|
|
2892
|
-
const d =
|
|
2890
|
+
const l = highlighterProximity(brand, signalScale, "light");
|
|
2891
|
+
const d = highlighterProximity(brand, signalScale, "dark");
|
|
2893
2892
|
const vividness = Math.min(1, brand.brandC / VIVID_C);
|
|
2894
2893
|
return {
|
|
2895
2894
|
signal: signalDef.name,
|
|
2896
|
-
|
|
2897
|
-
|
|
2895
|
+
dHueHighlighter: { light: l.dH, dark: d.dH },
|
|
2896
|
+
highlighterDeltaE: { light: l.dE, dark: d.dE },
|
|
2898
2897
|
vividness,
|
|
2899
|
-
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <=
|
|
2898
|
+
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_HIGHLIGHTER_DEG
|
|
2900
2899
|
};
|
|
2901
2900
|
}
|
|
2902
2901
|
var YELLOW_SPLIT_H = 96;
|
|
@@ -3089,7 +3088,7 @@ function resolveBrand(hex, name, opts) {
|
|
|
3089
3088
|
// (buildSignalScales/signalShift) — neutral alone stays outside; exact has enforce off.
|
|
3090
3089
|
apcaClearance: opts?.apcaClearance ?? true,
|
|
3091
3090
|
darkCtaFlatApp: opts?.darkCtaFlatApp,
|
|
3092
|
-
|
|
3091
|
+
textGround: opts?.textGround
|
|
3093
3092
|
};
|
|
3094
3093
|
let scale = generateScale(hex, name, void 0, floor);
|
|
3095
3094
|
let pending = [];
|
|
@@ -3172,7 +3171,7 @@ function resolveLinkTrio(linkHex, contrastProfile) {
|
|
|
3172
3171
|
const s = resolveBrand(linkHex, "link", { skipCollisionRules: true, contrastProfile }).scale;
|
|
3173
3172
|
const at = (arr, n) => {
|
|
3174
3173
|
const st = arr.find((x2) => x2.stop === n);
|
|
3175
|
-
if (!st) throw new Error(`resolveLinkTrio: the link resolve has no
|
|
3174
|
+
if (!st) throw new Error(`resolveLinkTrio: the link resolve has no pen stop ${n}`);
|
|
3176
3175
|
return st;
|
|
3177
3176
|
};
|
|
3178
3177
|
return {
|
|
@@ -3185,16 +3184,16 @@ function resolveLinkTrio(linkHex, contrastProfile) {
|
|
|
3185
3184
|
};
|
|
3186
3185
|
}
|
|
3187
3186
|
function resolveLinkInverseTrio(seedHex, contrastProfile) {
|
|
3188
|
-
const ground =
|
|
3187
|
+
const ground = PEN_70_GROUND[contrastProfile === "apca" ? "apca" : "wcag"];
|
|
3189
3188
|
const s = resolveBrand(seedHex, "link-inverse", {
|
|
3190
3189
|
skipCollisionRules: true,
|
|
3191
3190
|
contrastProfile,
|
|
3192
3191
|
// the cross — ramp keyed, ground mode-swapped
|
|
3193
|
-
|
|
3192
|
+
textGround: { light: ground.dark, dark: ground.light }
|
|
3194
3193
|
}).scale;
|
|
3195
3194
|
const at = (arr, n) => {
|
|
3196
3195
|
const st = arr.find((x2) => x2.stop === n);
|
|
3197
|
-
if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no
|
|
3196
|
+
if (!st) throw new Error(`resolveLinkInverseTrio: the link resolve has no pen stop ${n}`);
|
|
3198
3197
|
return st;
|
|
3199
3198
|
};
|
|
3200
3199
|
return {
|
|
@@ -3208,12 +3207,12 @@ function resolveLinkInverseTrio(seedHex, contrastProfile) {
|
|
|
3208
3207
|
};
|
|
3209
3208
|
}
|
|
3210
3209
|
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(
|
|
3210
|
+
const strongText = (mode === "light" ? nScale.light : nScale.dark).find((s) => s.stop === 11);
|
|
3211
|
+
if (!strongText) throw new Error("escapeCtaFamily: the neutral scale has no ink-11 stop");
|
|
3212
|
+
const mk = (stop, L) => makeStop(stop, L, strongText.C, strongText.H);
|
|
3213
|
+
const cta = mk(9, strongText.L);
|
|
3214
|
+
const ctaHover = mk(10, stateFillL(strongText.L, mode, 1));
|
|
3215
|
+
const ctaPressed = mk(11, stateFillL(strongText.L, mode, 2));
|
|
3217
3216
|
const onEnforce = contrastProfile !== "apca";
|
|
3218
3217
|
const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
|
|
3219
3218
|
const onFillIsWhite = onTextIsWhite(apcaY(cta.r, cta.g, cta.b), cta.L, cta.C, cta.H, onEnforce, onFloor);
|
|
@@ -3289,7 +3288,7 @@ function resolveTheme(input) {
|
|
|
3289
3288
|
for (const def of SIGNALS2) {
|
|
3290
3289
|
if (adoptedForSecondary.has(def.name)) continue;
|
|
3291
3290
|
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.
|
|
3291
|
+
if (h.collides) out.push(wording(def.name, Math.min(h.highlighterDeltaE.light, h.highlighterDeltaE.dark)));
|
|
3293
3292
|
}
|
|
3294
3293
|
return out;
|
|
3295
3294
|
};
|
|
@@ -3339,7 +3338,7 @@ function resolveTheme(input) {
|
|
|
3339
3338
|
derived: true,
|
|
3340
3339
|
notes: [
|
|
3341
3340
|
`secondary derived from the brand color (default model, seed ${liftedHex})`,
|
|
3342
|
-
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (
|
|
3341
|
+
...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
3342
|
],
|
|
3344
3343
|
distinctness: ctaDistinctness(primary.scale, scale2)
|
|
3345
3344
|
},
|
|
@@ -3364,7 +3363,7 @@ function resolveTheme(input) {
|
|
|
3364
3363
|
derived: false,
|
|
3365
3364
|
notes: [
|
|
3366
3365
|
`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 (
|
|
3366
|
+
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (highlighter \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
|
|
3368
3367
|
],
|
|
3369
3368
|
distinctness: distinctness2
|
|
3370
3369
|
},
|
|
@@ -3376,7 +3375,7 @@ function resolveTheme(input) {
|
|
|
3376
3375
|
const scale = rSec.scale;
|
|
3377
3376
|
const level = "standard";
|
|
3378
3377
|
mergeSecondarySignals(scale, input.secondaryHex);
|
|
3379
|
-
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (
|
|
3378
|
+
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (highlighter \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
|
|
3380
3379
|
const distinctness = ctaDistinctness(primary.scale, scale);
|
|
3381
3380
|
if (distinctness.close)
|
|
3382
3381
|
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 +3434,7 @@ var alphaRootVars = (mode) => [
|
|
|
3435
3434
|
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`),
|
|
3436
3435
|
// the toward-bg ladder (the 2026-08-29 inverse ladder, renamed 2026-08-31): the
|
|
3437
3436
|
// same rungs, pole flipped per mode — state layers for INVERTED grounds (never
|
|
3438
|
-
// called a "
|
|
3437
|
+
// called a "highlighter" — that word is the tinted band's). One spelling with the Figma
|
|
3439
3438
|
// row: --alpha-toward-bg-06 ↔ system/alpha/toward-bg/06.
|
|
3440
3439
|
...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
3440
|
];
|
|
@@ -3462,7 +3461,7 @@ function brandKindBody(prefix, s, mode, page) {
|
|
|
3462
3461
|
` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
|
|
3463
3462
|
` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
|
|
3464
3463
|
// 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
|
|
3464
|
+
// neutral's cta is the scale-fed HIGHLIGHTER-level fill, the system's other quiet cta, so its
|
|
3466
3465
|
// button text takes the pole AT ALPHA like the default-model secondary's — composited
|
|
3467
3466
|
// over whatever state the fill is in, so hover/pressed carry their own legibility. Same
|
|
3468
3467
|
// register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
|
|
@@ -3492,16 +3491,16 @@ function neutralCss(selector, brandH, level = "default", contrastProfile, ctaBor
|
|
|
3492
3491
|
const p3Dark = brandKindP3Body(CSS_NEUTRAL, s, "dark");
|
|
3493
3492
|
return [
|
|
3494
3493
|
`${selector} {`,
|
|
3495
|
-
` --${
|
|
3496
|
-
` --${
|
|
3494
|
+
` --${PAPER_0}: ${p02(s.paper0, "#ffffff")};`,
|
|
3495
|
+
` --${PEN_100}: ${p02(s.pen100, "#000000")};`,
|
|
3497
3496
|
// the neutral IS the page, so it is judged against its own paper stop
|
|
3498
3497
|
// the neutral's overlays never read the visibility bar (the exempt family), so
|
|
3499
3498
|
// the brandless chrome context passes 0
|
|
3500
3499
|
...brandKindBody(CSS_NEUTRAL, s, "light", nPage("light")),
|
|
3501
3500
|
`}`,
|
|
3502
3501
|
`${selector}[data-theme="dark"] {`,
|
|
3503
|
-
` --${
|
|
3504
|
-
` --${
|
|
3502
|
+
` --${PAPER_0}: ${p02(s.paper0Dark, "#000000")};`,
|
|
3503
|
+
` --${PEN_100}: ${p02(s.pen100Dark, "#ffffff")};`,
|
|
3505
3504
|
...brandKindBody(CSS_NEUTRAL, s, "dark", nPage("dark")),
|
|
3506
3505
|
`}`,
|
|
3507
3506
|
...p3Light.length || p3Dark.length ? [
|
|
@@ -3590,8 +3589,8 @@ function brandCss(slug, displayName, r, secondary, noteSuffix = "", neutralLevel
|
|
|
3590
3589
|
const brandIdentity = ` --${CSS_BRAND}-identity: ${scale.identityHex};`;
|
|
3591
3590
|
const secondaryIdentity = secondary ? ` --${CSS_SECONDARY}-identity: ${secondary.identityHex};` : ` --${CSS_SECONDARY}-identity: var(--${CSS_BRAND}-identity);`;
|
|
3592
3591
|
const p0hex = (s, fallback) => s ? stopHex(s) : fallback;
|
|
3593
|
-
const lightAnchors = [` --${
|
|
3594
|
-
const darkAnchors = [` --${
|
|
3592
|
+
const lightAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0, "#ffffff")};`, ` --${PEN_100}: ${p0hex(nScale.pen100, "#000000")};`];
|
|
3593
|
+
const darkAnchors = [` --${PAPER_0}: ${p0hex(nScale.paper0Dark, "#000000")};`, ` --${PEN_100}: ${p0hex(nScale.pen100Dark, "#ffffff")};`];
|
|
3595
3594
|
const linkTrio = linkHex ? resolveLinkTrio(linkHex, contrastProfile) : null;
|
|
3596
3595
|
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
3596
|
` --link: var(--${CSS_BRAND}-${stopTokenName(9)});`,
|
|
@@ -3779,17 +3778,17 @@ function themeToFigma(r, input) {
|
|
|
3779
3778
|
const build = (mode) => {
|
|
3780
3779
|
const p02 = mode === "light" ? nScale.paper0 : nScale.paper0Dark;
|
|
3781
3780
|
const ramp = rampGroup(nScale[mode], mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark, neutralExtra(mode));
|
|
3782
|
-
const i02 = mode === "light" ? nScale.
|
|
3783
|
-
const
|
|
3781
|
+
const i02 = mode === "light" ? nScale.pen100 : nScale.pen100Dark;
|
|
3782
|
+
const splicePen100 = (g3) => {
|
|
3784
3783
|
if (!i02) return g3;
|
|
3785
3784
|
const out = {};
|
|
3786
3785
|
for (const [k, v2] of Object.entries(g3)) {
|
|
3787
3786
|
out[k] = v2;
|
|
3788
|
-
if (k === stopTokenName(11)) out[
|
|
3787
|
+
if (k === stopTokenName(11)) out[PEN_100] = colorFromStop(i02);
|
|
3789
3788
|
}
|
|
3790
3789
|
return out;
|
|
3791
3790
|
};
|
|
3792
|
-
const neutralGroup =
|
|
3791
|
+
const neutralGroup = splicePen100(p02 ? { [PAPER_0]: colorFromStop(p02), ...ramp } : ramp);
|
|
3793
3792
|
const secondaryGroup = rampGroup(secondary[mode], mode === "light" ? secondaryOnFillLight : secondaryOnFillDark, brandExtra(secondary, mode, CSS_FAMILY.brandSecondary));
|
|
3794
3793
|
if (input.secondaryStyle === "outline" && input.secondary) {
|
|
3795
3794
|
const s8 = secondary[mode].find((s) => s.stop === 8);
|
|
@@ -3827,12 +3826,12 @@ function themeToFigma(r, input) {
|
|
|
3827
3826
|
putLeaf(g3, STAMP_ON, colorFromHex(esc.onFillIsWhite));
|
|
3828
3827
|
}
|
|
3829
3828
|
}
|
|
3830
|
-
const
|
|
3829
|
+
const scaleTextAt = (n) => {
|
|
3831
3830
|
const s = scale[mode].find((x2) => x2.stop === n);
|
|
3832
|
-
if (!s) throw new Error(`themeToFigma link: the brand scale has no
|
|
3831
|
+
if (!s) throw new Error(`themeToFigma link: the brand scale has no pen stop ${n}`);
|
|
3833
3832
|
return s;
|
|
3834
3833
|
};
|
|
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(
|
|
3834
|
+
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
3835
|
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
3836
|
const g2 = {
|
|
3838
3837
|
brand: brandGroup,
|
|
@@ -3948,11 +3947,11 @@ export {
|
|
|
3948
3947
|
DEFAULT_LINK_HEX,
|
|
3949
3948
|
DISABLED_OPACITY,
|
|
3950
3949
|
EXT_KEY,
|
|
3951
|
-
INK_0,
|
|
3952
3950
|
MODE_SPECS,
|
|
3953
3951
|
OFFSET_ALPHAS,
|
|
3954
3952
|
OUTLINE_HOVER_ALPHA,
|
|
3955
|
-
|
|
3953
|
+
PAPER_0,
|
|
3954
|
+
PEN_100,
|
|
3956
3955
|
RESOLVER_ID,
|
|
3957
3956
|
SCALE_STOP_COUNT,
|
|
3958
3957
|
SCRIM_ALPHA,
|