okchroma 0.8.2 → 0.9.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 +136 -101
- package/dist-lib/index.cjs +107 -127
- package/dist-lib/index.mjs +107 -127
- package/dist-lib/types/engine/archetypes.d.ts +1 -1
- package/dist-lib/types/engine/dtcgRender.d.ts +2 -2
- package/dist-lib/types/engine/figmaRender.d.ts +0 -1
- package/dist-lib/types/engine/requirements/producers.d.ts +4 -0
- package/dist-lib/types/engine/tokenNames.d.ts +8 -12
- package/dist-lib/types/index.d.ts +1 -1
- package/package.json +1 -1
package/dist-lib/index.mjs
CHANGED
|
@@ -21,13 +21,13 @@ function stateStepL(restL, _mode, k) {
|
|
|
21
21
|
}
|
|
22
22
|
var OVERRIDE_LIGHT_BELOW = ARCHETYPES.find((a) => a.name === "rich").min;
|
|
23
23
|
var OVERRIDE_DARK_ABOVE = ARCHETYPES.find((a) => a.name === "light").min;
|
|
24
|
-
function stateFillL(restL, mode, k) {
|
|
24
|
+
function stateFillL(restL, mode, k, keepsPole) {
|
|
25
25
|
const natural = mode === "dark" ? 1 : -1;
|
|
26
26
|
const overridden = mode === "light" ? restL < OVERRIDE_LIGHT_BELOW : restL > OVERRIDE_DARK_ABOVE;
|
|
27
27
|
const preferred = overridden ? -natural : natural;
|
|
28
28
|
const fits = (d) => {
|
|
29
29
|
const p = restL + d * stateStepL(restL, mode, 2);
|
|
30
|
-
return p >= STATE_L_MIN && p <= STATE_L_MAX;
|
|
30
|
+
return p >= STATE_L_MIN && p <= STATE_L_MAX && (keepsPole?.(p) ?? true);
|
|
31
31
|
};
|
|
32
32
|
const dir = fits(preferred) ? preferred : fits(-preferred) ? -preferred : preferred;
|
|
33
33
|
const L = restL + dir * stateStepL(restL, mode, k);
|
|
@@ -745,32 +745,20 @@ function stopTokenName(stop) {
|
|
|
745
745
|
}
|
|
746
746
|
var PAPER_0 = "paper-0";
|
|
747
747
|
var PEN_100 = "pen-100";
|
|
748
|
-
var STAMP_FILL = "stamp-
|
|
749
|
-
var STAMP_FILL_HOVER = "stamp-
|
|
750
|
-
var STAMP_FILL_PRESSED = "stamp-
|
|
748
|
+
var STAMP_FILL = "stamp-0";
|
|
749
|
+
var STAMP_FILL_HOVER = "stamp-1";
|
|
750
|
+
var STAMP_FILL_PRESSED = "stamp-2";
|
|
751
751
|
var STAMP_EDGE = "stamp-edge";
|
|
752
752
|
var STAMP_ON = "stamp-on";
|
|
753
|
-
var STAMP_STATE_LEAVES = {
|
|
754
|
-
[STAMP_FILL]: "stamp/fill",
|
|
755
|
-
[STAMP_FILL_HOVER]: "stamp/fill-hover",
|
|
756
|
-
[STAMP_FILL_PRESSED]: "stamp/fill-pressed",
|
|
757
|
-
[STAMP_EDGE]: "stamp/edge",
|
|
758
|
-
[STAMP_ON]: "stamp/on"
|
|
759
|
-
};
|
|
760
|
-
var STAMP_LEAF = {
|
|
761
|
-
FILL: STAMP_STATE_LEAVES[STAMP_FILL],
|
|
762
|
-
FILL_HOVER: STAMP_STATE_LEAVES[STAMP_FILL_HOVER],
|
|
763
|
-
FILL_PRESSED: STAMP_STATE_LEAVES[STAMP_FILL_PRESSED],
|
|
764
|
-
EDGE: STAMP_STATE_LEAVES[STAMP_EDGE],
|
|
765
|
-
ON: STAMP_STATE_LEAVES[STAMP_ON]
|
|
766
|
-
};
|
|
767
753
|
var cssVarName = (path) => `--${path.join("-")}`;
|
|
768
754
|
var figmaPathOf = (path) => path.join("/");
|
|
769
|
-
var familyPath = (family, leaf) => [family,
|
|
755
|
+
var familyPath = (family, leaf) => [family, leaf];
|
|
770
756
|
var LINK_GROUP = "link";
|
|
771
757
|
var LINK_POSTURES = ["default", "inverse"];
|
|
772
758
|
var LINK_STATES = ["enabled", "hover", "pressed"];
|
|
773
|
-
var
|
|
759
|
+
var LINK_STEP = { enabled: 0, hover: 1, pressed: 2 };
|
|
760
|
+
var linkLeaf = (posture, state) => `${posture}-${LINK_STEP[state]}`;
|
|
761
|
+
var linkPath = (posture, state) => [LINK_GROUP, linkLeaf(posture, state)];
|
|
774
762
|
var ABSOLUTE_GROUP = "absolute";
|
|
775
763
|
var absolutePath = (family) => [ABSOLUTE_GROUP, family];
|
|
776
764
|
var COLOR_GROUP = "color";
|
|
@@ -2143,6 +2131,20 @@ function ctaDarkDualGateL(base, darkH, onFillTextIsWhite, enforce, coLc, capLoL,
|
|
|
2143
2131
|
const lLc = findLForBlackTextLc(startL, C2, darkH, coLc + APCA_SOLVE_MARGIN_LC, capHiL);
|
|
2144
2132
|
return Math.max(l45, Math.min(lLc, capHiL));
|
|
2145
2133
|
}
|
|
2134
|
+
function stepKeepsPole(mode, white, chromaAt, H2, law) {
|
|
2135
|
+
if (mode === "light") return void 0;
|
|
2136
|
+
if (law.enforceLc !== void 0) {
|
|
2137
|
+
const bar = law.enforceLc;
|
|
2138
|
+
const lcAt = white ? whiteTextLcAt : blackTextLcAt;
|
|
2139
|
+
return (L) => lcAt(L, chromaAt(L), H2) >= bar;
|
|
2140
|
+
}
|
|
2141
|
+
if (law.ratioFloor === void 0) return void 0;
|
|
2142
|
+
const floor = law.ratioFloor, poleY = white ? 1 : 0;
|
|
2143
|
+
return (L) => {
|
|
2144
|
+
const C2 = clampChromaToGamut(L, chromaAt(L), H2);
|
|
2145
|
+
return Math.min(legalRatio(L, C2, H2, poleY), contrastRatio(shippedY(L, C2, H2), poleY)) >= floor;
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2146
2148
|
function onFillIsWhiteDarkAt(L, C2, H2, enforce, ratioFloor) {
|
|
2147
2149
|
return onTextIsWhite(apcaYAt(L, C2, H2), L, C2, H2, enforce, ratioFloor);
|
|
2148
2150
|
}
|
|
@@ -2425,7 +2427,8 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2425
2427
|
const enforceLc = spec.ons.onFill.enforceLc;
|
|
2426
2428
|
const coLc = ctx.opts?.apcaClearance && enforceLc === void 0 ? ctx.opts.apcaClearanceLc ?? spec.ons.onFill.coEnforceLc : void 0;
|
|
2427
2429
|
const onFloor = enforceLc !== void 0 ? void 0 : spec.ons.onFill.ratioFloor;
|
|
2428
|
-
let cta,
|
|
2430
|
+
let cta, onFillIsWhite;
|
|
2431
|
+
let stateC, stateH;
|
|
2429
2432
|
if (mode === "light") {
|
|
2430
2433
|
onFillIsWhite = onFillIsWhiteLight(ctx, enforceLc !== void 0 ? false : onFillEnforce);
|
|
2431
2434
|
let light9L = enforceLc !== void 0 ? ctaLightLApca(ctx, onFillIsWhite, onFillEnforce, enforceLc) : ctaLightL(ctx, onFillIsWhite, onFillEnforce);
|
|
@@ -2457,21 +2460,14 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2457
2460
|
}
|
|
2458
2461
|
}
|
|
2459
2462
|
cta = emitRole(STAMP_FILL, light9L, ctaCFor(light9L) * ctaCMul, ctaH);
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
if (light9L !== ctx.scaleL) {
|
|
2467
|
-
cta.enforced = true;
|
|
2468
|
-
ctaHover.enforced = true;
|
|
2469
|
-
ctaPressed.enforced = true;
|
|
2470
|
-
}
|
|
2463
|
+
stateC = {
|
|
2464
|
+
hover: (L) => ctx.cAt("light", L, (hoverReq?.chromaMult ?? 1) * ctx.brandC) * ctaCMul,
|
|
2465
|
+
pressed: (L) => ctx.cAt("light", L, (pressedReq?.chromaMult ?? 1) * ctx.brandC) * ctaCMul
|
|
2466
|
+
};
|
|
2467
|
+
stateH = ctaH;
|
|
2468
|
+
if (light9L !== ctx.scaleL) cta.enforced = true;
|
|
2471
2469
|
if (repelled) {
|
|
2472
2470
|
cta.repelled = true;
|
|
2473
|
-
ctaHover.repelled = true;
|
|
2474
|
-
ctaPressed.repelled = true;
|
|
2475
2471
|
onFillIsWhite = onFillIsWhiteDarkAt(
|
|
2476
2472
|
cta.L,
|
|
2477
2473
|
cta.C,
|
|
@@ -2484,50 +2480,29 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2484
2480
|
const d = dctx;
|
|
2485
2481
|
const cta9L = ctx.opts?.darkCtaFlatApp !== void 0 ? flatDarkCtaL(d, (L) => ctx.cAt("dark", L, d.darkC9), ctx.darkCtaH, ctx.opts.darkCtaFlatApp) : d.dark9L;
|
|
2486
2482
|
const dCFor = (L) => ctx.cAt("dark", L, d.darkC9);
|
|
2487
|
-
|
|
2483
|
+
stateC = { hover: dCFor, pressed: dCFor };
|
|
2484
|
+
stateH = ctx.darkCtaH;
|
|
2488
2485
|
cta = emitRole(STAMP_FILL, cta9L, dCFor(cta9L), ctx.darkCtaH);
|
|
2489
|
-
{
|
|
2490
|
-
const [hL, pL] = dTrio(cta9L);
|
|
2491
|
-
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2492
|
-
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2493
|
-
}
|
|
2494
2486
|
onFillIsWhite = onFillIsWhiteDarkAt(cta.L, cta.C, cta.H, enforceLc !== void 0 ? false : onFillEnforce);
|
|
2495
2487
|
const enforcedL = enforceLc !== void 0 ? ctaDarkEnforcedLApca(ctx, cta, onFillIsWhite, onFillEnforce, enforceLc) : ctaDarkEnforcedL(ctx, cta, onFillIsWhite, onFillEnforce);
|
|
2496
2488
|
if (enforcedL !== null) {
|
|
2497
2489
|
cta = emitRole(STAMP_FILL, enforcedL, dCFor(enforcedL), ctx.darkCtaH);
|
|
2498
|
-
const [hL, pL] = dTrio(enforcedL);
|
|
2499
|
-
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2500
|
-
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2501
2490
|
cta.enforced = true;
|
|
2502
|
-
ctaHover.enforced = true;
|
|
2503
|
-
ctaPressed.enforced = true;
|
|
2504
2491
|
}
|
|
2505
2492
|
if (coLc !== void 0) {
|
|
2506
2493
|
const [capLoL, capHiL] = CTA_CLEARANCE_CAPS;
|
|
2507
2494
|
const clearedL = ctaDarkDualGateL(cta, ctx.darkCtaH, onFillIsWhite, onFillEnforce, coLc + APCA_ENFORCE_MARGIN_LC, capLoL, capHiL);
|
|
2508
2495
|
if (Math.abs(clearedL - cta.L) > 1e-9) {
|
|
2509
2496
|
cta = emitRole(STAMP_FILL, clearedL, dCFor(clearedL), ctx.darkCtaH);
|
|
2510
|
-
const [hL, pL] = dTrio(clearedL);
|
|
2511
|
-
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2512
|
-
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2513
2497
|
cta.enforced = true;
|
|
2514
|
-
ctaHover.enforced = true;
|
|
2515
|
-
ctaPressed.enforced = true;
|
|
2516
2498
|
}
|
|
2517
2499
|
}
|
|
2518
2500
|
if (ctx.opts?.ctaSolve) {
|
|
2519
2501
|
const exitL = solveDarkCtaExit(cta, dCFor, ctx.darkCtaH, ctx.opts.ctaSolve.redDark, enforceLc, coLc);
|
|
2520
2502
|
if (exitL !== null) {
|
|
2521
2503
|
cta = emitRole(STAMP_FILL, exitL, dCFor(exitL), ctx.darkCtaH);
|
|
2522
|
-
const [hL, pL] = dTrio(exitL);
|
|
2523
|
-
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2524
|
-
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2525
2504
|
cta.enforced = true;
|
|
2526
|
-
ctaHover.enforced = true;
|
|
2527
|
-
ctaPressed.enforced = true;
|
|
2528
2505
|
cta.repelled = true;
|
|
2529
|
-
ctaHover.repelled = true;
|
|
2530
|
-
ctaPressed.repelled = true;
|
|
2531
2506
|
onFillIsWhite = onFillIsWhiteDarkAt(
|
|
2532
2507
|
cta.L,
|
|
2533
2508
|
cta.C,
|
|
@@ -2543,6 +2518,19 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2543
2518
|
const other = legalRatio(cta.L, cta.C, cta.H, onFillIsWhite ? 0 : 1);
|
|
2544
2519
|
if (chosen < onFloor && other >= onFloor) onFillIsWhite = !onFillIsWhite;
|
|
2545
2520
|
}
|
|
2521
|
+
const keepsPole = stepKeepsPole(mode, onFillIsWhite, stateC.pressed, stateH, { ratioFloor: onFloor, enforceLc });
|
|
2522
|
+
const hL = stateFillL(cta.L, mode, 1, keepsPole);
|
|
2523
|
+
const pL = stateFillL(cta.L, mode, 2, keepsPole);
|
|
2524
|
+
const ctaHover = emitRole(STAMP_FILL_HOVER, hL, stateC.hover(hL), stateH);
|
|
2525
|
+
const ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, stateC.pressed(pL), stateH);
|
|
2526
|
+
if (cta.enforced) {
|
|
2527
|
+
ctaHover.enforced = true;
|
|
2528
|
+
ctaPressed.enforced = true;
|
|
2529
|
+
}
|
|
2530
|
+
if (cta.repelled) {
|
|
2531
|
+
ctaHover.repelled = true;
|
|
2532
|
+
ctaPressed.repelled = true;
|
|
2533
|
+
}
|
|
2546
2534
|
return { mode, seed, stops, roles: { cta, ctaHover, ctaPressed }, ons: { onFillIsWhite } };
|
|
2547
2535
|
}
|
|
2548
2536
|
|
|
@@ -2625,18 +2613,18 @@ function generateNeutralScale(brandH, level = "default", contrastProfile) {
|
|
|
2625
2613
|
if (popLift > 1e-6) {
|
|
2626
2614
|
scale.ctaDark = makeStop(9, scale.dark[3].L + popLift, scale.dark[3].C, scale.dark[3].H);
|
|
2627
2615
|
}
|
|
2628
|
-
const nState = (stop, mode, rest, k) => {
|
|
2629
|
-
const L = stateFillL(rest.L, mode, k);
|
|
2630
|
-
return makeStop(stop, L, curve(L, mode), rest.H);
|
|
2631
|
-
};
|
|
2632
|
-
scale.ctaHover = nState(10, "light", scale.cta, 1);
|
|
2633
|
-
scale.ctaPressed = nState(11, "light", scale.cta, 2);
|
|
2634
|
-
scale.ctaHoverDark = nState(10, "dark", scale.ctaDark, 1);
|
|
2635
|
-
scale.ctaPressedDark = nState(11, "dark", scale.ctaDark, 2);
|
|
2636
2616
|
const onEnforce = contrastProfile !== "apca";
|
|
2637
2617
|
const onFloor = 4.5;
|
|
2638
2618
|
scale.onFillTextIsWhite = onTextIsWhite(apcaY(scale.cta.r, scale.cta.g, scale.cta.b), scale.cta.L, scale.cta.C, scale.cta.H, onEnforce, onFloor);
|
|
2639
2619
|
scale.onFillTextIsWhiteDark = onTextIsWhite(apcaY(scale.ctaDark.r, scale.ctaDark.g, scale.ctaDark.b), scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, onEnforce, onFloor);
|
|
2620
|
+
const nState = (stop, mode, rest, k, white) => {
|
|
2621
|
+
const L = stateFillL(rest.L, mode, k, stepKeepsPole(mode, white, (l) => curve(l, mode), rest.H, { ratioFloor: onFloor }));
|
|
2622
|
+
return makeStop(stop, L, curve(L, mode), rest.H);
|
|
2623
|
+
};
|
|
2624
|
+
scale.ctaHover = nState(10, "light", scale.cta, 1, scale.onFillTextIsWhite);
|
|
2625
|
+
scale.ctaPressed = nState(11, "light", scale.cta, 2, scale.onFillTextIsWhite);
|
|
2626
|
+
scale.ctaHoverDark = nState(10, "dark", scale.ctaDark, 1, scale.onFillTextIsWhiteDark);
|
|
2627
|
+
scale.ctaPressedDark = nState(11, "dark", scale.ctaDark, 2, scale.onFillTextIsWhiteDark);
|
|
2640
2628
|
return scale;
|
|
2641
2629
|
}
|
|
2642
2630
|
function generateSubtleSecondary(hex, opts) {
|
|
@@ -2649,10 +2637,6 @@ function generateSubtleSecondary(hex, opts) {
|
|
|
2649
2637
|
});
|
|
2650
2638
|
const asCta = (stop, src) => makeStop(stop, src.L, src.C, src.H);
|
|
2651
2639
|
const mk = (stop, L, mode) => makeStop(stop, L, curve(L, mode), scale.brandH);
|
|
2652
|
-
const st = (stop, mode, rest, k) => {
|
|
2653
|
-
const L = stateFillL(rest.L, mode, k);
|
|
2654
|
-
return makeStop(stop, L, curve(L, mode), rest.H);
|
|
2655
|
-
};
|
|
2656
2640
|
if (opts?.ctaL) {
|
|
2657
2641
|
scale.cta = mk(9, opts.ctaL.light, "light");
|
|
2658
2642
|
scale.ctaDark = mk(9, opts.ctaL.dark, "dark");
|
|
@@ -2660,14 +2644,18 @@ function generateSubtleSecondary(hex, opts) {
|
|
|
2660
2644
|
scale.cta = asCta(9, scale.light[3]);
|
|
2661
2645
|
scale.ctaDark = asCta(9, scale.dark[3]);
|
|
2662
2646
|
}
|
|
2663
|
-
scale.ctaHover = st(10, "light", scale.cta, 1);
|
|
2664
|
-
scale.ctaPressed = st(11, "light", scale.cta, 2);
|
|
2665
|
-
scale.ctaHoverDark = st(10, "dark", scale.ctaDark, 1);
|
|
2666
|
-
scale.ctaPressedDark = st(11, "dark", scale.ctaDark, 2);
|
|
2667
2647
|
const onEnforce = opts?.contrastProfile !== "apca";
|
|
2668
2648
|
const onFloor = 4.5;
|
|
2669
2649
|
scale.onFillTextIsWhite = onTextIsWhite(apcaY(scale.cta.r, scale.cta.g, scale.cta.b), scale.cta.L, scale.cta.C, scale.cta.H, onEnforce, onFloor);
|
|
2670
2650
|
scale.onFillTextIsWhiteDark = onTextIsWhite(apcaY(scale.ctaDark.r, scale.ctaDark.g, scale.ctaDark.b), scale.ctaDark.L, scale.ctaDark.C, scale.ctaDark.H, onEnforce, onFloor);
|
|
2651
|
+
const st = (stop, mode, rest, k, white) => {
|
|
2652
|
+
const L = stateFillL(rest.L, mode, k, stepKeepsPole(mode, white, (l) => curve(l, mode), rest.H, { ratioFloor: onFloor }));
|
|
2653
|
+
return makeStop(stop, L, curve(L, mode), rest.H);
|
|
2654
|
+
};
|
|
2655
|
+
scale.ctaHover = st(10, "light", scale.cta, 1, scale.onFillTextIsWhite);
|
|
2656
|
+
scale.ctaPressed = st(11, "light", scale.cta, 2, scale.onFillTextIsWhite);
|
|
2657
|
+
scale.ctaHoverDark = st(10, "dark", scale.ctaDark, 1, scale.onFillTextIsWhiteDark);
|
|
2658
|
+
scale.ctaPressedDark = st(11, "dark", scale.ctaDark, 2, scale.onFillTextIsWhiteDark);
|
|
2671
2659
|
return scale;
|
|
2672
2660
|
}
|
|
2673
2661
|
|
|
@@ -2761,20 +2749,21 @@ var SCALE = {
|
|
|
2761
2749
|
// ("heavy-emphasis": "high" and "low" are plane words in the community plugin and
|
|
2762
2750
|
// "strong" carries "on")
|
|
2763
2751
|
"pen-70": { req: "heavy-emphasis text, inverted backgrounds", contrast: AA_BODY, claim: claimOf("pen"), theming: solved, collides: true },
|
|
2764
|
-
// the stamp
|
|
2752
|
+
// the stamp rows, each a position: the fill at rest and at one and two steps, the edge,
|
|
2753
|
+
// the on-text. No body names a state; a consumer's semantic layer names the states and
|
|
2754
|
+
// aliases the steps. "CTA" stays in these bodies on purpose: it is not a token label, so
|
|
2765
2755
|
// it floods nothing, and a designer's "cta" query lands on these rows.
|
|
2766
2756
|
// the usage lines are the owner's guidance, for the document rendering: the fill is
|
|
2767
2757
|
// misused as a standalone color in the wild
|
|
2768
|
-
"stamp
|
|
2769
|
-
req: "CTAs",
|
|
2758
|
+
"stamp-0": {
|
|
2759
|
+
req: "CTAs, the fill at rest",
|
|
2770
2760
|
theming: (f) => `fully re-solved per theme and family${COLOR_WORD[f]}`,
|
|
2771
|
-
use: "primary button fills; optional for avatars, badges and other elements that carry text. Not a standalone color: it has no contrast guarantee of its own, only its on-text has one. Use it alone or with
|
|
2761
|
+
use: "primary button fills; optional for avatars, badges and other elements that carry text. Not a standalone color: it has no contrast guarantee of its own, only its on-text has one. Use it alone or with the two steps above it. Always bordered with its edge"
|
|
2772
2762
|
},
|
|
2773
|
-
"stamp
|
|
2774
|
-
"stamp
|
|
2775
|
-
"stamp
|
|
2776
|
-
|
|
2777
|
-
"stamp/on": { req: "text over the CTA color", contrast: `${AA_BODY} over its CTA`, theming: (f) => `whichever pole passes; quiet CTAs take the soft pole${COLOR_WORD[f]}`, use: "the only text color over the fill; never elsewhere" }
|
|
2763
|
+
"stamp-1": { req: "CTAs, the fill one step from rest", theming: (f) => `follows the fill at rest${COLOR_WORD[f]}`, use: "the fill one step from rest, with the fill at rest; for nothing else. Always bordered with the edge" },
|
|
2764
|
+
"stamp-2": { req: "CTAs, the fill two steps from rest", theming: (f) => `follows the fill at rest${COLOR_WORD[f]}`, use: "the fill two steps from rest, with the fill at rest; for nothing else. Always bordered with the edge" },
|
|
2765
|
+
"stamp-edge": { req: "min APCA visibility", theming: (f) => `draws for CTAs that sit close to the page; strength per family tier${COLOR_WORD[f]}`, use: "the border of the fill at every step, always rendered with it and never without it (it resolves to transparent unless the fill sits close to the page); for nothing else" },
|
|
2766
|
+
"stamp-on": { req: "text over the CTA color", contrast: `${AA_BODY} over its CTA`, theming: (f) => `whichever pole passes; quiet CTAs take the soft pole${COLOR_WORD[f]}`, use: "the only text color over the fill at every step; never elsewhere" }
|
|
2778
2767
|
};
|
|
2779
2768
|
var NEUTRAL_ONLY = {
|
|
2780
2769
|
// the pole is measured against the neutral's own stops only; as text, it is the on-color
|
|
@@ -2783,15 +2772,15 @@ var NEUTRAL_ONLY = {
|
|
|
2783
2772
|
// the literal pole carries no tint, so no theming line
|
|
2784
2773
|
"pen-100": { req: "max-emphasis text", contrast: AA_BODY, claim: () => `${AA_BODY} on every paper and chalk of every family` }
|
|
2785
2774
|
};
|
|
2786
|
-
var LINK = (
|
|
2787
|
-
req: "links" +
|
|
2775
|
+
var LINK = (step, contrast) => ({
|
|
2776
|
+
req: "links" + step,
|
|
2788
2777
|
contrast,
|
|
2789
2778
|
claim: () => `${contrast} on every paper of the neutral`,
|
|
2790
2779
|
theming: "rides the theme\u2019s link color; custom seed re-solves; overridable per theme",
|
|
2791
2780
|
use: "hyperlinks; not a text-style CTA, which is the text stops read as states"
|
|
2792
2781
|
});
|
|
2793
|
-
var LINK_INVERSE = (
|
|
2794
|
-
req: "links over inverted backgrounds" +
|
|
2782
|
+
var LINK_INVERSE = (step, contrast) => ({
|
|
2783
|
+
req: "links over inverted backgrounds" + step,
|
|
2795
2784
|
contrast,
|
|
2796
2785
|
claim: () => `${contrast} on any family's pen-70`,
|
|
2797
2786
|
theming: "same seed as the link, re-solved for inverted backgrounds; overridable per theme",
|
|
@@ -2800,12 +2789,12 @@ var LINK_INVERSE = (state, contrast) => ({
|
|
|
2800
2789
|
var SYSTEM = {
|
|
2801
2790
|
"absolute/brand": { req: "identity seed reference", theming: "the theme\u2019s own input, as given", use: "a reference value (logos, swatches of the input); never a UI color, it carries no contrast guarantee" },
|
|
2802
2791
|
"absolute/brand-alt": { req: "identity seed reference", theming: "the theme\u2019s paired input, as given", use: "a reference value (logos, swatches of the input); never a UI color, it carries no contrast guarantee" },
|
|
2803
|
-
"link/default
|
|
2804
|
-
"link/default
|
|
2805
|
-
"link/default
|
|
2806
|
-
"link/inverse
|
|
2807
|
-
"link/inverse
|
|
2808
|
-
"link/inverse
|
|
2792
|
+
"link/default-0": LINK("", AA_BODY),
|
|
2793
|
+
"link/default-1": LINK(", one step from rest", AA_BODY),
|
|
2794
|
+
"link/default-2": LINK(", two steps from rest", AA_BODY),
|
|
2795
|
+
"link/inverse-0": LINK_INVERSE("", AA_BODY),
|
|
2796
|
+
"link/inverse-1": LINK_INVERSE(", one step from rest", AA_BODY),
|
|
2797
|
+
"link/inverse-2": LINK_INVERSE(", two steps from rest", AA_BODY)
|
|
2809
2798
|
};
|
|
2810
2799
|
var PREFIXES = [
|
|
2811
2800
|
// community spellings first: the family word is a prefix of the community shapes
|
|
@@ -3242,11 +3231,12 @@ function escapeCtaFamily(nScale, mode, contrastProfile) {
|
|
|
3242
3231
|
if (!strongText) throw new Error("escapeCtaFamily: the neutral scale has no ink-11 stop");
|
|
3243
3232
|
const mk = (stop, L) => makeStop(stop, L, strongText.C, strongText.H);
|
|
3244
3233
|
const cta = mk(9, strongText.L);
|
|
3245
|
-
const ctaHover = mk(10, stateFillL(strongText.L, mode, 1));
|
|
3246
|
-
const ctaPressed = mk(11, stateFillL(strongText.L, mode, 2));
|
|
3247
3234
|
const onEnforce = contrastProfile !== "apca";
|
|
3248
3235
|
const onFloor = contrastProfile === "apca" ? void 0 : 4.5;
|
|
3249
3236
|
const onFillIsWhite = onTextIsWhite(apcaY(cta.r, cta.g, cta.b), cta.L, cta.C, cta.H, onEnforce, onFloor);
|
|
3237
|
+
const keepsPole = stepKeepsPole(mode, onFillIsWhite, () => strongText.C, strongText.H, { ratioFloor: onFloor });
|
|
3238
|
+
const ctaHover = mk(10, stateFillL(strongText.L, mode, 1, keepsPole));
|
|
3239
|
+
const ctaPressed = mk(11, stateFillL(strongText.L, mode, 2, keepsPole));
|
|
3250
3240
|
return { cta, ctaHover, ctaPressed, onFillIsWhite };
|
|
3251
3241
|
}
|
|
3252
3242
|
var SECONDARY_DISTINCT_DELTA_E = 0.12;
|
|
@@ -3796,9 +3786,6 @@ function colorFromHexString(hex) {
|
|
|
3796
3786
|
const h = hex.replace("#", "");
|
|
3797
3787
|
return color(parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255);
|
|
3798
3788
|
}
|
|
3799
|
-
function bandedLeaf(flat) {
|
|
3800
|
-
return STAMP_STATE_LEAVES[flat] ?? flat;
|
|
3801
|
-
}
|
|
3802
3789
|
function groupEntries(g2) {
|
|
3803
3790
|
const entries = Object.entries(g2);
|
|
3804
3791
|
const digitLeading = (k) => /^\d/.test(k);
|
|
@@ -3806,24 +3793,15 @@ function groupEntries(g2) {
|
|
|
3806
3793
|
return entries.sort((a, b) => parseInt(b[0], 10) - parseInt(a[0], 10));
|
|
3807
3794
|
return entries;
|
|
3808
3795
|
}
|
|
3809
|
-
function putLeaf(g2, flat, tok) {
|
|
3810
|
-
const path = bandedLeaf(flat).split("/");
|
|
3811
|
-
let cur = g2;
|
|
3812
|
-
for (const seg of path.slice(0, -1)) {
|
|
3813
|
-
if (!(seg in cur) || "$type" in cur[seg]) cur[seg] = {};
|
|
3814
|
-
cur = cur[seg];
|
|
3815
|
-
}
|
|
3816
|
-
cur[path[path.length - 1]] = tok;
|
|
3817
|
-
}
|
|
3818
3796
|
function rampGroup(stops, onFillWhite, extra) {
|
|
3819
3797
|
const g2 = {};
|
|
3820
3798
|
const leaves = stops.map((s) => ({ name: stopTokenName(s.stop), tok: colorFromStop(s) })).sort((a, b) => tokenOrder(a.name) - tokenOrder(b.name));
|
|
3821
|
-
for (const l of leaves)
|
|
3822
|
-
if (extra?.cta)
|
|
3823
|
-
if (extra?.ctaHover)
|
|
3824
|
-
if (extra?.ctaPressed)
|
|
3825
|
-
if (extra?.cta)
|
|
3826
|
-
|
|
3799
|
+
for (const l of leaves) g2[l.name] = l.tok;
|
|
3800
|
+
if (extra?.cta) g2[STAMP_FILL] = colorFromStop(extra.cta);
|
|
3801
|
+
if (extra?.ctaHover) g2[STAMP_FILL_HOVER] = colorFromStop(extra.ctaHover);
|
|
3802
|
+
if (extra?.ctaPressed) g2[STAMP_FILL_PRESSED] = colorFromStop(extra.ctaPressed);
|
|
3803
|
+
if (extra?.cta) g2[STAMP_EDGE] = extra.ctaBorder ?? TRANSPARENT_TOKEN;
|
|
3804
|
+
g2[STAMP_ON] = pole(onFillWhite);
|
|
3827
3805
|
return g2;
|
|
3828
3806
|
}
|
|
3829
3807
|
function themeToFigma(r, input) {
|
|
@@ -3862,16 +3840,18 @@ function themeToFigma(r, input) {
|
|
|
3862
3840
|
if (input.secondaryStyle === "outline" && input.secondary) {
|
|
3863
3841
|
const s8 = secondary[mode].find((s) => s.stop === 8);
|
|
3864
3842
|
const s9 = secondary[mode].find((s) => s.stop === 9);
|
|
3865
|
-
|
|
3843
|
+
secondaryGroup[STAMP_FILL] = TRANSPARENT_TOKEN;
|
|
3866
3844
|
if (s8) {
|
|
3867
3845
|
const e = srgbEmitChannels(s8);
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3846
|
+
secondaryGroup[STAMP_FILL_HOVER] = color(e.r, e.g, e.b, OUTLINE_HOVER_ALPHA);
|
|
3847
|
+
secondaryGroup[STAMP_FILL_PRESSED] = color(e.r, e.g, e.b, OUTLINE_PRESSED_ALPHA);
|
|
3848
|
+
secondaryGroup[STAMP_EDGE] = colorFromStop(s8);
|
|
3871
3849
|
}
|
|
3872
|
-
if (s9)
|
|
3850
|
+
if (s9) secondaryGroup[STAMP_ON] = colorFromStop(s9);
|
|
3873
3851
|
}
|
|
3874
|
-
const softOnCta = (g3, white) =>
|
|
3852
|
+
const softOnCta = (g3, white) => {
|
|
3853
|
+
g3[STAMP_ON] = pole(white, SOFT_ON_CTA_ALPHA[mode]);
|
|
3854
|
+
};
|
|
3875
3855
|
softOnCta(neutralGroup, mode === "light" ? nScale.onFillTextIsWhite : nScale.onFillTextIsWhiteDark);
|
|
3876
3856
|
if (input.secondary && input.secondaryStyle !== "outline" && softOnCtaPasses(input.secondary, mode))
|
|
3877
3857
|
softOnCta(secondaryGroup, mode === "light" ? secondaryOnFillLight : secondaryOnFillDark);
|
|
@@ -3879,10 +3859,10 @@ function themeToFigma(r, input) {
|
|
|
3879
3859
|
const esc = input.ctaEscape ? escapeCtaFamily(nScale, mode, input.contrastProfile) : null;
|
|
3880
3860
|
if (esc) {
|
|
3881
3861
|
for (const g3 of input.secondary ? [brandGroup] : [brandGroup, secondaryGroup]) {
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3862
|
+
g3[STAMP_FILL] = colorFromStop(esc.cta);
|
|
3863
|
+
g3[STAMP_FILL_HOVER] = colorFromStop(esc.ctaHover);
|
|
3864
|
+
g3[STAMP_FILL_PRESSED] = colorFromStop(esc.ctaPressed);
|
|
3865
|
+
g3[STAMP_ON] = pole(esc.onFillIsWhite);
|
|
3886
3866
|
}
|
|
3887
3867
|
}
|
|
3888
3868
|
const scaleTextAt = (n) => {
|
|
@@ -3892,7 +3872,7 @@ function themeToFigma(r, input) {
|
|
|
3892
3872
|
};
|
|
3893
3873
|
const trio = (posture) => {
|
|
3894
3874
|
const t = posture === "default" ? lt ? mode === "light" ? [lt.link, lt.linkHover, lt.linkPressed] : [lt.linkDark, lt.linkHoverDark, lt.linkPressedDark] : [scaleTextAt(9), scaleTextAt(10), scaleTextAt(11)] : mode === "light" ? [invLt.link, invLt.linkHover, invLt.linkPressed] : [invLt.linkDark, invLt.linkHoverDark, invLt.linkPressedDark];
|
|
3895
|
-
return Object.fromEntries(LINK_STATES.map((state, i) => [state, colorFromStop(t[i])]));
|
|
3875
|
+
return Object.fromEntries(LINK_STATES.map((state, i) => [linkLeaf(posture, state), colorFromStop(t[i])]));
|
|
3896
3876
|
};
|
|
3897
3877
|
const g2 = {
|
|
3898
3878
|
[CSS_FAMILY.neutral]: neutralGroup,
|
|
@@ -3907,7 +3887,7 @@ function themeToFigma(r, input) {
|
|
|
3907
3887
|
ctaFamily(sig.scale, mode, role)
|
|
3908
3888
|
);
|
|
3909
3889
|
}
|
|
3910
|
-
g2[LINK_GROUP] = {
|
|
3890
|
+
g2[LINK_GROUP] = { ...trio("default"), ...trio("inverse") };
|
|
3911
3891
|
g2[ABSOLUTE_GROUP] = {
|
|
3912
3892
|
[CSS_FAMILY.brandPrimary]: colorFromHexString(scale.identityHex ?? invSeed),
|
|
3913
3893
|
[CSS_FAMILY.brandSecondary]: colorFromHexString((input.secondary ?? scale).identityHex ?? invSeed)
|
|
@@ -4008,6 +3988,7 @@ export {
|
|
|
4008
3988
|
LINK_GROUP,
|
|
4009
3989
|
LINK_POSTURES,
|
|
4010
3990
|
LINK_STATES,
|
|
3991
|
+
LINK_STEP,
|
|
4011
3992
|
MODE_SPECS,
|
|
4012
3993
|
OFFSET_ALPHAS,
|
|
4013
3994
|
OUTLINE_HOVER_ALPHA,
|
|
@@ -4022,9 +4003,7 @@ export {
|
|
|
4022
4003
|
STAMP_FILL,
|
|
4023
4004
|
STAMP_FILL_HOVER,
|
|
4024
4005
|
STAMP_FILL_PRESSED,
|
|
4025
|
-
STAMP_LEAF,
|
|
4026
4006
|
STAMP_ON,
|
|
4027
|
-
STAMP_STATE_LEAVES,
|
|
4028
4007
|
SUBTLE_PASTEL_K,
|
|
4029
4008
|
SUBTLE_TINT_MULT,
|
|
4030
4009
|
absolutePath,
|
|
@@ -4045,6 +4024,7 @@ export {
|
|
|
4045
4024
|
generateScale,
|
|
4046
4025
|
generateSubtleSecondary,
|
|
4047
4026
|
groupEntries,
|
|
4027
|
+
linkLeaf,
|
|
4048
4028
|
linkPath,
|
|
4049
4029
|
neutralCss,
|
|
4050
4030
|
neutralTintHue,
|
|
@@ -10,4 +10,4 @@ export declare function medianLForArchetype(archetype: Archetype): number;
|
|
|
10
10
|
export declare function hoverL(L: number): number;
|
|
11
11
|
export declare function pressedL(L: number): number;
|
|
12
12
|
export declare function stateStepL(restL: number, _mode: 'light' | 'dark', k: 1 | 2): number;
|
|
13
|
-
export declare function stateFillL(restL: number, mode: 'light' | 'dark', k: 1 | 2): number;
|
|
13
|
+
export declare function stateFillL(restL: number, mode: 'light' | 'dark', k: 1 | 2, keepsPole?: (L: number) => boolean): number;
|
|
@@ -18,11 +18,11 @@ export type DtcgGroup = {
|
|
|
18
18
|
export type DtcgDocument = DtcgGroup;
|
|
19
19
|
/**
|
|
20
20
|
* Every primitive's path, in document order: the neutral with its poles, the brand, the
|
|
21
|
-
* alt, the four signals by role, the link
|
|
21
|
+
* alt, the four signals by role, the link rows, the seed absolutes. This roster is the
|
|
22
22
|
* definition of what the engine emits; the emitters and the audit ride it.
|
|
23
23
|
*/
|
|
24
24
|
export declare function tokenPaths(): TokenPath[];
|
|
25
|
-
/** the path of a structured-emit name (`brand-stamp-
|
|
25
|
+
/** the path of a structured-emit name (`brand-stamp-0`), or undefined when it is not a primitive */
|
|
26
26
|
export declare function tokenPathOf(name: string): TokenPath | undefined;
|
|
27
27
|
/** the description key of a structured-emit name: its slash-joined path */
|
|
28
28
|
export declare function descriptionPathOf(name: string): string | undefined;
|
|
@@ -14,7 +14,6 @@ export type FigmaGroup = {
|
|
|
14
14
|
[key: string]: FigmaLeaf | FigmaGroup;
|
|
15
15
|
};
|
|
16
16
|
export declare function groupEntries(g: FigmaGroup): Array<[string, FigmaLeaf | FigmaGroup]>;
|
|
17
|
-
export declare function putLeaf(g: FigmaGroup, flat: string, tok: FigmaLeaf): void;
|
|
18
17
|
export interface ThemeInput {
|
|
19
18
|
secondary?: GeneratedScale | null;
|
|
20
19
|
secondaryStyle?: SecondaryStyle;
|
|
@@ -133,6 +133,10 @@ export declare function ctaDarkDualGateL(base: {
|
|
|
133
133
|
L: number;
|
|
134
134
|
C: number;
|
|
135
135
|
}, darkH: number, onFillTextIsWhite: boolean, enforce: boolean, coLc: number, capLoL: number, capHiL: number): number;
|
|
136
|
+
export declare function stepKeepsPole(mode: 'light' | 'dark', white: boolean, chromaAt: (L: number) => number, H: number, law: {
|
|
137
|
+
ratioFloor?: number;
|
|
138
|
+
enforceLc?: number;
|
|
139
|
+
}): ((L: number) => boolean) | undefined;
|
|
136
140
|
export declare function onFillIsWhiteDarkAt(L: number, C: number, H: number, enforce: boolean, ratioFloor?: number): boolean;
|
|
137
141
|
export interface BrandExitLanding {
|
|
138
142
|
L: number;
|
|
@@ -2,29 +2,25 @@ export declare const SCALE_STOP_COUNT: number;
|
|
|
2
2
|
export declare function stopTokenName(stop: number): string;
|
|
3
3
|
export declare const PAPER_0 = "paper-0";
|
|
4
4
|
export declare const PEN_100 = "pen-100";
|
|
5
|
-
export declare const STAMP_FILL = "stamp-
|
|
6
|
-
export declare const STAMP_FILL_HOVER = "stamp-
|
|
7
|
-
export declare const STAMP_FILL_PRESSED = "stamp-
|
|
5
|
+
export declare const STAMP_FILL = "stamp-0";
|
|
6
|
+
export declare const STAMP_FILL_HOVER = "stamp-1";
|
|
7
|
+
export declare const STAMP_FILL_PRESSED = "stamp-2";
|
|
8
8
|
export declare const STAMP_EDGE = "stamp-edge";
|
|
9
9
|
export declare const STAMP_ON = "stamp-on";
|
|
10
|
-
export declare const STAMP_STATE_LEAVES: Record<string, string>;
|
|
11
|
-
export declare const STAMP_LEAF: {
|
|
12
|
-
readonly FILL: string;
|
|
13
|
-
readonly FILL_HOVER: string;
|
|
14
|
-
readonly FILL_PRESSED: string;
|
|
15
|
-
readonly EDGE: string;
|
|
16
|
-
readonly ON: string;
|
|
17
|
-
};
|
|
18
10
|
export type TokenPath = readonly string[];
|
|
19
11
|
export declare const cssVarName: (path: TokenPath) => string;
|
|
20
12
|
export declare const figmaPathOf: (path: TokenPath) => string;
|
|
21
|
-
/** a family row: a scale stop, a pole, or a stamp
|
|
13
|
+
/** a family row: a scale stop, a pole, or a stamp row, flat in the family group */
|
|
22
14
|
export declare const familyPath: (family: string, leaf: string) => string[];
|
|
23
15
|
export declare const LINK_GROUP = "link";
|
|
24
16
|
export declare const LINK_POSTURES: readonly ["default", "inverse"];
|
|
25
17
|
export type LinkPosture = (typeof LINK_POSTURES)[number];
|
|
26
18
|
export declare const LINK_STATES: readonly ["enabled", "hover", "pressed"];
|
|
27
19
|
export type LinkState = (typeof LINK_STATES)[number];
|
|
20
|
+
/** the step each state is emitted as: rest 0, one step 1, two steps 2 */
|
|
21
|
+
export declare const LINK_STEP: Record<LinkState, number>;
|
|
22
|
+
/** the one word a link row has: its posture at its step (`default-1`) */
|
|
23
|
+
export declare const linkLeaf: (posture: LinkPosture, state: LinkState) => string;
|
|
28
24
|
export declare const linkPath: (posture: LinkPosture, state: LinkState) => string[];
|
|
29
25
|
export declare const ABSOLUTE_GROUP = "absolute";
|
|
30
26
|
export declare const absolutePath: (family: string) => string[];
|
|
@@ -6,7 +6,7 @@ export { themeTokens, tokensFromEmission, readEmission, resolveReferences, type
|
|
|
6
6
|
export { themeToFigma, groupEntries, type FigmaGroup, type FigmaColorToken, type FigmaLeaf, type ThemeInput, } from './engine/figmaRender';
|
|
7
7
|
export { tokensToDtcg, tokenPathOf, descriptionPathOf, type DtcgDocument, type DtcgGroup, type DtcgToken, type DtcgColorValue, type DtcgOptions, } from './engine/dtcgRender';
|
|
8
8
|
export { MODE_SPECS, type ModeSpec } from './engine/requirements/spec';
|
|
9
|
-
export { stopTokenName, tokenOrder, SCALE_STOP_COUNT, PAPER_0, PEN_100, STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED, STAMP_EDGE, STAMP_ON,
|
|
9
|
+
export { stopTokenName, tokenOrder, SCALE_STOP_COUNT, PAPER_0, PEN_100, STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED, STAMP_EDGE, STAMP_ON, cssVarName, figmaPathOf, familyPath, linkLeaf, linkPath, LINK_STEP, absolutePath, LINK_GROUP, LINK_POSTURES, LINK_STATES, ABSOLUTE_GROUP, COLOR_GROUP, type TokenPath, type LinkPosture, type LinkState, } from './engine/tokenNames';
|
|
10
10
|
export { SIGNAL_EMIT_NAME } from './engine/signals';
|
|
11
11
|
export { classifyArchetype, type Archetype } from './engine/archetypes';
|
|
12
12
|
export { SIGNALS, type SignalDef } from './engine/signals';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okchroma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Color-system engine: one brand hex in, a light and dark set of color primitives out, with the contrast requirements solved during generation; emitted as CSS custom properties, DTCG documents, and Figma variables",
|
|
6
6
|
"repository": {
|