okchroma 0.4.0 → 0.5.1
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 +3 -3
- package/dist-lib/index.cjs +91 -58
- package/dist-lib/index.mjs +91 -58
- package/dist-lib/types/engine/collision.d.ts +3 -3
- package/dist-lib/types/engine/colorEngine.d.ts +1 -1
- package/dist-lib/types/engine/cssRender.d.ts +16 -2
- package/dist-lib/types/engine/figmaRender.d.ts +8 -3
- package/dist-lib/types/engine/requirements/spec.d.ts +2 -2
- package/dist-lib/types/engine/tokenNames.d.ts +1 -1
- package/dist-lib/types/index.d.ts +2 -2
- package/package.json +1 -1
- package/tokens/semantic.css +59 -58
package/README.md
CHANGED
|
@@ -5,13 +5,13 @@ light and dark token system whose contrast requirements are solved during genera
|
|
|
5
5
|
then emits it as CSS custom properties or Figma variables.
|
|
6
6
|
|
|
7
7
|
Every family (neutral, brand, brand-alt, critical, warning, positive, info) carries the
|
|
8
|
-
same scale: 3 papers, 4
|
|
8
|
+
same scale: 3 papers, 4 chalks, 1 highlighter, 1 pencil, 2 pens, plus the stamp (the
|
|
9
9
|
solid fill with its hover and pressed states, its edge, and its text). The neutral adds
|
|
10
10
|
the two poles. Light and dark resolve together and ship on the same names, so a token is
|
|
11
11
|
mapped once and holds for any brand.
|
|
12
12
|
|
|
13
|
-
Contrast is built into the math, not checked afterwards:
|
|
14
|
-
3:1 (the non-text bar), pencil at 4.5:1, pen at 4.5:1 on every paper and
|
|
13
|
+
Contrast is built into the math, not checked afterwards: highlighter reads on every paper at
|
|
14
|
+
3:1 (the non-text bar), pencil at 4.5:1, pen at 4.5:1 on every paper and chalk in
|
|
15
15
|
both directions; the stamp's text passes 4.5:1 on its fill. APCA is used once, as a booster
|
|
16
16
|
that nudges the stamp fill until its text reads at Lc 65. Each claim, its scope, and the audit that
|
|
17
17
|
proves it: [Guarantees](https://egerrity.github.io/okchroma/#/docs/guarantees).
|
package/dist-lib/index.cjs
CHANGED
|
@@ -24,15 +24,16 @@ __export(index_exports, {
|
|
|
24
24
|
DEFAULT_LINK_HEX: () => DEFAULT_LINK_HEX,
|
|
25
25
|
DISABLED_OPACITY: () => DISABLED_OPACITY,
|
|
26
26
|
EXT_KEY: () => EXT_KEY,
|
|
27
|
+
INTERACTION_RUNGS: () => INTERACTION_RUNGS,
|
|
27
28
|
MODE_SPECS: () => MODE_SPECS,
|
|
28
29
|
OFFSET_ALPHAS: () => OFFSET_ALPHAS,
|
|
30
|
+
OPACITY_RUNGS: () => OPACITY_RUNGS,
|
|
29
31
|
OUTLINE_HOVER_ALPHA: () => OUTLINE_HOVER_ALPHA,
|
|
30
32
|
PAPER_0: () => PAPER_0,
|
|
31
33
|
PEN_100: () => PEN_100,
|
|
32
34
|
RESOLVER_ID: () => RESOLVER_ID,
|
|
33
35
|
SCALE_STOP_COUNT: () => SCALE_STOP_COUNT,
|
|
34
36
|
SCRIM_ALPHA: () => SCRIM_ALPHA,
|
|
35
|
-
SCRIM_VAR: () => SCRIM_VAR,
|
|
36
37
|
SECONDARY_DISTINCT_DELTA_E: () => SECONDARY_DISTINCT_DELTA_E,
|
|
37
38
|
SHADOW_ALPHAS: () => SHADOW_ALPHAS,
|
|
38
39
|
SIGNALS: () => SIGNALS2,
|
|
@@ -61,6 +62,9 @@ __export(index_exports, {
|
|
|
61
62
|
generateSubtleSecondary: () => generateSubtleSecondary,
|
|
62
63
|
neutralCss: () => neutralCss,
|
|
63
64
|
neutralTintHue: () => neutralTintHue,
|
|
65
|
+
opacityLeafName: () => opacityLeafName,
|
|
66
|
+
opacityTokenPath: () => opacityTokenPath,
|
|
67
|
+
opacityVarName: () => opacityVarName,
|
|
64
68
|
pageStopFor: () => pageStopFor,
|
|
65
69
|
parseToken: () => parseToken,
|
|
66
70
|
resolveBrand: () => resolveBrand,
|
|
@@ -399,7 +403,7 @@ var NEUTRAL_TINT_POINTS = {
|
|
|
399
403
|
[0.95, 0.895],
|
|
400
404
|
// paper-3 ─┘
|
|
401
405
|
[0.924, 1],
|
|
402
|
-
// wash-4 ─┐ peak: the mid-
|
|
406
|
+
// wash-4 ─┐ peak: the mid-chalk band, the neutral's most-branded moment
|
|
403
407
|
[0.892, 1],
|
|
404
408
|
// wash-5 ─┘
|
|
405
409
|
[0.852, 0.842],
|
|
@@ -407,7 +411,7 @@ var NEUTRAL_TINT_POINTS = {
|
|
|
407
411
|
[0.801, 0.653],
|
|
408
412
|
// wash-7 │
|
|
409
413
|
[0.738, 0.505],
|
|
410
|
-
//
|
|
414
|
+
// highlighter-26
|
|
411
415
|
[0.6, 0.4],
|
|
412
416
|
// knee (ex-scaffold) the drop-off: hue fades as you climb
|
|
413
417
|
[0.56, 0.347],
|
|
@@ -433,7 +437,7 @@ var NEUTRAL_TINT_POINTS = {
|
|
|
433
437
|
[0.42, 0.653],
|
|
434
438
|
// wash-7
|
|
435
439
|
[0.55, 0.505],
|
|
436
|
-
//
|
|
440
|
+
// highlighter-26
|
|
437
441
|
[0.6, 0.4],
|
|
438
442
|
// knee (ex-scaffold)
|
|
439
443
|
[0.64, 0.347],
|
|
@@ -808,18 +812,18 @@ var SHARED_NAMES = {
|
|
|
808
812
|
2: "paper-3",
|
|
809
813
|
// stop 3 renamed wash-3 → paper-3 (owner 2026-07-24, elevation round): it is a
|
|
810
814
|
// PLANE in both themes (light sink / dark pop), so it belongs to the surface band.
|
|
811
|
-
// NAME ONLY — generation is index-keyed and unchanged; the
|
|
812
|
-
// (collision.ts
|
|
815
|
+
// NAME ONLY — generation is index-keyed and unchanged; the chalk-collision machinery
|
|
816
|
+
// (collision.ts CHALK_STOPS) deliberately still spans stops 3–7. (The requirement
|
|
813
817
|
// spec's `group` label followed the band to 'paper' on 2026-08-31 — labels only,
|
|
814
818
|
// the spans never moved; see spec.ts groupOf.)
|
|
815
819
|
// Stage B (owner 2026-08-07, names only): relabeled to the ROOT_L-derived digit,
|
|
816
820
|
// paper-5 — same band, same index.
|
|
817
821
|
3: "paper-5",
|
|
818
|
-
4: "
|
|
819
|
-
5: "
|
|
820
|
-
6: "
|
|
821
|
-
7: "
|
|
822
|
-
8: "
|
|
822
|
+
4: "chalk-8",
|
|
823
|
+
5: "chalk-11",
|
|
824
|
+
6: "chalk-15",
|
|
825
|
+
7: "chalk-20",
|
|
826
|
+
8: "highlighter-26",
|
|
823
827
|
// THE PEN BAND (C49, owner 2026-08-05): the first text stop + emphasis fill (the
|
|
824
828
|
// 2026-07-29 highlight collapse — the old highlight-9 and on-highlight are
|
|
825
829
|
// deleted; this stop carries both jobs, its on-color a paper token), the between
|
|
@@ -871,13 +875,12 @@ var SYSTEM_LEAF = {
|
|
|
871
875
|
},
|
|
872
876
|
ALPHA: {
|
|
873
877
|
TRANSPARENT: "system/alpha/transparent",
|
|
874
|
-
SCRIM: "system/alpha/abs-black-060",
|
|
875
878
|
INK: "system/alpha/ink",
|
|
876
879
|
// the DIRECTIONAL ladders (owner 2026-08-31, renaming the 2026-08-18 bare rungs
|
|
877
880
|
// and the 2026-08-29 inverse ladder): away-from-bg is the page-polarity pole
|
|
878
881
|
// (black in light, white in dark), toward-bg the flipped pole — STATE LAYERS for
|
|
879
882
|
// INVERTED grounds, whose polarity is opposite the page's. (Never call toward-bg
|
|
880
|
-
// a "
|
|
883
|
+
// a "chalk": that word belongs to the tinted band stops.) The reversal lives in
|
|
881
884
|
// the engine: a consumer on an inverted ground names one token and never picks a
|
|
882
885
|
// pole. Colored fills never ride these; their states stay engine-solved stops.
|
|
883
886
|
// Rungs are two-digit percent, harmonizing with shadow-04/08/12.
|
|
@@ -890,6 +893,19 @@ var SYSTEM_LEAF = {
|
|
|
890
893
|
SHADOW_04: "system/alpha/shadow-04",
|
|
891
894
|
SHADOW_08: "system/alpha/shadow-08",
|
|
892
895
|
SHADOW_12: "system/alpha/shadow-12"
|
|
896
|
+
},
|
|
897
|
+
// the OPACITY LADDER's rows: bare numbers, never colors. `alpha` above is the group
|
|
898
|
+
// of colors that carry transparency; `opacity` is the numbers they compose with
|
|
899
|
+
// (values in cssRender OPACITY_RUNGS; the ext plugin zones these under utility/).
|
|
900
|
+
OPACITY: {
|
|
901
|
+
"004": "system/opacity/004",
|
|
902
|
+
"008": "system/opacity/008",
|
|
903
|
+
"012": "system/opacity/012",
|
|
904
|
+
"016": "system/opacity/016",
|
|
905
|
+
"024": "system/opacity/024",
|
|
906
|
+
"032": "system/opacity/032",
|
|
907
|
+
"048": "system/opacity/048",
|
|
908
|
+
"064": "system/opacity/064"
|
|
893
909
|
}
|
|
894
910
|
};
|
|
895
911
|
var SURFACE_PLANE_LAW = {
|
|
@@ -922,14 +938,14 @@ var P_FIXED = { hue: "warm-torsion", L: "fixed", chroma: "ladder" };
|
|
|
922
938
|
var P_TEXT = { hue: "warm-torsion", L: "perceptual", chroma: "brand" };
|
|
923
939
|
var S8 = { metric: "wcag", against: "paper-5", target: STOP_8_NONTEXT_CONTRAST, level: "AA" };
|
|
924
940
|
var T9 = { metric: "wcag", against: "paper-5", target: PENCIL_9_CONTRAST, level: "AA" };
|
|
925
|
-
var T10 = { metric: "wcag", against: "
|
|
941
|
+
var T10 = { metric: "wcag", against: "chalk-20", target: PEN_10_CONTRAST, level: "AA" };
|
|
926
942
|
var T11 = { metric: "wcag", against: "paper-5", target: PEN_11_CONTRAST_FLOOR, level: "AAA" };
|
|
927
943
|
var ONS = { onFill: { metric: "apca-pole", enforce: true, ratioFloor: 4.5, coEnforceLc: 65 } };
|
|
928
944
|
var LIGHT = {
|
|
929
945
|
stops: [
|
|
930
946
|
// paper-0 (paper-0 pre-Stage-B): the resolved ladder extreme — in light it genuinely is white (rootL 1.0, zero chroma)
|
|
931
947
|
{ 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 },
|
|
932
|
-
// paper/highlighter
|
|
948
|
+
// paper/chalk/highlighter-26: perceptual ladder/envelope blend on the geometric ROOT_L_LIGHT scaffold. Separation
|
|
933
949
|
// falls out of the shape (see above) — no min-separation require. Only stop 8 carries a require: the WCAG
|
|
934
950
|
// 3:1 vs the resolved paper-3 (paper-2) (re-solves automatically since it references paper-3 (paper-2)).
|
|
935
951
|
...[1, 2, 3, 4, 5, 6, 7, 8].map((stop) => ({
|
|
@@ -960,7 +976,7 @@ var DARK = {
|
|
|
960
976
|
// paper-0 (paper-0 pre-Stage-B): the resolved ladder extreme — one seam BELOW paper-1 (paper-1), deep and brand-tinted, never the
|
|
961
977
|
// absolute void (the old hard-coded #000000 was "too much"). Lift applies like the rest of the scale.
|
|
962
978
|
{ stop: 0, rootL: PAPER0_DARK_ROOT_L, group: groupOf(0), produce: P_LIFT, satFraction: SCALE_C_DARK[0].sat },
|
|
963
|
-
// paper/
|
|
979
|
+
// paper/chalk 1–7: perceptual on the dark scaffold. stop 8: FIXED at the hand-placed scaffold BUT with the
|
|
964
980
|
// 3:1 non-text require DECLARED (the Stage-5 flip, owner-approved) — the blue-recede failure is prevented
|
|
965
981
|
// BY RULE, not by patch. The require now genuinely PLACES the stop rather than catching a few low-luminance
|
|
966
982
|
// hues: on the delta-carry path it solves from the sentinel every time, and the old claim that "most hues
|
|
@@ -1905,7 +1921,7 @@ function p2Diff(a, b) {
|
|
|
1905
1921
|
var DEFAULT_APCA_LC_MAP = { 3: 30, 4.5: 75, 6.5: 85, 7: 90 };
|
|
1906
1922
|
function toApca(req, lcMap) {
|
|
1907
1923
|
if (req.metric !== "wcag") return req;
|
|
1908
|
-
if (req.against === "
|
|
1924
|
+
if (req.against === "chalk-20") return { metric: "apca", against: "paper-3", targetLc: 85 };
|
|
1909
1925
|
const targetLc = lcMap[req.target];
|
|
1910
1926
|
if (targetLc === void 0) throw new Error(`apca profile: no Lc mapping for wcag target ${req.target}`);
|
|
1911
1927
|
return { metric: "apca", against: req.against, targetLc };
|
|
@@ -2391,7 +2407,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2391
2407
|
if (!ref) throw new Error(`stop ${forWhom}: require against stop ${stopNum} but it is not resolved yet`);
|
|
2392
2408
|
return ref;
|
|
2393
2409
|
};
|
|
2394
|
-
const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "
|
|
2410
|
+
const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "chalk-20": 7 };
|
|
2395
2411
|
const declaredAnchor = (req) => req.metric === "min-separation" ? 1 : AGAINST_STOP[req.against] ?? 2;
|
|
2396
2412
|
const wcagAnchorStop = (req, stop) => stop >= 9 && declaredAnchor(req) <= 3 ? 3 : declaredAnchor(req);
|
|
2397
2413
|
const textGround = ctx.opts?.textGround?.[mode];
|
|
@@ -2505,13 +2521,13 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2505
2521
|
const own = contrastRatio(y2, anchorShipY);
|
|
2506
2522
|
return paperBound ? Math.min(own, contrastRatio(y2, crossBoundY)) : own;
|
|
2507
2523
|
};
|
|
2508
|
-
const
|
|
2509
|
-
const
|
|
2524
|
+
const chalkBoundY = ctx.opts?.crossChalkBoundY && sp.stop >= 10 && !textGround ? ctx.opts.crossChalkBoundY[mode] : void 0;
|
|
2525
|
+
const chalkRatio = (L) => chalkBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), chalkBoundY);
|
|
2510
2526
|
const paperBoundY = ctx.opts?.crossPaperBoundY && sp.stop >= 8 && !textGround ? ctx.opts.crossPaperBoundY[mode] : void 0;
|
|
2511
2527
|
const paperBar = sp.stop === 8 ? STOP_8_NONTEXT_CONTRAST : sp.stop === 9 ? PENCIL_9_CONTRAST : PEN_10_CONTRAST;
|
|
2512
2528
|
const paperRatio = (L) => paperBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), paperBoundY);
|
|
2513
2529
|
const target = sp.require.target;
|
|
2514
|
-
const clears = (L) => shipRatio(L) >= target &&
|
|
2530
|
+
const clears = (L) => shipRatio(L) >= target && chalkRatio(L) >= PEN_10_CONTRAST && paperRatio(L) >= paperBar;
|
|
2515
2531
|
if (!clears(placed.L)) {
|
|
2516
2532
|
const away = shippedY(placed.L, placed.C, placed.H) < anchorShipY ? -1 : 1;
|
|
2517
2533
|
let L2 = placed.L;
|
|
@@ -2527,7 +2543,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2527
2543
|
shipRatio(placed.L)
|
|
2528
2544
|
);
|
|
2529
2545
|
if (got < sp.require.target - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${got.toFixed(2)} < required ${sp.require.target}`;
|
|
2530
|
-
else if (
|
|
2546
|
+
else if (chalkRatio(placed.L) < PEN_10_CONTRAST - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${chalkRatio(placed.L).toFixed(2)} < required ${PEN_10_CONTRAST} on the worst chromatic chalk-20`;
|
|
2531
2547
|
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`;
|
|
2532
2548
|
} else if (sp.require?.metric === "apca") {
|
|
2533
2549
|
const ga = apcaGroundOf(sp.require, sp.stop);
|
|
@@ -2735,9 +2751,9 @@ function generateNeutralScale(brandH, level = "default", contrastProfile) {
|
|
|
2735
2751
|
chromaCurve: curve,
|
|
2736
2752
|
enforceOnFillContrast: true,
|
|
2737
2753
|
contrastProfile,
|
|
2738
|
-
// T13: the neutral's pens clear every chromatic family's
|
|
2754
|
+
// T13: the neutral's pens clear every chromatic family's chalk-20 (the symmetric
|
|
2739
2755
|
// pen guarantee); the chromatic ramps clear the neutral's through NEUTRAL_W80_WORST_SHIP_Y
|
|
2740
|
-
|
|
2756
|
+
crossChalkBoundY: CHROMATIC_W80_WORST_SHIP_Y,
|
|
2741
2757
|
crossPaperBoundY: CHROMATIC_P3_WORST_SHIP_Y
|
|
2742
2758
|
});
|
|
2743
2759
|
const asCta = (stop, src) => makeStop(stop, src.L, src.C, src.H);
|
|
@@ -2834,7 +2850,7 @@ var COLOR_WORD = {
|
|
|
2834
2850
|
[FAMILY.info]: " (blue)"
|
|
2835
2851
|
};
|
|
2836
2852
|
var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
|
|
2837
|
-
var
|
|
2853
|
+
var CHALK = { req: "decorative borders, inverted text, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
|
|
2838
2854
|
var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
|
|
2839
2855
|
var OVERLAY = {
|
|
2840
2856
|
req: "translucent backgrounds that hold their reading on any paper",
|
|
@@ -2848,13 +2864,13 @@ var SCALE = {
|
|
|
2848
2864
|
"paper-97-overlay": OVERLAY,
|
|
2849
2865
|
"paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
|
|
2850
2866
|
"paper-95-overlay": OVERLAY,
|
|
2851
|
-
"
|
|
2852
|
-
"
|
|
2853
|
-
"
|
|
2854
|
-
"
|
|
2867
|
+
"chalk-8": CHALK,
|
|
2868
|
+
"chalk-11": CHALK,
|
|
2869
|
+
"chalk-15": CHALK,
|
|
2870
|
+
"chalk-20": CHALK,
|
|
2855
2871
|
// ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
|
|
2856
|
-
// landing on the
|
|
2857
|
-
"
|
|
2872
|
+
// landing on the highlighter rows is worth its ic-ON-s noise — the one surviving body carrier)
|
|
2873
|
+
"highlighter-26": { req: "focus rings, icons, large text, translucent state layers over any ground", contrast: AA_LARGE, theming: solved, collides: true },
|
|
2858
2874
|
"pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2859
2875
|
"pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2860
2876
|
// ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
|
|
@@ -2875,6 +2891,8 @@ var ABS = { req: "extreme poles for CTAs, aliased global endpoints" };
|
|
|
2875
2891
|
var OFFSET = { req: "min APCA visibility", theming: "offsets CTAs in themes where they sit close to the page" };
|
|
2876
2892
|
var OFFSET_INVERSE = { req: "state layers over inverted backgrounds", theming: "the offset ladder with its pole flipped, so inverted grounds keep the same rungs" };
|
|
2877
2893
|
var SHADOW = { req: "drop shadows" };
|
|
2894
|
+
var OPACITY_RUNG = { req: "translucency weight for any color", theming: "a bare number; a translucent row composes its color with one rung" };
|
|
2895
|
+
var OPACITY_STATE = { req: "translucency weight for any color; the state layer weight of the non-text stop over any ground", theming: "a bare number; a translucent row composes its color with one rung" };
|
|
2878
2896
|
var PLANE = (req) => ({ req, theming: "aliased to the gray ramp" });
|
|
2879
2897
|
var LINK = (state, contrast) => ({
|
|
2880
2898
|
req: "links" + state,
|
|
@@ -2896,8 +2914,17 @@ var SYSTEM = {
|
|
|
2896
2914
|
// ("companion" reworded 2026-08-28: compani-ON fed the "on" flood)
|
|
2897
2915
|
"system/abs-alt": { req: "identity seed reference", theming: "the theme\u2019s paired input, as given" },
|
|
2898
2916
|
"system/alpha/transparent": { req: "aliased off-states" },
|
|
2899
|
-
//
|
|
2900
|
-
|
|
2917
|
+
// the OPACITY LADDER: bare numbers a translucent color row composes with (the
|
|
2918
|
+
// scrim is the absolute black at the top rung). No conformance line of its own:
|
|
2919
|
+
// the state-layer claim rides the non-text stop these weights apply to
|
|
2920
|
+
"system/opacity/004": OPACITY_RUNG,
|
|
2921
|
+
"system/opacity/008": OPACITY_STATE,
|
|
2922
|
+
"system/opacity/012": OPACITY_STATE,
|
|
2923
|
+
"system/opacity/016": OPACITY_STATE,
|
|
2924
|
+
"system/opacity/024": OPACITY_STATE,
|
|
2925
|
+
"system/opacity/032": OPACITY_STATE,
|
|
2926
|
+
"system/opacity/048": OPACITY_RUNG,
|
|
2927
|
+
"system/opacity/064": OPACITY_RUNG,
|
|
2901
2928
|
// ("fills" reworded 2026-08-18: fill became a label word and is foreign here;
|
|
2902
2929
|
// "on-color" reworded 2026-08-28: the bare on fed the "on" flood)
|
|
2903
2930
|
"system/alpha/ink": { req: "soft text pole for quiet CTAs" },
|
|
@@ -2905,7 +2932,7 @@ var SYSTEM = {
|
|
|
2905
2932
|
"system/alpha/away-from-bg/08": OFFSET,
|
|
2906
2933
|
"system/alpha/away-from-bg/16": OFFSET,
|
|
2907
2934
|
// the inverse ladder: the offset rungs with the pole flipped per mode, for
|
|
2908
|
-
//
|
|
2935
|
+
// chalks and edges over inverted backgrounds ("interaction" avoided in the
|
|
2909
2936
|
// bodies: acti-ON would feed the "on" flood)
|
|
2910
2937
|
"system/alpha/toward-bg/06": OFFSET_INVERSE,
|
|
2911
2938
|
"system/alpha/toward-bg/08": OFFSET_INVERSE,
|
|
@@ -2972,15 +2999,15 @@ function checkCollision(brand, signalScale, signalDef, mode) {
|
|
|
2972
2999
|
collides: hueGate && deltaE <= threshold
|
|
2973
3000
|
};
|
|
2974
3001
|
}
|
|
2975
|
-
var
|
|
3002
|
+
var HUE_COLLISION_CHALK_DEG = 15;
|
|
2976
3003
|
var HUE_COLLISION_MIN_V = 0.5;
|
|
2977
3004
|
var SECONDARY_NOTE_MIN_V = HUE_NOISE_C / VIVID_C;
|
|
2978
|
-
var
|
|
2979
|
-
function
|
|
3005
|
+
var CHALK_STOPS = [3, 4, 5, 6, 7];
|
|
3006
|
+
function chalkProximity(brand, sig, mode) {
|
|
2980
3007
|
const b = mode === "light" ? brand.light : brand.dark;
|
|
2981
3008
|
const s = mode === "light" ? sig.light : sig.dark;
|
|
2982
3009
|
let dH = Infinity, dE = Infinity;
|
|
2983
|
-
for (const k of
|
|
3010
|
+
for (const k of CHALK_STOPS) {
|
|
2984
3011
|
const bs = b.find((x2) => x2.stop === k), ss = s.find((x2) => x2.stop === k);
|
|
2985
3012
|
if (!bs || !ss) continue;
|
|
2986
3013
|
dH = Math.min(dH, hueDistance(bs.H, ss.H));
|
|
@@ -2989,15 +3016,15 @@ function highlighterProximity(brand, sig, mode) {
|
|
|
2989
3016
|
return { dH, dE };
|
|
2990
3017
|
}
|
|
2991
3018
|
function checkHueCollision(brand, signalScale, signalDef, opts) {
|
|
2992
|
-
const l =
|
|
2993
|
-
const d =
|
|
3019
|
+
const l = chalkProximity(brand, signalScale, "light");
|
|
3020
|
+
const d = chalkProximity(brand, signalScale, "dark");
|
|
2994
3021
|
const vividness = Math.min(1, brand.brandC / VIVID_C);
|
|
2995
3022
|
return {
|
|
2996
3023
|
signal: signalDef.name,
|
|
2997
|
-
|
|
2998
|
-
|
|
3024
|
+
dHueChalk: { light: l.dH, dark: d.dH },
|
|
3025
|
+
chalkDeltaE: { light: l.dE, dark: d.dE },
|
|
2999
3026
|
vividness,
|
|
3000
|
-
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <=
|
|
3027
|
+
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_CHALK_DEG
|
|
3001
3028
|
};
|
|
3002
3029
|
}
|
|
3003
3030
|
var YELLOW_SPLIT_H = 96;
|
|
@@ -3390,7 +3417,7 @@ function resolveTheme(input) {
|
|
|
3390
3417
|
for (const def of SIGNALS2) {
|
|
3391
3418
|
if (adoptedForSecondary.has(def.name)) continue;
|
|
3392
3419
|
const h = checkHueCollision(scale2, effectiveOf(def.name), def, { minV: SECONDARY_NOTE_MIN_V });
|
|
3393
|
-
if (h.collides) out.push(wording(def.name, Math.min(h.
|
|
3420
|
+
if (h.collides) out.push(wording(def.name, Math.min(h.chalkDeltaE.light, h.chalkDeltaE.dark)));
|
|
3394
3421
|
}
|
|
3395
3422
|
return out;
|
|
3396
3423
|
};
|
|
@@ -3440,7 +3467,7 @@ function resolveTheme(input) {
|
|
|
3440
3467
|
derived: true,
|
|
3441
3468
|
notes: [
|
|
3442
3469
|
`secondary derived from the brand color (default model, seed ${liftedHex})`,
|
|
3443
|
-
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (
|
|
3470
|
+
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (chalk \u0394E ${dE.toFixed(3)}) \u2014 it tracks the brand color`)
|
|
3444
3471
|
],
|
|
3445
3472
|
distinctness: ctaDistinctness(primary.scale, scale2)
|
|
3446
3473
|
},
|
|
@@ -3465,7 +3492,7 @@ function resolveTheme(input) {
|
|
|
3465
3492
|
derived: false,
|
|
3466
3493
|
notes: [
|
|
3467
3494
|
`secondary keeps your color through the ramp; the cta is a tint of it (tint seed ${tintedHex})`,
|
|
3468
|
-
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (
|
|
3495
|
+
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (chalk \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
|
|
3469
3496
|
],
|
|
3470
3497
|
distinctness: distinctness2
|
|
3471
3498
|
},
|
|
@@ -3477,7 +3504,7 @@ function resolveTheme(input) {
|
|
|
3477
3504
|
const scale = rSec.scale;
|
|
3478
3505
|
const level = "standard";
|
|
3479
3506
|
mergeSecondarySignals(scale, input.secondaryHex);
|
|
3480
|
-
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (
|
|
3507
|
+
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (chalk \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
|
|
3481
3508
|
const distinctness = ctaDistinctness(primary.scale, scale);
|
|
3482
3509
|
if (distinctness.close)
|
|
3483
3510
|
notes.push(`secondary reads close to the primary (\u0394E ${Math.min(distinctness.light, distinctness.dark).toFixed(2)}) \u2014 consider a more distinct color`);
|
|
@@ -3522,24 +3549,29 @@ var OFFSET_ALPHAS = { 6: 0.06, 8: 0.08, 16: 0.16 };
|
|
|
3522
3549
|
var ctaBorderRung = (prefix) => prefix === CSS_NEUTRAL ? 8 : prefix === CSS_SECONDARY ? 6 : 16;
|
|
3523
3550
|
var offsetLeafName = (rung) => String(rung).padStart(2, "0");
|
|
3524
3551
|
var offsetVarName = (rung) => `--alpha-away-from-bg-${offsetLeafName(rung)}`;
|
|
3552
|
+
var OPACITY_RUNGS = { 4: 0.04, 8: 0.08, 12: 0.12, 16: 0.16, 24: 0.24, 32: 0.32, 48: 0.48, 64: 0.64 };
|
|
3553
|
+
var INTERACTION_RUNGS = [8, 12, 16, 24, 32];
|
|
3554
|
+
var opacityLeafName = (rung) => String(rung).padStart(3, "0");
|
|
3555
|
+
var opacityVarName = (rung) => `--opacity-${opacityLeafName(rung)}`;
|
|
3556
|
+
var opacityTokenPath = (rung) => SYSTEM_LEAF.OPACITY[opacityLeafName(rung)];
|
|
3525
3557
|
var SHADOW_ALPHAS = {
|
|
3526
|
-
4: { light:
|
|
3527
|
-
8: { light:
|
|
3528
|
-
12: { light:
|
|
3558
|
+
4: { light: OPACITY_RUNGS[4], dark: OPACITY_RUNGS[32] },
|
|
3559
|
+
8: { light: OPACITY_RUNGS[8], dark: OPACITY_RUNGS[48] },
|
|
3560
|
+
12: { light: OPACITY_RUNGS[12], dark: OPACITY_RUNGS[64] }
|
|
3529
3561
|
};
|
|
3530
|
-
var SCRIM_ALPHA =
|
|
3562
|
+
var SCRIM_ALPHA = OPACITY_RUNGS[64];
|
|
3531
3563
|
var DISABLED_OPACITY = 0.38;
|
|
3532
3564
|
var TRANSPARENT_VAR = "--alpha-transparent";
|
|
3533
|
-
var SCRIM_VAR = "--abs-black-060";
|
|
3534
3565
|
var offsetRgba = (rung, mode) => mode === "light" ? `rgba(0, 0, 0, ${OFFSET_ALPHAS[rung]})` : `rgba(255, 255, 255, ${OFFSET_ALPHAS[rung]})`;
|
|
3535
3566
|
var alphaRootVars = (mode) => [
|
|
3536
3567
|
` ${TRANSPARENT_VAR}: transparent;`,
|
|
3537
|
-
// mode-invariant like transparent; repeated in the dark block to
|
|
3538
|
-
|
|
3568
|
+
// the opacity ladder, mode-invariant like transparent; repeated in the dark block to
|
|
3569
|
+
// keep the set together
|
|
3570
|
+
...Object.keys(OPACITY_RUNGS).map(Number).map((r) => ` ${opacityVarName(r)}: ${OPACITY_RUNGS[r]};`),
|
|
3539
3571
|
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`),
|
|
3540
3572
|
// the toward-bg ladder (the 2026-08-29 inverse ladder, renamed 2026-08-31): the
|
|
3541
3573
|
// same rungs, pole flipped per mode — state layers for INVERTED grounds (never
|
|
3542
|
-
// called a "
|
|
3574
|
+
// called a "chalk" — that word is the tinted band's). One spelling with the Figma
|
|
3543
3575
|
// row: --alpha-toward-bg-06 ↔ system/alpha/toward-bg/06.
|
|
3544
3576
|
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` --alpha-toward-bg-${offsetLeafName(r)}: ${offsetRgba(r, mode === "light" ? "dark" : "light")};`)
|
|
3545
3577
|
];
|
|
@@ -3566,7 +3598,7 @@ function brandKindBody(prefix, s, mode, page, on = prefix === CSS_NEUTRAL ? "sof
|
|
|
3566
3598
|
` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
|
|
3567
3599
|
` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
|
|
3568
3600
|
// the SOFT on-cta (owner 2026-08-04: "neutral cta on's should also be the alpha"): the
|
|
3569
|
-
// neutral's cta is the scale-fed
|
|
3601
|
+
// neutral's cta is the scale-fed CHALK-level fill, the system's other quiet cta, so its
|
|
3570
3602
|
// button text takes the pole AT ALPHA like the default-model secondary's — composited
|
|
3571
3603
|
// over whatever state the fill is in, so hover/pressed carry their own legibility. Same
|
|
3572
3604
|
// register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
|
|
@@ -3640,7 +3672,7 @@ function signalsCss(contrastProfile) {
|
|
|
3640
3672
|
// the system alpha variables every family's cta-border aliases (owner 2026-07-29) — this is
|
|
3641
3673
|
// the engine's one global :root, the CSS counterpart of the Figma side's system/alpha/* rows.
|
|
3642
3674
|
// Emitted in BOTH blocks because the offset rungs are scheme-DIVERGENT (black in light,
|
|
3643
|
-
// white in dark); --alpha-transparent and the
|
|
3675
|
+
// white in dark); --alpha-transparent and the opacity ladder (mode-invariant) repeat
|
|
3644
3676
|
// harmlessly and keep the set together.
|
|
3645
3677
|
...alphaRootVars("light"),
|
|
3646
3678
|
...lightBlocks,
|
|
@@ -3962,7 +3994,8 @@ function themeToFigma(r, input) {
|
|
|
3962
3994
|
if (tok && "$type" in tok) putLeaf(g2, path, tok);
|
|
3963
3995
|
}
|
|
3964
3996
|
putLeaf(g2, SYSTEM_LEAF.ALPHA.TRANSPARENT, pole(true, 0));
|
|
3965
|
-
|
|
3997
|
+
for (const rung of Object.keys(OPACITY_RUNGS).map(Number))
|
|
3998
|
+
putLeaf(g2, opacityTokenPath(rung), { $type: "number", $value: rung });
|
|
3966
3999
|
putLeaf(g2, SYSTEM_LEAF.ALPHA.INK, pole(mode === "dark", SOFT_ON_CTA_ALPHA[mode]));
|
|
3967
4000
|
putLeaf(g2, SYSTEM_LEAF.ALPHA.AWAY_FROM_BG_06, OFFSET_TOKEN(6, mode));
|
|
3968
4001
|
putLeaf(g2, SYSTEM_LEAF.ALPHA.AWAY_FROM_BG_08, OFFSET_TOKEN(8, mode));
|
package/dist-lib/index.mjs
CHANGED
|
@@ -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-chalk 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
|
+
// highlighter-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
|
+
// highlighter-26
|
|
356
356
|
[0.6, 0.4],
|
|
357
357
|
// knee (ex-scaffold)
|
|
358
358
|
[0.64, 0.347],
|
|
@@ -727,18 +727,18 @@ var SHARED_NAMES = {
|
|
|
727
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 chalk-collision machinery
|
|
731
|
+
// (collision.ts CHALK_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
735
|
// paper-5 — same band, same index.
|
|
736
736
|
3: "paper-5",
|
|
737
|
-
4: "
|
|
738
|
-
5: "
|
|
739
|
-
6: "
|
|
740
|
-
7: "
|
|
741
|
-
8: "
|
|
737
|
+
4: "chalk-8",
|
|
738
|
+
5: "chalk-11",
|
|
739
|
+
6: "chalk-15",
|
|
740
|
+
7: "chalk-20",
|
|
741
|
+
8: "highlighter-26",
|
|
742
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
|
|
@@ -790,13 +790,12 @@ var SYSTEM_LEAF = {
|
|
|
790
790
|
},
|
|
791
791
|
ALPHA: {
|
|
792
792
|
TRANSPARENT: "system/alpha/transparent",
|
|
793
|
-
SCRIM: "system/alpha/abs-black-060",
|
|
794
793
|
INK: "system/alpha/ink",
|
|
795
794
|
// the DIRECTIONAL ladders (owner 2026-08-31, renaming the 2026-08-18 bare rungs
|
|
796
795
|
// and the 2026-08-29 inverse ladder): away-from-bg is the page-polarity pole
|
|
797
796
|
// (black in light, white in dark), toward-bg the flipped pole — STATE LAYERS for
|
|
798
797
|
// INVERTED grounds, whose polarity is opposite the page's. (Never call toward-bg
|
|
799
|
-
// a "
|
|
798
|
+
// a "chalk": that word belongs to the tinted band stops.) The reversal lives in
|
|
800
799
|
// the engine: a consumer on an inverted ground names one token and never picks a
|
|
801
800
|
// pole. Colored fills never ride these; their states stay engine-solved stops.
|
|
802
801
|
// Rungs are two-digit percent, harmonizing with shadow-04/08/12.
|
|
@@ -809,6 +808,19 @@ var SYSTEM_LEAF = {
|
|
|
809
808
|
SHADOW_04: "system/alpha/shadow-04",
|
|
810
809
|
SHADOW_08: "system/alpha/shadow-08",
|
|
811
810
|
SHADOW_12: "system/alpha/shadow-12"
|
|
811
|
+
},
|
|
812
|
+
// the OPACITY LADDER's rows: bare numbers, never colors. `alpha` above is the group
|
|
813
|
+
// of colors that carry transparency; `opacity` is the numbers they compose with
|
|
814
|
+
// (values in cssRender OPACITY_RUNGS; the ext plugin zones these under utility/).
|
|
815
|
+
OPACITY: {
|
|
816
|
+
"004": "system/opacity/004",
|
|
817
|
+
"008": "system/opacity/008",
|
|
818
|
+
"012": "system/opacity/012",
|
|
819
|
+
"016": "system/opacity/016",
|
|
820
|
+
"024": "system/opacity/024",
|
|
821
|
+
"032": "system/opacity/032",
|
|
822
|
+
"048": "system/opacity/048",
|
|
823
|
+
"064": "system/opacity/064"
|
|
812
824
|
}
|
|
813
825
|
};
|
|
814
826
|
var SURFACE_PLANE_LAW = {
|
|
@@ -841,14 +853,14 @@ var P_FIXED = { hue: "warm-torsion", L: "fixed", chroma: "ladder" };
|
|
|
841
853
|
var P_TEXT = { hue: "warm-torsion", L: "perceptual", chroma: "brand" };
|
|
842
854
|
var S8 = { metric: "wcag", against: "paper-5", target: STOP_8_NONTEXT_CONTRAST, level: "AA" };
|
|
843
855
|
var T9 = { metric: "wcag", against: "paper-5", target: PENCIL_9_CONTRAST, level: "AA" };
|
|
844
|
-
var T10 = { metric: "wcag", against: "
|
|
856
|
+
var T10 = { metric: "wcag", against: "chalk-20", target: PEN_10_CONTRAST, level: "AA" };
|
|
845
857
|
var T11 = { metric: "wcag", against: "paper-5", target: PEN_11_CONTRAST_FLOOR, level: "AAA" };
|
|
846
858
|
var ONS = { onFill: { metric: "apca-pole", enforce: true, ratioFloor: 4.5, coEnforceLc: 65 } };
|
|
847
859
|
var LIGHT = {
|
|
848
860
|
stops: [
|
|
849
861
|
// paper-0 (paper-0 pre-Stage-B): the resolved ladder extreme — in light it genuinely is white (rootL 1.0, zero chroma)
|
|
850
862
|
{ 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/highlighter
|
|
863
|
+
// paper/chalk/highlighter-26: perceptual ladder/envelope blend on the geometric ROOT_L_LIGHT scaffold. Separation
|
|
852
864
|
// falls out of the shape (see above) — no min-separation require. Only stop 8 carries a require: the WCAG
|
|
853
865
|
// 3:1 vs the resolved paper-3 (paper-2) (re-solves automatically since it references paper-3 (paper-2)).
|
|
854
866
|
...[1, 2, 3, 4, 5, 6, 7, 8].map((stop) => ({
|
|
@@ -879,7 +891,7 @@ var DARK = {
|
|
|
879
891
|
// 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
892
|
// absolute void (the old hard-coded #000000 was "too much"). Lift applies like the rest of the scale.
|
|
881
893
|
{ stop: 0, rootL: PAPER0_DARK_ROOT_L, group: groupOf(0), produce: P_LIFT, satFraction: SCALE_C_DARK[0].sat },
|
|
882
|
-
// paper/
|
|
894
|
+
// paper/chalk 1–7: perceptual on the dark scaffold. stop 8: FIXED at the hand-placed scaffold BUT with the
|
|
883
895
|
// 3:1 non-text require DECLARED (the Stage-5 flip, owner-approved) — the blue-recede failure is prevented
|
|
884
896
|
// BY RULE, not by patch. The require now genuinely PLACES the stop rather than catching a few low-luminance
|
|
885
897
|
// hues: on the delta-carry path it solves from the sentinel every time, and the old claim that "most hues
|
|
@@ -1824,7 +1836,7 @@ function p2Diff(a, b) {
|
|
|
1824
1836
|
var DEFAULT_APCA_LC_MAP = { 3: 30, 4.5: 75, 6.5: 85, 7: 90 };
|
|
1825
1837
|
function toApca(req, lcMap) {
|
|
1826
1838
|
if (req.metric !== "wcag") return req;
|
|
1827
|
-
if (req.against === "
|
|
1839
|
+
if (req.against === "chalk-20") return { metric: "apca", against: "paper-3", targetLc: 85 };
|
|
1828
1840
|
const targetLc = lcMap[req.target];
|
|
1829
1841
|
if (targetLc === void 0) throw new Error(`apca profile: no Lc mapping for wcag target ${req.target}`);
|
|
1830
1842
|
return { metric: "apca", against: req.against, targetLc };
|
|
@@ -2310,7 +2322,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2310
2322
|
if (!ref) throw new Error(`stop ${forWhom}: require against stop ${stopNum} but it is not resolved yet`);
|
|
2311
2323
|
return ref;
|
|
2312
2324
|
};
|
|
2313
|
-
const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "
|
|
2325
|
+
const AGAINST_STOP = { "paper-1": 1, "paper-3": 2, "paper-5": 3, "chalk-20": 7 };
|
|
2314
2326
|
const declaredAnchor = (req) => req.metric === "min-separation" ? 1 : AGAINST_STOP[req.against] ?? 2;
|
|
2315
2327
|
const wcagAnchorStop = (req, stop) => stop >= 9 && declaredAnchor(req) <= 3 ? 3 : declaredAnchor(req);
|
|
2316
2328
|
const textGround = ctx.opts?.textGround?.[mode];
|
|
@@ -2424,13 +2436,13 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2424
2436
|
const own = contrastRatio(y2, anchorShipY);
|
|
2425
2437
|
return paperBound ? Math.min(own, contrastRatio(y2, crossBoundY)) : own;
|
|
2426
2438
|
};
|
|
2427
|
-
const
|
|
2428
|
-
const
|
|
2439
|
+
const chalkBoundY = ctx.opts?.crossChalkBoundY && sp.stop >= 10 && !textGround ? ctx.opts.crossChalkBoundY[mode] : void 0;
|
|
2440
|
+
const chalkRatio = (L) => chalkBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), chalkBoundY);
|
|
2429
2441
|
const paperBoundY = ctx.opts?.crossPaperBoundY && sp.stop >= 8 && !textGround ? ctx.opts.crossPaperBoundY[mode] : void 0;
|
|
2430
2442
|
const paperBar = sp.stop === 8 ? STOP_8_NONTEXT_CONTRAST : sp.stop === 9 ? PENCIL_9_CONTRAST : PEN_10_CONTRAST;
|
|
2431
2443
|
const paperRatio = (L) => paperBoundY === void 0 ? Infinity : contrastRatio(shippedY(L, placed.C, placed.H), paperBoundY);
|
|
2432
2444
|
const target = sp.require.target;
|
|
2433
|
-
const clears = (L) => shipRatio(L) >= target &&
|
|
2445
|
+
const clears = (L) => shipRatio(L) >= target && chalkRatio(L) >= PEN_10_CONTRAST && paperRatio(L) >= paperBar;
|
|
2434
2446
|
if (!clears(placed.L)) {
|
|
2435
2447
|
const away = shippedY(placed.L, placed.C, placed.H) < anchorShipY ? -1 : 1;
|
|
2436
2448
|
let L2 = placed.L;
|
|
@@ -2446,7 +2458,7 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2446
2458
|
shipRatio(placed.L)
|
|
2447
2459
|
);
|
|
2448
2460
|
if (got < sp.require.target - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${got.toFixed(2)} < required ${sp.require.target}`;
|
|
2449
|
-
else if (
|
|
2461
|
+
else if (chalkRatio(placed.L) < PEN_10_CONTRAST - 1e-3) unresolvable = `stop ${sp.stop}: contrast ${chalkRatio(placed.L).toFixed(2)} < required ${PEN_10_CONTRAST} on the worst chromatic chalk-20`;
|
|
2450
2462
|
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`;
|
|
2451
2463
|
} else if (sp.require?.metric === "apca") {
|
|
2452
2464
|
const ga = apcaGroundOf(sp.require, sp.stop);
|
|
@@ -2654,9 +2666,9 @@ function generateNeutralScale(brandH, level = "default", contrastProfile) {
|
|
|
2654
2666
|
chromaCurve: curve,
|
|
2655
2667
|
enforceOnFillContrast: true,
|
|
2656
2668
|
contrastProfile,
|
|
2657
|
-
// T13: the neutral's pens clear every chromatic family's
|
|
2669
|
+
// T13: the neutral's pens clear every chromatic family's chalk-20 (the symmetric
|
|
2658
2670
|
// pen guarantee); the chromatic ramps clear the neutral's through NEUTRAL_W80_WORST_SHIP_Y
|
|
2659
|
-
|
|
2671
|
+
crossChalkBoundY: CHROMATIC_W80_WORST_SHIP_Y,
|
|
2660
2672
|
crossPaperBoundY: CHROMATIC_P3_WORST_SHIP_Y
|
|
2661
2673
|
});
|
|
2662
2674
|
const asCta = (stop, src) => makeStop(stop, src.L, src.C, src.H);
|
|
@@ -2753,7 +2765,7 @@ var COLOR_WORD = {
|
|
|
2753
2765
|
[FAMILY.info]: " (blue)"
|
|
2754
2766
|
};
|
|
2755
2767
|
var PAPER = { req: "backgrounds, inverted text", theming: (f) => TINT[f], collides: true };
|
|
2756
|
-
var
|
|
2768
|
+
var CHALK = { req: "decorative borders, inverted text, illos, signal hierarchy", theming: (f) => TINT[f], collides: true };
|
|
2757
2769
|
var solved = (f) => `${TINT[f]}; re-solved to clear its floor`;
|
|
2758
2770
|
var OVERLAY = {
|
|
2759
2771
|
req: "translucent backgrounds that hold their reading on any paper",
|
|
@@ -2767,13 +2779,13 @@ var SCALE = {
|
|
|
2767
2779
|
"paper-97-overlay": OVERLAY,
|
|
2768
2780
|
"paper-5": { req: "backgrounds, inverted text", theming: (f) => `${TINT[f]}. Worst background text stops must clear.`, collides: true },
|
|
2769
2781
|
"paper-95-overlay": OVERLAY,
|
|
2770
|
-
"
|
|
2771
|
-
"
|
|
2772
|
-
"
|
|
2773
|
-
"
|
|
2782
|
+
"chalk-8": CHALK,
|
|
2783
|
+
"chalk-11": CHALK,
|
|
2784
|
+
"chalk-15": CHALK,
|
|
2785
|
+
"chalk-20": CHALK,
|
|
2774
2786
|
// ("icons" KEPT through the 2026-08-28 on-flood strip (owner): an "icon" query
|
|
2775
|
-
// landing on the
|
|
2776
|
-
"
|
|
2787
|
+
// landing on the highlighter rows is worth its ic-ON-s noise — the one surviving body carrier)
|
|
2788
|
+
"highlighter-26": { req: "focus rings, icons, large text, translucent state layers over any ground", contrast: AA_LARGE, theming: solved, collides: true },
|
|
2777
2789
|
"pencil-47": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2778
2790
|
"pen-58": { req: "regular text, inverted backgrounds", contrast: AA_BODY, theming: solved, collides: true },
|
|
2779
2791
|
// ("high-emphasis" reworded 2026-08-12: the surface planes took low/high as label
|
|
@@ -2794,6 +2806,8 @@ var ABS = { req: "extreme poles for CTAs, aliased global endpoints" };
|
|
|
2794
2806
|
var OFFSET = { req: "min APCA visibility", theming: "offsets CTAs in themes where they sit close to the page" };
|
|
2795
2807
|
var OFFSET_INVERSE = { req: "state layers over inverted backgrounds", theming: "the offset ladder with its pole flipped, so inverted grounds keep the same rungs" };
|
|
2796
2808
|
var SHADOW = { req: "drop shadows" };
|
|
2809
|
+
var OPACITY_RUNG = { req: "translucency weight for any color", theming: "a bare number; a translucent row composes its color with one rung" };
|
|
2810
|
+
var OPACITY_STATE = { req: "translucency weight for any color; the state layer weight of the non-text stop over any ground", theming: "a bare number; a translucent row composes its color with one rung" };
|
|
2797
2811
|
var PLANE = (req) => ({ req, theming: "aliased to the gray ramp" });
|
|
2798
2812
|
var LINK = (state, contrast) => ({
|
|
2799
2813
|
req: "links" + state,
|
|
@@ -2815,8 +2829,17 @@ var SYSTEM = {
|
|
|
2815
2829
|
// ("companion" reworded 2026-08-28: compani-ON fed the "on" flood)
|
|
2816
2830
|
"system/abs-alt": { req: "identity seed reference", theming: "the theme\u2019s paired input, as given" },
|
|
2817
2831
|
"system/alpha/transparent": { req: "aliased off-states" },
|
|
2818
|
-
//
|
|
2819
|
-
|
|
2832
|
+
// the OPACITY LADDER: bare numbers a translucent color row composes with (the
|
|
2833
|
+
// scrim is the absolute black at the top rung). No conformance line of its own:
|
|
2834
|
+
// the state-layer claim rides the non-text stop these weights apply to
|
|
2835
|
+
"system/opacity/004": OPACITY_RUNG,
|
|
2836
|
+
"system/opacity/008": OPACITY_STATE,
|
|
2837
|
+
"system/opacity/012": OPACITY_STATE,
|
|
2838
|
+
"system/opacity/016": OPACITY_STATE,
|
|
2839
|
+
"system/opacity/024": OPACITY_STATE,
|
|
2840
|
+
"system/opacity/032": OPACITY_STATE,
|
|
2841
|
+
"system/opacity/048": OPACITY_RUNG,
|
|
2842
|
+
"system/opacity/064": OPACITY_RUNG,
|
|
2820
2843
|
// ("fills" reworded 2026-08-18: fill became a label word and is foreign here;
|
|
2821
2844
|
// "on-color" reworded 2026-08-28: the bare on fed the "on" flood)
|
|
2822
2845
|
"system/alpha/ink": { req: "soft text pole for quiet CTAs" },
|
|
@@ -2824,7 +2847,7 @@ var SYSTEM = {
|
|
|
2824
2847
|
"system/alpha/away-from-bg/08": OFFSET,
|
|
2825
2848
|
"system/alpha/away-from-bg/16": OFFSET,
|
|
2826
2849
|
// the inverse ladder: the offset rungs with the pole flipped per mode, for
|
|
2827
|
-
//
|
|
2850
|
+
// chalks and edges over inverted backgrounds ("interaction" avoided in the
|
|
2828
2851
|
// bodies: acti-ON would feed the "on" flood)
|
|
2829
2852
|
"system/alpha/toward-bg/06": OFFSET_INVERSE,
|
|
2830
2853
|
"system/alpha/toward-bg/08": OFFSET_INVERSE,
|
|
@@ -2891,15 +2914,15 @@ function checkCollision(brand, signalScale, signalDef, mode) {
|
|
|
2891
2914
|
collides: hueGate && deltaE <= threshold
|
|
2892
2915
|
};
|
|
2893
2916
|
}
|
|
2894
|
-
var
|
|
2917
|
+
var HUE_COLLISION_CHALK_DEG = 15;
|
|
2895
2918
|
var HUE_COLLISION_MIN_V = 0.5;
|
|
2896
2919
|
var SECONDARY_NOTE_MIN_V = HUE_NOISE_C / VIVID_C;
|
|
2897
|
-
var
|
|
2898
|
-
function
|
|
2920
|
+
var CHALK_STOPS = [3, 4, 5, 6, 7];
|
|
2921
|
+
function chalkProximity(brand, sig, mode) {
|
|
2899
2922
|
const b = mode === "light" ? brand.light : brand.dark;
|
|
2900
2923
|
const s = mode === "light" ? sig.light : sig.dark;
|
|
2901
2924
|
let dH = Infinity, dE = Infinity;
|
|
2902
|
-
for (const k of
|
|
2925
|
+
for (const k of CHALK_STOPS) {
|
|
2903
2926
|
const bs = b.find((x2) => x2.stop === k), ss = s.find((x2) => x2.stop === k);
|
|
2904
2927
|
if (!bs || !ss) continue;
|
|
2905
2928
|
dH = Math.min(dH, hueDistance(bs.H, ss.H));
|
|
@@ -2908,15 +2931,15 @@ function highlighterProximity(brand, sig, mode) {
|
|
|
2908
2931
|
return { dH, dE };
|
|
2909
2932
|
}
|
|
2910
2933
|
function checkHueCollision(brand, signalScale, signalDef, opts) {
|
|
2911
|
-
const l =
|
|
2912
|
-
const d =
|
|
2934
|
+
const l = chalkProximity(brand, signalScale, "light");
|
|
2935
|
+
const d = chalkProximity(brand, signalScale, "dark");
|
|
2913
2936
|
const vividness = Math.min(1, brand.brandC / VIVID_C);
|
|
2914
2937
|
return {
|
|
2915
2938
|
signal: signalDef.name,
|
|
2916
|
-
|
|
2917
|
-
|
|
2939
|
+
dHueChalk: { light: l.dH, dark: d.dH },
|
|
2940
|
+
chalkDeltaE: { light: l.dE, dark: d.dE },
|
|
2918
2941
|
vividness,
|
|
2919
|
-
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <=
|
|
2942
|
+
collides: vividness >= (opts?.minV ?? HUE_COLLISION_MIN_V) && Math.min(l.dH, d.dH) <= HUE_COLLISION_CHALK_DEG
|
|
2920
2943
|
};
|
|
2921
2944
|
}
|
|
2922
2945
|
var YELLOW_SPLIT_H = 96;
|
|
@@ -3309,7 +3332,7 @@ function resolveTheme(input) {
|
|
|
3309
3332
|
for (const def of SIGNALS2) {
|
|
3310
3333
|
if (adoptedForSecondary.has(def.name)) continue;
|
|
3311
3334
|
const h = checkHueCollision(scale2, effectiveOf(def.name), def, { minV: SECONDARY_NOTE_MIN_V });
|
|
3312
|
-
if (h.collides) out.push(wording(def.name, Math.min(h.
|
|
3335
|
+
if (h.collides) out.push(wording(def.name, Math.min(h.chalkDeltaE.light, h.chalkDeltaE.dark)));
|
|
3313
3336
|
}
|
|
3314
3337
|
return out;
|
|
3315
3338
|
};
|
|
@@ -3359,7 +3382,7 @@ function resolveTheme(input) {
|
|
|
3359
3382
|
derived: true,
|
|
3360
3383
|
notes: [
|
|
3361
3384
|
`secondary derived from the brand color (default model, seed ${liftedHex})`,
|
|
3362
|
-
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (
|
|
3385
|
+
...signalNotesFor(scale2, (name, dE) => `derived secondary sits on the ${name} signal's hue (chalk \u0394E ${dE.toFixed(3)}) \u2014 it tracks the brand color`)
|
|
3363
3386
|
],
|
|
3364
3387
|
distinctness: ctaDistinctness(primary.scale, scale2)
|
|
3365
3388
|
},
|
|
@@ -3384,7 +3407,7 @@ function resolveTheme(input) {
|
|
|
3384
3407
|
derived: false,
|
|
3385
3408
|
notes: [
|
|
3386
3409
|
`secondary keeps your color through the ramp; the cta is a tint of it (tint seed ${tintedHex})`,
|
|
3387
|
-
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (
|
|
3410
|
+
...signalNotesFor(scale2, (name, dE) => `secondary sits on the ${name} signal's hue (chalk \u0394E ${dE.toFixed(3)}) \u2014 it tracks your color`)
|
|
3388
3411
|
],
|
|
3389
3412
|
distinctness: distinctness2
|
|
3390
3413
|
},
|
|
@@ -3396,7 +3419,7 @@ function resolveTheme(input) {
|
|
|
3396
3419
|
const scale = rSec.scale;
|
|
3397
3420
|
const level = "standard";
|
|
3398
3421
|
mergeSecondarySignals(scale, input.secondaryHex);
|
|
3399
|
-
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (
|
|
3422
|
+
const secNotes = signalNotesFor(scale, (name, dE) => `secondary reads close to the ${name} signal (chalk \u0394E ${dE.toFixed(3)}) \u2014 your color ships untouched`);
|
|
3400
3423
|
const distinctness = ctaDistinctness(primary.scale, scale);
|
|
3401
3424
|
if (distinctness.close)
|
|
3402
3425
|
notes.push(`secondary reads close to the primary (\u0394E ${Math.min(distinctness.light, distinctness.dark).toFixed(2)}) \u2014 consider a more distinct color`);
|
|
@@ -3441,24 +3464,29 @@ var OFFSET_ALPHAS = { 6: 0.06, 8: 0.08, 16: 0.16 };
|
|
|
3441
3464
|
var ctaBorderRung = (prefix) => prefix === CSS_NEUTRAL ? 8 : prefix === CSS_SECONDARY ? 6 : 16;
|
|
3442
3465
|
var offsetLeafName = (rung) => String(rung).padStart(2, "0");
|
|
3443
3466
|
var offsetVarName = (rung) => `--alpha-away-from-bg-${offsetLeafName(rung)}`;
|
|
3467
|
+
var OPACITY_RUNGS = { 4: 0.04, 8: 0.08, 12: 0.12, 16: 0.16, 24: 0.24, 32: 0.32, 48: 0.48, 64: 0.64 };
|
|
3468
|
+
var INTERACTION_RUNGS = [8, 12, 16, 24, 32];
|
|
3469
|
+
var opacityLeafName = (rung) => String(rung).padStart(3, "0");
|
|
3470
|
+
var opacityVarName = (rung) => `--opacity-${opacityLeafName(rung)}`;
|
|
3471
|
+
var opacityTokenPath = (rung) => SYSTEM_LEAF.OPACITY[opacityLeafName(rung)];
|
|
3444
3472
|
var SHADOW_ALPHAS = {
|
|
3445
|
-
4: { light:
|
|
3446
|
-
8: { light:
|
|
3447
|
-
12: { light:
|
|
3473
|
+
4: { light: OPACITY_RUNGS[4], dark: OPACITY_RUNGS[32] },
|
|
3474
|
+
8: { light: OPACITY_RUNGS[8], dark: OPACITY_RUNGS[48] },
|
|
3475
|
+
12: { light: OPACITY_RUNGS[12], dark: OPACITY_RUNGS[64] }
|
|
3448
3476
|
};
|
|
3449
|
-
var SCRIM_ALPHA =
|
|
3477
|
+
var SCRIM_ALPHA = OPACITY_RUNGS[64];
|
|
3450
3478
|
var DISABLED_OPACITY = 0.38;
|
|
3451
3479
|
var TRANSPARENT_VAR = "--alpha-transparent";
|
|
3452
|
-
var SCRIM_VAR = "--abs-black-060";
|
|
3453
3480
|
var offsetRgba = (rung, mode) => mode === "light" ? `rgba(0, 0, 0, ${OFFSET_ALPHAS[rung]})` : `rgba(255, 255, 255, ${OFFSET_ALPHAS[rung]})`;
|
|
3454
3481
|
var alphaRootVars = (mode) => [
|
|
3455
3482
|
` ${TRANSPARENT_VAR}: transparent;`,
|
|
3456
|
-
// mode-invariant like transparent; repeated in the dark block to
|
|
3457
|
-
|
|
3483
|
+
// the opacity ladder, mode-invariant like transparent; repeated in the dark block to
|
|
3484
|
+
// keep the set together
|
|
3485
|
+
...Object.keys(OPACITY_RUNGS).map(Number).map((r) => ` ${opacityVarName(r)}: ${OPACITY_RUNGS[r]};`),
|
|
3458
3486
|
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` ${offsetVarName(r)}: ${offsetRgba(r, mode)};`),
|
|
3459
3487
|
// the toward-bg ladder (the 2026-08-29 inverse ladder, renamed 2026-08-31): the
|
|
3460
3488
|
// same rungs, pole flipped per mode — state layers for INVERTED grounds (never
|
|
3461
|
-
// called a "
|
|
3489
|
+
// called a "chalk" — that word is the tinted band's). One spelling with the Figma
|
|
3462
3490
|
// row: --alpha-toward-bg-06 ↔ system/alpha/toward-bg/06.
|
|
3463
3491
|
...Object.keys(OFFSET_ALPHAS).map(Number).sort((a, b) => a - b).map((r) => ` --alpha-toward-bg-${offsetLeafName(r)}: ${offsetRgba(r, mode === "light" ? "dark" : "light")};`)
|
|
3464
3492
|
];
|
|
@@ -3485,7 +3513,7 @@ function brandKindBody(prefix, s, mode, page, on = prefix === CSS_NEUTRAL ? "sof
|
|
|
3485
3513
|
` --${prefix}-stamp-fill-pressed: ${stopHex(f.ctaPressed)};`,
|
|
3486
3514
|
` --${prefix}-stamp-edge: var(${border ? offsetVarName(ctaBorderRung(prefix)) : TRANSPARENT_VAR});`,
|
|
3487
3515
|
// the SOFT on-cta (owner 2026-08-04: "neutral cta on's should also be the alpha"): the
|
|
3488
|
-
// neutral's cta is the scale-fed
|
|
3516
|
+
// neutral's cta is the scale-fed CHALK-level fill, the system's other quiet cta, so its
|
|
3489
3517
|
// button text takes the pole AT ALPHA like the default-model secondary's — composited
|
|
3490
3518
|
// over whatever state the fill is in, so hover/pressed carry their own legibility. Same
|
|
3491
3519
|
// register both families (SOFT_ON_CTA_ALPHA), so both alias the ONE system/alpha/ink
|
|
@@ -3559,7 +3587,7 @@ function signalsCss(contrastProfile) {
|
|
|
3559
3587
|
// the system alpha variables every family's cta-border aliases (owner 2026-07-29) — this is
|
|
3560
3588
|
// the engine's one global :root, the CSS counterpart of the Figma side's system/alpha/* rows.
|
|
3561
3589
|
// Emitted in BOTH blocks because the offset rungs are scheme-DIVERGENT (black in light,
|
|
3562
|
-
// white in dark); --alpha-transparent and the
|
|
3590
|
+
// white in dark); --alpha-transparent and the opacity ladder (mode-invariant) repeat
|
|
3563
3591
|
// harmlessly and keep the set together.
|
|
3564
3592
|
...alphaRootVars("light"),
|
|
3565
3593
|
...lightBlocks,
|
|
@@ -3881,7 +3909,8 @@ function themeToFigma(r, input) {
|
|
|
3881
3909
|
if (tok && "$type" in tok) putLeaf(g2, path, tok);
|
|
3882
3910
|
}
|
|
3883
3911
|
putLeaf(g2, SYSTEM_LEAF.ALPHA.TRANSPARENT, pole(true, 0));
|
|
3884
|
-
|
|
3912
|
+
for (const rung of Object.keys(OPACITY_RUNGS).map(Number))
|
|
3913
|
+
putLeaf(g2, opacityTokenPath(rung), { $type: "number", $value: rung });
|
|
3885
3914
|
putLeaf(g2, SYSTEM_LEAF.ALPHA.INK, pole(mode === "dark", SOFT_ON_CTA_ALPHA[mode]));
|
|
3886
3915
|
putLeaf(g2, SYSTEM_LEAF.ALPHA.AWAY_FROM_BG_06, OFFSET_TOKEN(6, mode));
|
|
3887
3916
|
putLeaf(g2, SYSTEM_LEAF.ALPHA.AWAY_FROM_BG_08, OFFSET_TOKEN(8, mode));
|
|
@@ -3971,15 +4000,16 @@ export {
|
|
|
3971
4000
|
DEFAULT_LINK_HEX,
|
|
3972
4001
|
DISABLED_OPACITY,
|
|
3973
4002
|
EXT_KEY,
|
|
4003
|
+
INTERACTION_RUNGS,
|
|
3974
4004
|
MODE_SPECS,
|
|
3975
4005
|
OFFSET_ALPHAS,
|
|
4006
|
+
OPACITY_RUNGS,
|
|
3976
4007
|
OUTLINE_HOVER_ALPHA,
|
|
3977
4008
|
PAPER_0,
|
|
3978
4009
|
PEN_100,
|
|
3979
4010
|
RESOLVER_ID,
|
|
3980
4011
|
SCALE_STOP_COUNT,
|
|
3981
4012
|
SCRIM_ALPHA,
|
|
3982
|
-
SCRIM_VAR,
|
|
3983
4013
|
SECONDARY_DISTINCT_DELTA_E,
|
|
3984
4014
|
SHADOW_ALPHAS,
|
|
3985
4015
|
SIGNALS2 as SIGNALS,
|
|
@@ -4008,6 +4038,9 @@ export {
|
|
|
4008
4038
|
generateSubtleSecondary,
|
|
4009
4039
|
neutralCss,
|
|
4010
4040
|
neutralTintHue,
|
|
4041
|
+
opacityLeafName,
|
|
4042
|
+
opacityTokenPath,
|
|
4043
|
+
opacityVarName,
|
|
4011
4044
|
pageStopFor,
|
|
4012
4045
|
parseToken,
|
|
4013
4046
|
resolveBrand,
|
|
@@ -15,16 +15,16 @@ export interface CollisionCheck {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function stopDeltaE(s1: ColorStop, s2: ColorStop): number;
|
|
17
17
|
export declare function checkCollision(brand: GeneratedScale, signalScale: GeneratedScale, signalDef: SignalDef, mode: Mode): CollisionCheck;
|
|
18
|
-
export declare const
|
|
18
|
+
export declare const HUE_COLLISION_CHALK_DEG = 15;
|
|
19
19
|
export declare const HUE_COLLISION_MIN_V = 0.5;
|
|
20
20
|
export declare const SECONDARY_NOTE_MIN_V: number;
|
|
21
21
|
export interface HueCollisionCheck {
|
|
22
22
|
signal: SignalDef['name'];
|
|
23
|
-
|
|
23
|
+
dHueChalk: {
|
|
24
24
|
light: number;
|
|
25
25
|
dark: number;
|
|
26
26
|
};
|
|
27
|
-
|
|
27
|
+
chalkDeltaE: {
|
|
28
28
|
light: number;
|
|
29
29
|
dark: number;
|
|
30
30
|
};
|
|
@@ -17,6 +17,21 @@ export declare const ctaBorderRung: (prefix: string) => OffsetRung;
|
|
|
17
17
|
export declare const offsetLeafName: (rung: OffsetRung) => string;
|
|
18
18
|
export declare const offsetVarName: (rung: OffsetRung) => string;
|
|
19
19
|
export declare const offsetTokenPath: (rung: OffsetRung) => string;
|
|
20
|
+
export declare const OPACITY_RUNGS: {
|
|
21
|
+
readonly 4: 0.04;
|
|
22
|
+
readonly 8: 0.08;
|
|
23
|
+
readonly 12: 0.12;
|
|
24
|
+
readonly 16: 0.16;
|
|
25
|
+
readonly 24: 0.24;
|
|
26
|
+
readonly 32: 0.32;
|
|
27
|
+
readonly 48: 0.48;
|
|
28
|
+
readonly 64: 0.64;
|
|
29
|
+
};
|
|
30
|
+
export type OpacityRung = keyof typeof OPACITY_RUNGS;
|
|
31
|
+
export declare const INTERACTION_RUNGS: readonly OpacityRung[];
|
|
32
|
+
export declare const opacityLeafName: (rung: OpacityRung) => string;
|
|
33
|
+
export declare const opacityVarName: (rung: OpacityRung) => string;
|
|
34
|
+
export declare const opacityTokenPath: (rung: OpacityRung) => string;
|
|
20
35
|
export declare const SHADOW_ALPHAS: {
|
|
21
36
|
readonly 4: {
|
|
22
37
|
readonly light: 0.04;
|
|
@@ -32,10 +47,9 @@ export declare const SHADOW_ALPHAS: {
|
|
|
32
47
|
};
|
|
33
48
|
};
|
|
34
49
|
export type ShadowRung = keyof typeof SHADOW_ALPHAS;
|
|
35
|
-
export declare const SCRIM_ALPHA
|
|
50
|
+
export declare const SCRIM_ALPHA: 0.64;
|
|
36
51
|
export declare const DISABLED_OPACITY = 0.38;
|
|
37
52
|
export declare const TRANSPARENT_VAR = "--alpha-transparent";
|
|
38
|
-
export declare const SCRIM_VAR = "--abs-black-060";
|
|
39
53
|
export declare const offsetRgba: (rung: OffsetRung, mode: "light" | "dark") => string;
|
|
40
54
|
export declare const alphaRootVars: (mode: "light" | "dark") => string[];
|
|
41
55
|
export declare function ctaPageLc(s: GeneratedScale, mode: 'light' | 'dark', page: ColorStop): number;
|
|
@@ -9,11 +9,16 @@ export interface FigmaColorToken {
|
|
|
9
9
|
hex: string;
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
+
export interface FigmaNumberToken {
|
|
13
|
+
$type: 'number';
|
|
14
|
+
$value: number;
|
|
15
|
+
}
|
|
16
|
+
export type FigmaLeaf = FigmaColorToken | FigmaNumberToken;
|
|
12
17
|
export type FigmaGroup = {
|
|
13
|
-
[key: string]:
|
|
18
|
+
[key: string]: FigmaLeaf | FigmaGroup;
|
|
14
19
|
};
|
|
15
|
-
export declare function groupEntries(g: FigmaGroup): Array<[string,
|
|
16
|
-
export declare function putLeaf(g: FigmaGroup, flat: string, tok:
|
|
20
|
+
export declare function groupEntries(g: FigmaGroup): Array<[string, FigmaLeaf | FigmaGroup]>;
|
|
21
|
+
export declare function putLeaf(g: FigmaGroup, flat: string, tok: FigmaLeaf): void;
|
|
17
22
|
export interface ThemeInput {
|
|
18
23
|
secondary?: GeneratedScale | null;
|
|
19
24
|
secondaryStyle?: SecondaryStyle;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED } from '../tokenNames';
|
|
2
|
-
export type Group = 'paper' | '
|
|
2
|
+
export type Group = 'paper' | 'chalk' | 'highlighter' | 'pencil' | 'pen';
|
|
3
3
|
export declare const textLane: (g: string) => boolean;
|
|
4
4
|
export type Producer = {
|
|
5
5
|
hue: 'warm-drift' | 'warm-torsion' | 'constant';
|
|
@@ -8,7 +8,7 @@ export type Producer = {
|
|
|
8
8
|
};
|
|
9
9
|
export type Require = {
|
|
10
10
|
metric: 'wcag';
|
|
11
|
-
against: 'paper-1' | 'paper-3' | 'paper-5' | '
|
|
11
|
+
against: 'paper-1' | 'paper-3' | 'paper-5' | 'chalk-20';
|
|
12
12
|
target: number;
|
|
13
13
|
level: 'AA' | 'AAA';
|
|
14
14
|
} | {
|
|
@@ -26,7 +26,6 @@ export declare const SYSTEM_LEAF: {
|
|
|
26
26
|
};
|
|
27
27
|
readonly ALPHA: {
|
|
28
28
|
readonly TRANSPARENT: "system/alpha/transparent";
|
|
29
|
-
readonly SCRIM: "system/alpha/abs-black-060";
|
|
30
29
|
readonly INK: "system/alpha/ink";
|
|
31
30
|
readonly AWAY_FROM_BG_06: "system/alpha/away-from-bg/06";
|
|
32
31
|
readonly AWAY_FROM_BG_08: "system/alpha/away-from-bg/08";
|
|
@@ -38,6 +37,7 @@ export declare const SYSTEM_LEAF: {
|
|
|
38
37
|
readonly SHADOW_08: "system/alpha/shadow-08";
|
|
39
38
|
readonly SHADOW_12: "system/alpha/shadow-12";
|
|
40
39
|
};
|
|
40
|
+
readonly OPACITY: Record<string, string>;
|
|
41
41
|
};
|
|
42
42
|
export declare const SURFACE_PLANE_LAW: Record<string, {
|
|
43
43
|
light: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { generateScale, generateNeutralScale, generateSubtleSecondary, neutralTintHue, type NeutralSource, type GeneratedScale, type ColorStop, type GenerateOptions, type NeutralLevel, type ContrastProfile, } from './engine/colorEngine';
|
|
2
2
|
export { resolveBrand, resolveTheme, resolveLinkTrio, resolveLinkInverseTrio, DEFAULT_LINK_HEX, SIGNAL_SCALES, signalScalesFor, SECONDARY_DISTINCT_DELTA_E, SUBTLE_TINT_MULT, SUBTLE_PASTEL_K, OUTLINE_HOVER_ALPHA, type ResolvedBrand, type ResolvedTheme, type ResolvedSecondary, type SecondaryLevel, type SecondaryStyle, type SignalOverride, } from './engine/resolve';
|
|
3
|
-
export { brandCss, neutralCss, signalsCss, stopsToVars, toHex, stopHex, OFFSET_ALPHAS, SHADOW_ALPHAS, SCRIM_ALPHA,
|
|
3
|
+
export { brandCss, neutralCss, signalsCss, stopsToVars, toHex, stopHex, OFFSET_ALPHAS, SHADOW_ALPHAS, SCRIM_ALPHA, OPACITY_RUNGS, INTERACTION_RUNGS, opacityLeafName, opacityVarName, opacityTokenPath, type OpacityRung, DISABLED_OPACITY, type ShadowRung, ctaNeedsBorder, ctaBorderRung, pageStopFor, } from './engine/cssRender';
|
|
4
4
|
export { CSS_FAMILY } from './engine/tokenDescriptions';
|
|
5
|
-
export { themeToFigma, type FigmaGroup, type FigmaColorToken, type ThemeInput, } from './engine/figmaRender';
|
|
5
|
+
export { themeToFigma, type FigmaGroup, type FigmaColorToken, type FigmaNumberToken, type FigmaLeaf, type ThemeInput, } from './engine/figmaRender';
|
|
6
6
|
export { emitDtcgRamp, resolveDtcgRamp, parseToken, EXT_KEY, RESOLVER_ID, type DtcgRampGroup, type DtcgRequirementToken, type DtcgSeedToken, type DtcgColorValue, } from './engine/requirements/dtcg';
|
|
7
7
|
export { MODE_SPECS, type ModeSpec } from './engine/requirements/spec';
|
|
8
8
|
export { stopTokenName, tokenOrder, SCALE_STOP_COUNT, PAPER_0, PEN_100, STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED, STAMP_EDGE, STAMP_ON, STAMP_STATE_LEAVES, SYSTEM_LEAF, SURFACE_PLANE_LAW, } from './engine/tokenNames';
|
package/package.json
CHANGED
package/tokens/semantic.css
CHANGED
|
@@ -22,25 +22,26 @@
|
|
|
22
22
|
--fg-subtle: var(--neutral-pencil-47);
|
|
23
23
|
|
|
24
24
|
/* --border-default IS THE ACCESSIBILITY BORDER: a border that has to be there (inputs
|
|
25
|
-
and other controls), so it rides
|
|
26
|
-
contrast requirement. Decorative borders are --border-subtle (
|
|
27
|
-
the per-family --*-border-default (
|
|
28
|
-
--border-default: var(--neutral-
|
|
29
|
-
--border-subtle: var(--neutral-
|
|
30
|
-
|
|
31
|
-
/* Shadow transparencies: pure black
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
--shadow-
|
|
38
|
-
--shadow-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
25
|
+
and other controls), so it rides highlighter-26, the stop that carries the 3:1 non-text
|
|
26
|
+
contrast requirement. Decorative borders are --border-subtle (chalk-11) and
|
|
27
|
+
the per-family --*-border-default (chalk-15). */
|
|
28
|
+
--border-default: var(--neutral-highlighter-26);
|
|
29
|
+
--border-subtle: var(--neutral-chalk-11);
|
|
30
|
+
|
|
31
|
+
/* Shadow transparencies: pure black at a rung of the engine's opacity ladder (the
|
|
32
|
+
--opacity-NNN numbers signalsCss writes at :root), the same rows the plugins write
|
|
33
|
+
as shadow-04|08|12. Dark is heavier by necessity: near black a light-mode weight
|
|
34
|
+
vanishes, so the dark block picks the heavier rungs for the same three roles.
|
|
35
|
+
Elevation recipes compose these, so dark shadows fall out of the token swap with
|
|
36
|
+
nothing bespoke per style. */
|
|
37
|
+
--shadow-04: rgb(0 0 0 / var(--opacity-004));
|
|
38
|
+
--shadow-08: rgb(0 0 0 / var(--opacity-008));
|
|
39
|
+
--shadow-12: rgb(0 0 0 / var(--opacity-012));
|
|
40
|
+
|
|
41
|
+
/* The scrim: the modal veil, the absolute black at the ladder's top rung in both
|
|
42
|
+
modes. Composed here from the number; a Figma kit composes the same thing as a
|
|
43
|
+
black alias at that opacity. There is no separate scrim color row. */
|
|
44
|
+
--scrim: rgb(0 0 0 / var(--opacity-064));
|
|
44
45
|
|
|
45
46
|
/* Disabled opacity: disabled is a COMPONENT-LEVEL opacity. Colors stay the enabled
|
|
46
47
|
ones and never swap. Mode-invariant; mirrors the JS API's DISABLED_OPACITY
|
|
@@ -59,9 +60,9 @@
|
|
|
59
60
|
--surface-mid: var(--neutral-paper-3);
|
|
60
61
|
--surface-high: var(--neutral-paper-5);
|
|
61
62
|
|
|
62
|
-
--shadow-04:
|
|
63
|
-
--shadow-08:
|
|
64
|
-
--shadow-12:
|
|
63
|
+
--shadow-04: rgb(0 0 0 / var(--opacity-032));
|
|
64
|
+
--shadow-08: rgb(0 0 0 / var(--opacity-048));
|
|
65
|
+
--shadow-12: rgb(0 0 0 / var(--opacity-064));
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/* ─── role.brand ────────────────────────────────────────────────────────────── */
|
|
@@ -74,16 +75,16 @@
|
|
|
74
75
|
--brand-fg-on-emphasis: var(--brand-stamp-on);
|
|
75
76
|
|
|
76
77
|
--brand-bg-faint: var(--brand-paper-3);
|
|
77
|
-
--brand-bg-subtle: var(--brand-
|
|
78
|
-
--brand-bg-subtle-hover: var(--brand-
|
|
78
|
+
--brand-bg-subtle: var(--brand-chalk-11);
|
|
79
|
+
--brand-bg-subtle-hover: var(--brand-chalk-15);
|
|
79
80
|
--brand-bg-emphasis: var(--brand-stamp-fill);
|
|
80
81
|
--brand-bg-emphasis-hover: var(--brand-stamp-fill-hover);
|
|
81
82
|
--brand-bg-emphasis-pressed: var(--brand-stamp-fill-pressed);
|
|
82
83
|
|
|
83
|
-
--brand-border-subtle: var(--brand-
|
|
84
|
-
--brand-border-subtle-hover: var(--brand-
|
|
85
|
-
--brand-border-default: var(--brand-
|
|
86
|
-
--brand-border-default-hover: var(--brand-
|
|
84
|
+
--brand-border-subtle: var(--brand-chalk-8);
|
|
85
|
+
--brand-border-subtle-hover: var(--brand-chalk-11);
|
|
86
|
+
--brand-border-default: var(--brand-chalk-15);
|
|
87
|
+
--brand-border-default-hover: var(--brand-highlighter-26);
|
|
87
88
|
--brand-border-emphasis: var(--brand-stamp-fill);
|
|
88
89
|
--brand-border-emphasis-hover: var(--brand-stamp-fill-hover);
|
|
89
90
|
|
|
@@ -111,16 +112,16 @@
|
|
|
111
112
|
--brand-alt-fg-on-emphasis: var(--brand-alt-stamp-on);
|
|
112
113
|
|
|
113
114
|
--brand-alt-bg-faint: var(--brand-alt-paper-3);
|
|
114
|
-
--brand-alt-bg-subtle: var(--brand-alt-
|
|
115
|
-
--brand-alt-bg-subtle-hover: var(--brand-alt-
|
|
115
|
+
--brand-alt-bg-subtle: var(--brand-alt-chalk-11);
|
|
116
|
+
--brand-alt-bg-subtle-hover: var(--brand-alt-chalk-15);
|
|
116
117
|
--brand-alt-bg-emphasis: var(--brand-alt-stamp-fill);
|
|
117
118
|
--brand-alt-bg-emphasis-hover: var(--brand-alt-stamp-fill-hover);
|
|
118
119
|
--brand-alt-bg-emphasis-pressed: var(--brand-alt-stamp-fill-pressed);
|
|
119
120
|
|
|
120
|
-
--brand-alt-border-subtle: var(--brand-alt-
|
|
121
|
-
--brand-alt-border-subtle-hover: var(--brand-alt-
|
|
122
|
-
--brand-alt-border-default: var(--brand-alt-
|
|
123
|
-
--brand-alt-border-default-hover: var(--brand-alt-
|
|
121
|
+
--brand-alt-border-subtle: var(--brand-alt-chalk-8);
|
|
122
|
+
--brand-alt-border-subtle-hover: var(--brand-alt-chalk-11);
|
|
123
|
+
--brand-alt-border-default: var(--brand-alt-chalk-15);
|
|
124
|
+
--brand-alt-border-default-hover: var(--brand-alt-highlighter-26);
|
|
124
125
|
--brand-alt-border-emphasis: var(--brand-alt-stamp-fill);
|
|
125
126
|
--brand-alt-border-emphasis-hover: var(--brand-alt-stamp-fill-hover);
|
|
126
127
|
}
|
|
@@ -145,13 +146,13 @@
|
|
|
145
146
|
--critical-fg-alt-hover: var(--critical-pen-70);
|
|
146
147
|
--critical-fg-on-emphasis: var(--paper-0);
|
|
147
148
|
--critical-bg-faint: var(--critical-paper-3);
|
|
148
|
-
--critical-bg-subtle: var(--critical-
|
|
149
|
-
--critical-bg-subtle-hover: var(--critical-
|
|
149
|
+
--critical-bg-subtle: var(--critical-chalk-11);
|
|
150
|
+
--critical-bg-subtle-hover: var(--critical-chalk-15);
|
|
150
151
|
--critical-bg-emphasis: var(--critical-pencil-47);
|
|
151
|
-
--critical-border-subtle: var(--critical-
|
|
152
|
-
--critical-border-subtle-hover: var(--critical-
|
|
153
|
-
--critical-border-default: var(--critical-
|
|
154
|
-
--critical-border-default-hover: var(--critical-
|
|
152
|
+
--critical-border-subtle: var(--critical-chalk-8);
|
|
153
|
+
--critical-border-subtle-hover: var(--critical-chalk-11);
|
|
154
|
+
--critical-border-default: var(--critical-chalk-15);
|
|
155
|
+
--critical-border-default-hover: var(--critical-highlighter-26);
|
|
155
156
|
--critical-border-emphasis: var(--critical-pencil-47);
|
|
156
157
|
|
|
157
158
|
--warning-fg: var(--warning-pen-70);
|
|
@@ -160,13 +161,13 @@
|
|
|
160
161
|
--warning-fg-alt-hover: var(--warning-pen-70);
|
|
161
162
|
--warning-fg-on-emphasis: var(--paper-0);
|
|
162
163
|
--warning-bg-faint: var(--warning-paper-3);
|
|
163
|
-
--warning-bg-subtle: var(--warning-
|
|
164
|
-
--warning-bg-subtle-hover: var(--warning-
|
|
164
|
+
--warning-bg-subtle: var(--warning-chalk-11);
|
|
165
|
+
--warning-bg-subtle-hover: var(--warning-chalk-15);
|
|
165
166
|
--warning-bg-emphasis: var(--warning-pencil-47);
|
|
166
|
-
--warning-border-subtle: var(--warning-
|
|
167
|
-
--warning-border-subtle-hover: var(--warning-
|
|
168
|
-
--warning-border-default: var(--warning-
|
|
169
|
-
--warning-border-default-hover: var(--warning-
|
|
167
|
+
--warning-border-subtle: var(--warning-chalk-8);
|
|
168
|
+
--warning-border-subtle-hover: var(--warning-chalk-11);
|
|
169
|
+
--warning-border-default: var(--warning-chalk-15);
|
|
170
|
+
--warning-border-default-hover: var(--warning-highlighter-26);
|
|
170
171
|
--warning-border-emphasis: var(--warning-pencil-47);
|
|
171
172
|
|
|
172
173
|
--positive-fg: var(--positive-pen-70);
|
|
@@ -175,13 +176,13 @@
|
|
|
175
176
|
--positive-fg-alt-hover: var(--positive-pen-70);
|
|
176
177
|
--positive-fg-on-emphasis: var(--paper-0);
|
|
177
178
|
--positive-bg-faint: var(--positive-paper-3);
|
|
178
|
-
--positive-bg-subtle: var(--positive-
|
|
179
|
-
--positive-bg-subtle-hover: var(--positive-
|
|
179
|
+
--positive-bg-subtle: var(--positive-chalk-11);
|
|
180
|
+
--positive-bg-subtle-hover: var(--positive-chalk-15);
|
|
180
181
|
--positive-bg-emphasis: var(--positive-pencil-47);
|
|
181
|
-
--positive-border-subtle: var(--positive-
|
|
182
|
-
--positive-border-subtle-hover: var(--positive-
|
|
183
|
-
--positive-border-default: var(--positive-
|
|
184
|
-
--positive-border-default-hover: var(--positive-
|
|
182
|
+
--positive-border-subtle: var(--positive-chalk-8);
|
|
183
|
+
--positive-border-subtle-hover: var(--positive-chalk-11);
|
|
184
|
+
--positive-border-default: var(--positive-chalk-15);
|
|
185
|
+
--positive-border-default-hover: var(--positive-highlighter-26);
|
|
185
186
|
--positive-border-emphasis: var(--positive-pencil-47);
|
|
186
187
|
|
|
187
188
|
--info-fg: var(--info-pen-70);
|
|
@@ -190,12 +191,12 @@
|
|
|
190
191
|
--info-fg-alt-hover: var(--info-pen-70);
|
|
191
192
|
--info-fg-on-emphasis: var(--paper-0);
|
|
192
193
|
--info-bg-faint: var(--info-paper-3);
|
|
193
|
-
--info-bg-subtle: var(--info-
|
|
194
|
-
--info-bg-subtle-hover: var(--info-
|
|
194
|
+
--info-bg-subtle: var(--info-chalk-11);
|
|
195
|
+
--info-bg-subtle-hover: var(--info-chalk-15);
|
|
195
196
|
--info-bg-emphasis: var(--info-pencil-47);
|
|
196
|
-
--info-border-subtle: var(--info-
|
|
197
|
-
--info-border-subtle-hover: var(--info-
|
|
198
|
-
--info-border-default: var(--info-
|
|
199
|
-
--info-border-default-hover: var(--info-
|
|
197
|
+
--info-border-subtle: var(--info-chalk-8);
|
|
198
|
+
--info-border-subtle-hover: var(--info-chalk-11);
|
|
199
|
+
--info-border-default: var(--info-chalk-15);
|
|
200
|
+
--info-border-default-hover: var(--info-highlighter-26);
|
|
200
201
|
--info-border-emphasis: var(--info-pencil-47);
|
|
201
202
|
}
|