okchroma 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,134 +1,95 @@
|
|
|
1
1
|
# OKChroma
|
|
2
2
|
|
|
3
|
-
**A color-system engine.** Give it one
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
`dist/signals.css`, via the semantic layer `tokens/semantic.css`.
|
|
21
|
-
- **Figma variables** — written into a Figma file by the bundled plugin.
|
|
22
|
-
|
|
23
|
-
The live demo and the Figma plugin are previews/front-ends; the engine and its output are
|
|
24
|
-
the product.
|
|
25
|
-
|
|
26
|
-
> The "reserved role per step" model is a conceptual nod to
|
|
3
|
+
**A color-system engine.** Give it one brand hex, or two, and it resolves a complete
|
|
4
|
+
light and dark token system whose contrast requirements are solved during generation,
|
|
5
|
+
then emits it as CSS custom properties, Figma variables, or DTCG requirement tokens.
|
|
6
|
+
|
|
7
|
+
Every family (neutral, brand, brand-alt, critical, warning, positive, info) carries the
|
|
8
|
+
same scale: 3 papers, 4 highlighters, 1 crayon, 1 pencil, 2 pens, plus the stamp (the
|
|
9
|
+
solid fill with its hover and pressed states, its edge, and its text). The neutral adds
|
|
10
|
+
the two poles. Light and dark resolve together and ship on the same names, so a token is
|
|
11
|
+
mapped once and holds for any brand.
|
|
12
|
+
|
|
13
|
+
Contrast is built into the math, not checked afterwards: `crayon-26` reads on every paper
|
|
14
|
+
at 3:1 (the non-text bar), `pencil-47` at 4.5:1, `pen-58` and `pen-70` at 4.5:1 on every
|
|
15
|
+
paper and highlighter in both directions; the stamp's text passes 4.5:1 on the fill and
|
|
16
|
+
brand and signal fills clear APCA Lc 65 on top. Each claim, its scope, and the audit that
|
|
17
|
+
proves it: [Guarantees](https://egerrity.github.io/okchroma/#/docs/guarantees).
|
|
18
|
+
|
|
19
|
+
> The reserved-role-per-stop model is a conceptual nod to
|
|
27
20
|
> [Radix Colors](https://www.radix-ui.com/colors/docs/palette-composition/understanding-the-scale).
|
|
28
|
-
> It is
|
|
29
|
-
|
|
30
|
-
## Quick start
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npm install
|
|
34
|
-
npm run demo:build # generate token CSS + bundle the demo
|
|
35
|
-
npx serve . # then open http://localhost:3000/demo/index.html
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Live editing: `npm run dev` (watch mode). Build the Figma plugin: `npm run plugin:build`,
|
|
39
|
-
then import `plugin/manifest.json` in Figma.
|
|
21
|
+
> It is not a dependency and does not touch the math; the color computation is original.
|
|
40
22
|
|
|
41
|
-
##
|
|
23
|
+
## Install
|
|
42
24
|
|
|
43
25
|
```bash
|
|
44
26
|
npm install okchroma
|
|
45
27
|
```
|
|
46
28
|
|
|
47
|
-
|
|
48
|
-
|
|
29
|
+
ESM and CommonJS builds with TypeScript declarations; no runtime dependencies (the one
|
|
30
|
+
perceptual-distance library the engine uses is bundled in).
|
|
49
31
|
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
```ts
|
|
33
|
+
import { resolveTheme, brandCss, signalsCss, neutralTintHue } from 'okchroma'
|
|
34
|
+
|
|
35
|
+
const theme = resolveTheme({ primaryHex: '#E93D82', name: 'acme', deriveSecondary: true })
|
|
36
|
+
const neutralH = neutralTintHue(theme.themed.scale.brandH)
|
|
37
|
+
const css = brandCss('acme', 'Acme', theme.themed, theme.secondary?.scale ?? null,
|
|
38
|
+
'', 'default', undefined, theme.secondary?.style, false, null, true, neutralH)
|
|
39
|
+
+ '\n' + signalsCss()
|
|
40
|
+
// put css in a stylesheet; set data-brand="acme" on the themed root,
|
|
41
|
+
// and data-theme="dark" on it for dark mode
|
|
52
42
|
```
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`--<signal>-<token>`. Inject the string into a `<style>` tag at runtime or write it to a
|
|
60
|
-
file at build time; `signalsCss()` is brand-independent and emitted once.
|
|
61
|
-
|
|
62
|
-
The same resolved theme feeds the other emitters: `themeToFigma` returns the Figma
|
|
63
|
-
variable payload, and `emitDtcgRamp` returns DTCG JSON carrying the live requirement in
|
|
64
|
-
`$extensions`. ESM and CJS builds ship with TypeScript declarations. The package bundles
|
|
65
|
-
its one runtime dependency (helmlab), so installing it adds nothing transitive.
|
|
66
|
-
|
|
67
|
-
## How it works (30 seconds)
|
|
68
|
-
|
|
69
|
-
Every token is a **requirement the engine solves, not a frozen value**. A pure-data
|
|
70
|
-
declaration (`src/engine/requirements/spec.ts`) states each stop's producer (perceptual
|
|
71
|
-
placement, warm drift, chroma ladder) and its requirements (contrast floors, seam
|
|
72
|
-
separations, on-text rules); a resolver executes it per seed — **produce → require →
|
|
73
|
-
refine**. `resolveBrand(hex)` runs that engine, applies **policy** (status-color
|
|
74
|
-
collisions, signal shifts), and an **emitter** (`cssRender` / `figmaRender`) maps the
|
|
75
|
-
resolved stops onto named tokens and picks light vs dark. The declaration also
|
|
76
|
-
round-trips to DTCG tokens (`$value` fallback + the live requirement in `$extensions`).
|
|
77
|
-
|
|
78
|
-
- **Engine:** `src/engine/*` — pure TypeScript with one runtime dependency (helmlab,
|
|
79
|
-
the P2 adjacency metric), bundled into the npm package at build time.
|
|
80
|
-
- **Entry points:** `resolveBrand` (`src/engine/resolve.ts`) and `generateScale`
|
|
81
|
-
(`src/engine/colorEngine.ts`, an adapter over the resolver — same signature as always).
|
|
82
|
-
- **Build:** `src/build.ts` writes the one static output, `dist/signals.css`. There is no
|
|
83
|
-
brand roster: per-brand CSS is generated live by whichever caller resolves a hex (the
|
|
84
|
-
demo's hex input, a plugin's form field).
|
|
85
|
-
|
|
86
|
-
## The Figma plugin
|
|
87
|
-
|
|
88
|
-
The bundled plugin runs the same engine inside Figma and writes the resolved system into
|
|
89
|
-
your file as variables: **theme** + **mode** collections whose semantic tokens alias onto
|
|
90
|
-
shared primitives — per-brand ramps under `brand/<name>`, with neutrals and signals
|
|
91
|
-
deduplicated across brands, light + dark values on every token.
|
|
92
|
-
|
|
93
|
-
**Install** — from source:
|
|
44
|
+
The Figma tree comes from `themeToFigma`, the DTCG requirement tokens from
|
|
45
|
+
`emitDtcgRamp`. Signatures, the full input, and an end-to-end example:
|
|
46
|
+
[Install and API](https://egerrity.github.io/okchroma/#/docs/install).
|
|
47
|
+
|
|
48
|
+
## Run from source
|
|
94
49
|
|
|
95
50
|
```bash
|
|
96
|
-
npm install
|
|
51
|
+
npm install
|
|
52
|
+
npm run demo:build # writes dist/signals.css and bundles the demo
|
|
53
|
+
npx serve . # open http://localhost:3000/demo/index.html
|
|
54
|
+
npm run dev # watch mode
|
|
97
55
|
```
|
|
98
56
|
|
|
99
|
-
|
|
100
|
-
`
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
> changed shape in that round — `highlight-9` and `on-highlight` were removed and the pen
|
|
104
|
-
> stops renumbered — and this plugin does not yet carry the rename table that migrates an
|
|
105
|
-
> existing Figma file across it, so updating an installed copy would orphan its variable
|
|
106
|
-
> bindings. Building from source is unaffected: a fresh file gets the new shape correctly.
|
|
107
|
-
>
|
|
108
|
-
> The **extended collections** plugin (Figma Enterprise) does carry the migration and is
|
|
109
|
-
> published as normal — **[install it here](https://egerrity.github.io/okchroma/install.html)**.
|
|
110
|
-
|
|
111
|
-
**Use** — name the brand, pick a primary, and Apply. Optional before applying:
|
|
112
|
-
|
|
113
|
-
- **Secondary color** — "+ Add secondary" starts on *From primary* (a pastel derived from
|
|
114
|
-
your primary); type any hex for a custom secondary in **Tint / Pastel / Outline / Exact**
|
|
115
|
-
style.
|
|
116
|
-
- **Neutral color** — *Default* (a touch of primary hue), *Medium* (slightly more tint), *Intense*, or *True grey*.
|
|
117
|
-
- **Contrast standard** — **APCA** (default) or **WCAG**. One profile per collection pair:
|
|
118
|
-
applying the other profile to an existing file forks a clearly-labeled second pair
|
|
119
|
-
(`theme-wcag`/`mode-wcag`) instead of ever mixing values.
|
|
120
|
-
|
|
121
|
-
Re-applying the same brand name updates it in place (after a confirm); a new name adds a
|
|
122
|
-
brand to the same collections — two applies with two brand colors is a multi-brand system
|
|
123
|
-
on shared foundations.
|
|
57
|
+
`npm run typecheck` runs the compiler; the audit gates (`npm run req:audit`,
|
|
58
|
+
`npm run audit:guarantee`, and the rest of `package.json`) each sweep agnostic seeds and
|
|
59
|
+
fail on the worst case. What each proves:
|
|
60
|
+
[How it is verified](https://egerrity.github.io/okchroma/#/docs/guarantees/how-it-is-verified).
|
|
124
61
|
|
|
125
62
|
## Documentation
|
|
126
63
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
64
|
+
The docs site is the source: [egerrity.github.io/okchroma/#/docs](https://egerrity.github.io/okchroma/#/docs).
|
|
65
|
+
|
|
66
|
+
- [Overview](https://egerrity.github.io/okchroma/#/docs/overview): what goes in, what comes out, the token roster.
|
|
67
|
+
- [Output contract](https://egerrity.github.io/okchroma/#/docs/output): the naming grammar, families and prefixes, modes and selectors, a live CSS block and Figma tree.
|
|
68
|
+
- [Guarantees](https://egerrity.github.io/okchroma/#/docs/guarantees): every claim stated exactly, and how it is verified.
|
|
69
|
+
- [How the theme is generated](https://egerrity.github.io/okchroma/#/docs/generation): the pipeline in execution order, with the constants and the code that runs each step.
|
|
70
|
+
- [Signals and companions](https://egerrity.github.io/okchroma/#/docs/signals), [Requirement tokens](https://egerrity.github.io/okchroma/#/docs/token-schema), [Reference](https://egerrity.github.io/okchroma/#/docs/reference).
|
|
71
|
+
|
|
72
|
+
In the repo: [docs/architecture.md](docs/architecture.md) (the maintainer's map: modules,
|
|
73
|
+
pipeline stages, data structures, the extended plugin's zones),
|
|
74
|
+
[docs/scale.md](docs/scale.md) (the scale and its declared targets),
|
|
75
|
+
[docs/schema.md](docs/schema.md) (the requirement-token format in the repo),
|
|
76
|
+
[docs/agents.md](docs/agents.md) (how a coding agent consuming the tokens should read
|
|
77
|
+
them), and [CHANGELOG.md](CHANGELOG.md).
|
|
78
|
+
|
|
79
|
+
## The Figma plugins
|
|
80
|
+
|
|
81
|
+
- **OKChroma Extended** (`plugin-ext/`) is the shipped Figma front-end. It requires the
|
|
82
|
+
Figma desktop app and a Figma Enterprise plan: it writes extended variable collections,
|
|
83
|
+
one base collection with light and dark modes plus one extension per brand that
|
|
84
|
+
overrides only what differs. Download and install steps:
|
|
85
|
+
[install page](https://egerrity.github.io/okchroma/install.html). Build from source with
|
|
86
|
+
`npm run plugin-ext:build`; see [plugin-ext/README.md](plugin-ext/README.md).
|
|
87
|
+
- **OKChroma** (`plugin/`, the community plugin) is withdrawn from download until it
|
|
88
|
+
carries the rename table that migrates an existing file across the scale change of July
|
|
89
|
+
2026. It still builds from source with `npm run plugin:build` and imports from
|
|
90
|
+
`plugin/manifest.json`; a fresh file gets the current shape.
|
|
91
|
+
|
|
92
|
+
The demo and the plugins are front-ends. The product is the engine and what it emits.
|
|
132
93
|
|
|
133
94
|
## License
|
|
134
95
|
|
package/dist-lib/index.cjs
CHANGED
|
@@ -947,9 +947,9 @@ var LIGHT = {
|
|
|
947
947
|
{ stop: 11, rootL: ROOT_L_LIGHT[11], group: groupOf(11), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[11].textMult, textMaxC: SCALE_C_LIGHT[11].textMaxC, chromaFloor: SCALE_C_LIGHT[11].chromaFloor, require: T11 }
|
|
948
948
|
],
|
|
949
949
|
roles: [
|
|
950
|
-
{ role:
|
|
951
|
-
{ role:
|
|
952
|
-
{ role:
|
|
950
|
+
{ role: STAMP_FILL, produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: 0, chromaMult: 1 },
|
|
951
|
+
{ role: STAMP_FILL_HOVER, produce: { hue: "constant", L: "hover", chroma: "brand" }, floorL: 0, chromaMult: 1 },
|
|
952
|
+
{ role: STAMP_FILL_PRESSED, produce: { hue: "constant", L: "pressed", chroma: "brand" }, floorL: 0, chromaMult: 1 }
|
|
953
953
|
],
|
|
954
954
|
ons: ONS
|
|
955
955
|
};
|
|
@@ -979,9 +979,9 @@ var DARK = {
|
|
|
979
979
|
{ stop: 11, rootL: ROOT_L_DARK[11], group: groupOf(11), produce: P_TEXT, chromaMult: SCALE_C_DARK[11].textMult, textMaxC: SCALE_C_DARK[11].textMaxC, chromaFloor: SCALE_C_DARK[11].chromaFloor, require: T11 }
|
|
980
980
|
],
|
|
981
981
|
roles: [
|
|
982
|
-
{ role:
|
|
983
|
-
{ role:
|
|
984
|
-
{ role:
|
|
982
|
+
{ role: STAMP_FILL, produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
|
|
983
|
+
{ role: STAMP_FILL_HOVER, produce: { hue: "constant", L: "hover", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
|
|
984
|
+
{ role: STAMP_FILL_PRESSED, produce: { hue: "constant", L: "pressed", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 }
|
|
985
985
|
],
|
|
986
986
|
ons: ONS
|
|
987
987
|
};
|
|
@@ -2360,6 +2360,8 @@ var toHex = (rgb) => "#" + rgb.map((c) => Math.round(c * 255).toString(16).padSt
|
|
|
2360
2360
|
var CTA_CLEARANCE_CAPS = [0.05, 0.92];
|
|
2361
2361
|
var CHROMATIC_W80_WORST_SHIP_Y = { light: 0.459357, dark: 0.074936 };
|
|
2362
2362
|
var CHROMATIC_P3_WORST_SHIP_Y = { light: 0.836007, dark: 0.016041 };
|
|
2363
|
+
var NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
|
|
2364
|
+
var NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
|
|
2363
2365
|
function resolveRamp(hex, mode, spec, opts) {
|
|
2364
2366
|
const o = hexToOklch(hex);
|
|
2365
2367
|
const seed = { hex, ...o };
|
|
@@ -2371,10 +2373,10 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2371
2373
|
stops: spec.stops.map((s) => s.stop === 10 && s.require?.metric === "wcag" ? { ...s, require: { ...s.require, target: 6.5 } } : s)
|
|
2372
2374
|
};
|
|
2373
2375
|
}
|
|
2374
|
-
const ctaReq = spec.roles.find((r) => r.role ===
|
|
2375
|
-
const hoverReq = spec.roles.find((r) => r.role ===
|
|
2376
|
-
const pressedReq = spec.roles.find((r) => r.role ===
|
|
2377
|
-
if (!ctaReq) throw new Error(
|
|
2376
|
+
const ctaReq = spec.roles.find((r) => r.role === STAMP_FILL);
|
|
2377
|
+
const hoverReq = spec.roles.find((r) => r.role === STAMP_FILL_HOVER);
|
|
2378
|
+
const pressedReq = spec.roles.find((r) => r.role === STAMP_FILL_PRESSED);
|
|
2379
|
+
if (!ctaReq) throw new Error(`spec has no ${STAMP_FILL} role`);
|
|
2378
2380
|
const onFillEnforce = ctx.opts?.enforceOnFillContrast ?? spec.ons.onFill.enforce;
|
|
2379
2381
|
const dctx = mode === "dark" ? buildDarkContext(ctx, ctaReq.floorL) : null;
|
|
2380
2382
|
const stops = [];
|
|
@@ -2393,8 +2395,6 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2393
2395
|
const textGround = ctx.opts?.textGround?.[mode];
|
|
2394
2396
|
const groundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(wcagAnchorStop(req, stop), stop);
|
|
2395
2397
|
const apcaGroundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(declaredAnchor(req), stop);
|
|
2396
|
-
const NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
|
|
2397
|
-
const NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
|
|
2398
2398
|
const maxLForOf = (req, forWhom, withMargin) => {
|
|
2399
2399
|
if (req.metric === "wcag") {
|
|
2400
2400
|
const g2 = groundOf(req, forWhom);
|
|
@@ -2579,13 +2579,13 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2579
2579
|
repelled = true;
|
|
2580
2580
|
}
|
|
2581
2581
|
}
|
|
2582
|
-
cta = emitRole(
|
|
2582
|
+
cta = emitRole(STAMP_FILL, light9L, ctaCFor(light9L) * ctaCMul, ctaH);
|
|
2583
2583
|
const hCFor = (L) => ctx.cAt("light", L, (hoverReq?.chromaMult ?? 1) * ctx.brandC) * ctaCMul;
|
|
2584
2584
|
const pCFor = (L) => ctx.cAt("light", L, (pressedReq?.chromaMult ?? 1) * ctx.brandC) * ctaCMul;
|
|
2585
2585
|
const hL = stateFillL(light9L, "light", 1);
|
|
2586
2586
|
const pL = stateFillL(light9L, "light", 2);
|
|
2587
|
-
ctaHover = emitRole(
|
|
2588
|
-
ctaPressed = emitRole(
|
|
2587
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, hCFor(hL), ctaH);
|
|
2588
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, pCFor(pL), ctaH);
|
|
2589
2589
|
if (light9L !== ctx.scaleL) {
|
|
2590
2590
|
cta.enforced = true;
|
|
2591
2591
|
ctaHover.enforced = true;
|
|
@@ -2608,19 +2608,19 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2608
2608
|
const cta9L = ctx.opts?.darkCtaFlatApp !== void 0 ? flatDarkCtaL(d, (L) => ctx.cAt("dark", L, d.darkC9), ctx.darkCtaH, ctx.opts.darkCtaFlatApp) : d.dark9L;
|
|
2609
2609
|
const dCFor = (L) => ctx.cAt("dark", L, d.darkC9);
|
|
2610
2610
|
const dTrio = (baseL) => [stateFillL(baseL, "dark", 1), stateFillL(baseL, "dark", 2)];
|
|
2611
|
-
cta = emitRole(
|
|
2611
|
+
cta = emitRole(STAMP_FILL, cta9L, dCFor(cta9L), ctx.darkCtaH);
|
|
2612
2612
|
{
|
|
2613
2613
|
const [hL, pL] = dTrio(cta9L);
|
|
2614
|
-
ctaHover = emitRole(
|
|
2615
|
-
ctaPressed = emitRole(
|
|
2614
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2615
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2616
2616
|
}
|
|
2617
2617
|
onFillIsWhite = onFillIsWhiteDarkAt(cta.L, cta.C, cta.H, enforceLc !== void 0 ? false : onFillEnforce);
|
|
2618
2618
|
const enforcedL = enforceLc !== void 0 ? ctaDarkEnforcedLApca(ctx, cta, onFillIsWhite, onFillEnforce, enforceLc) : ctaDarkEnforcedL(ctx, cta, onFillIsWhite, onFillEnforce);
|
|
2619
2619
|
if (enforcedL !== null) {
|
|
2620
|
-
cta = emitRole(
|
|
2620
|
+
cta = emitRole(STAMP_FILL, enforcedL, dCFor(enforcedL), ctx.darkCtaH);
|
|
2621
2621
|
const [hL, pL] = dTrio(enforcedL);
|
|
2622
|
-
ctaHover = emitRole(
|
|
2623
|
-
ctaPressed = emitRole(
|
|
2622
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2623
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2624
2624
|
cta.enforced = true;
|
|
2625
2625
|
ctaHover.enforced = true;
|
|
2626
2626
|
ctaPressed.enforced = true;
|
|
@@ -2629,10 +2629,10 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2629
2629
|
const [capLoL, capHiL] = CTA_CLEARANCE_CAPS;
|
|
2630
2630
|
const clearedL = ctaDarkDualGateL(cta, ctx.darkCtaH, onFillIsWhite, onFillEnforce, coLc + APCA_ENFORCE_MARGIN_LC, capLoL, capHiL);
|
|
2631
2631
|
if (Math.abs(clearedL - cta.L) > 1e-9) {
|
|
2632
|
-
cta = emitRole(
|
|
2632
|
+
cta = emitRole(STAMP_FILL, clearedL, dCFor(clearedL), ctx.darkCtaH);
|
|
2633
2633
|
const [hL, pL] = dTrio(clearedL);
|
|
2634
|
-
ctaHover = emitRole(
|
|
2635
|
-
ctaPressed = emitRole(
|
|
2634
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2635
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2636
2636
|
cta.enforced = true;
|
|
2637
2637
|
ctaHover.enforced = true;
|
|
2638
2638
|
ctaPressed.enforced = true;
|
|
@@ -2641,10 +2641,10 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2641
2641
|
if (ctx.opts?.ctaSolve) {
|
|
2642
2642
|
const exitL = solveDarkCtaExit(cta, dCFor, ctx.darkCtaH, ctx.opts.ctaSolve.redDark, enforceLc, coLc);
|
|
2643
2643
|
if (exitL !== null) {
|
|
2644
|
-
cta = emitRole(
|
|
2644
|
+
cta = emitRole(STAMP_FILL, exitL, dCFor(exitL), ctx.darkCtaH);
|
|
2645
2645
|
const [hL, pL] = dTrio(exitL);
|
|
2646
|
-
ctaHover = emitRole(
|
|
2647
|
-
ctaPressed = emitRole(
|
|
2646
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2647
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2648
2648
|
cta.enforced = true;
|
|
2649
2649
|
ctaHover.enforced = true;
|
|
2650
2650
|
ctaPressed.enforced = true;
|
|
@@ -3977,9 +3977,14 @@ function themeToFigma(r, input) {
|
|
|
3977
3977
|
// src/engine/requirements/dtcg.ts
|
|
3978
3978
|
var EXT_KEY = "org.okchroma.requirement";
|
|
3979
3979
|
var RESOLVER_ID = "okchroma-reqtoken@2";
|
|
3980
|
+
var LEGACY_ROLE_NAMES = {
|
|
3981
|
+
"cta": STAMP_FILL,
|
|
3982
|
+
"cta-hover": STAMP_FILL_HOVER,
|
|
3983
|
+
"cta-pressed": STAMP_FILL_PRESSED
|
|
3984
|
+
};
|
|
3980
3985
|
var hexToValue = (hex) => {
|
|
3981
3986
|
const h = hex.replace("#", "");
|
|
3982
|
-
const c = (i) => parseInt(h.slice(i, i + 2), 16) / 255;
|
|
3987
|
+
const c = (i) => Math.round(parseInt(h.slice(i, i + 2), 16) / 255 * 1e4) / 1e4;
|
|
3983
3988
|
return { colorSpace: "srgb", components: [c(0), c(2), c(4)], alpha: 1, hex: hex.toLowerCase() };
|
|
3984
3989
|
};
|
|
3985
3990
|
function emitDtcgRamp(hex, mode, groupName, spec) {
|
|
@@ -3995,9 +4000,9 @@ function emitDtcgRamp(hex, mode, groupName, spec) {
|
|
|
3995
4000
|
group[String(sp.stop)] = { $type: "color", $value: hexToValue(st.hex), $extensions: { [EXT_KEY]: { resolver: RESOLVER_ID, seed: seedRef, mode, ...sp } } };
|
|
3996
4001
|
}
|
|
3997
4002
|
const roleVal = {
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4003
|
+
[STAMP_FILL]: ramp.roles.cta,
|
|
4004
|
+
[STAMP_FILL_HOVER]: ramp.roles.ctaHover,
|
|
4005
|
+
[STAMP_FILL_PRESSED]: ramp.roles.ctaPressed
|
|
4001
4006
|
};
|
|
4002
4007
|
for (const rr of spec.roles) {
|
|
4003
4008
|
const rv = roleVal[rr.role];
|
|
@@ -4017,6 +4022,7 @@ function parseToken(token) {
|
|
|
4017
4022
|
if (isRole) {
|
|
4018
4023
|
for (const k of ["role", "produce", "floorL", "chromaMult"]) if (req[k] === void 0) throw new Error(`role requirement missing "${k}"`);
|
|
4019
4024
|
}
|
|
4025
|
+
if (isRole && LEGACY_ROLE_NAMES[req.role]) req.role = LEGACY_ROLE_NAMES[req.role];
|
|
4020
4026
|
return { req, seedRef: seed, mode };
|
|
4021
4027
|
}
|
|
4022
4028
|
function resolveDtcgRamp(group) {
|
package/dist-lib/index.mjs
CHANGED
|
@@ -868,9 +868,9 @@ var LIGHT = {
|
|
|
868
868
|
{ stop: 11, rootL: ROOT_L_LIGHT[11], group: groupOf(11), produce: PL_TEXT, chromaMult: SCALE_C_LIGHT[11].textMult, textMaxC: SCALE_C_LIGHT[11].textMaxC, chromaFloor: SCALE_C_LIGHT[11].chromaFloor, require: T11 }
|
|
869
869
|
],
|
|
870
870
|
roles: [
|
|
871
|
-
{ role:
|
|
872
|
-
{ role:
|
|
873
|
-
{ role:
|
|
871
|
+
{ role: STAMP_FILL, produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: 0, chromaMult: 1 },
|
|
872
|
+
{ role: STAMP_FILL_HOVER, produce: { hue: "constant", L: "hover", chroma: "brand" }, floorL: 0, chromaMult: 1 },
|
|
873
|
+
{ role: STAMP_FILL_PRESSED, produce: { hue: "constant", L: "pressed", chroma: "brand" }, floorL: 0, chromaMult: 1 }
|
|
874
874
|
],
|
|
875
875
|
ons: ONS
|
|
876
876
|
};
|
|
@@ -900,9 +900,9 @@ var DARK = {
|
|
|
900
900
|
{ stop: 11, rootL: ROOT_L_DARK[11], group: groupOf(11), produce: P_TEXT, chromaMult: SCALE_C_DARK[11].textMult, textMaxC: SCALE_C_DARK[11].textMaxC, chromaFloor: SCALE_C_DARK[11].chromaFloor, require: T11 }
|
|
901
901
|
],
|
|
902
902
|
roles: [
|
|
903
|
-
{ role:
|
|
904
|
-
{ role:
|
|
905
|
-
{ role:
|
|
903
|
+
{ role: STAMP_FILL, produce: { hue: "constant", L: "anchor", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
|
|
904
|
+
{ role: STAMP_FILL_HOVER, produce: { hue: "constant", L: "hover", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 },
|
|
905
|
+
{ role: STAMP_FILL_PRESSED, produce: { hue: "constant", L: "pressed", chroma: "brand" }, floorL: DARK_CTA_MIN_L, chromaMult: 1 }
|
|
906
906
|
],
|
|
907
907
|
ons: ONS
|
|
908
908
|
};
|
|
@@ -2281,6 +2281,8 @@ var toHex = (rgb) => "#" + rgb.map((c) => Math.round(c * 255).toString(16).padSt
|
|
|
2281
2281
|
var CTA_CLEARANCE_CAPS = [0.05, 0.92];
|
|
2282
2282
|
var CHROMATIC_W80_WORST_SHIP_Y = { light: 0.459357, dark: 0.074936 };
|
|
2283
2283
|
var CHROMATIC_P3_WORST_SHIP_Y = { light: 0.836007, dark: 0.016041 };
|
|
2284
|
+
var NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
|
|
2285
|
+
var NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
|
|
2284
2286
|
function resolveRamp(hex, mode, spec, opts) {
|
|
2285
2287
|
const o = hexToOklch(hex);
|
|
2286
2288
|
const seed = { hex, ...o };
|
|
@@ -2292,10 +2294,10 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2292
2294
|
stops: spec.stops.map((s) => s.stop === 10 && s.require?.metric === "wcag" ? { ...s, require: { ...s.require, target: 6.5 } } : s)
|
|
2293
2295
|
};
|
|
2294
2296
|
}
|
|
2295
|
-
const ctaReq = spec.roles.find((r) => r.role ===
|
|
2296
|
-
const hoverReq = spec.roles.find((r) => r.role ===
|
|
2297
|
-
const pressedReq = spec.roles.find((r) => r.role ===
|
|
2298
|
-
if (!ctaReq) throw new Error(
|
|
2297
|
+
const ctaReq = spec.roles.find((r) => r.role === STAMP_FILL);
|
|
2298
|
+
const hoverReq = spec.roles.find((r) => r.role === STAMP_FILL_HOVER);
|
|
2299
|
+
const pressedReq = spec.roles.find((r) => r.role === STAMP_FILL_PRESSED);
|
|
2300
|
+
if (!ctaReq) throw new Error(`spec has no ${STAMP_FILL} role`);
|
|
2299
2301
|
const onFillEnforce = ctx.opts?.enforceOnFillContrast ?? spec.ons.onFill.enforce;
|
|
2300
2302
|
const dctx = mode === "dark" ? buildDarkContext(ctx, ctaReq.floorL) : null;
|
|
2301
2303
|
const stops = [];
|
|
@@ -2314,8 +2316,6 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2314
2316
|
const textGround = ctx.opts?.textGround?.[mode];
|
|
2315
2317
|
const groundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(wcagAnchorStop(req, stop), stop);
|
|
2316
2318
|
const apcaGroundOf = (req, stop) => textGround && stop >= 9 ? textGround : refOf(declaredAnchor(req), stop);
|
|
2317
|
-
const NEUTRAL_P3_WORST_SHIP_Y = { light: 0.845015, dark: 0.014247 };
|
|
2318
|
-
const NEUTRAL_W80_WORST_SHIP_Y = { light: 0.506433, dark: 0.074262 };
|
|
2319
2319
|
const maxLForOf = (req, forWhom, withMargin) => {
|
|
2320
2320
|
if (req.metric === "wcag") {
|
|
2321
2321
|
const g2 = groundOf(req, forWhom);
|
|
@@ -2500,13 +2500,13 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2500
2500
|
repelled = true;
|
|
2501
2501
|
}
|
|
2502
2502
|
}
|
|
2503
|
-
cta = emitRole(
|
|
2503
|
+
cta = emitRole(STAMP_FILL, light9L, ctaCFor(light9L) * ctaCMul, ctaH);
|
|
2504
2504
|
const hCFor = (L) => ctx.cAt("light", L, (hoverReq?.chromaMult ?? 1) * ctx.brandC) * ctaCMul;
|
|
2505
2505
|
const pCFor = (L) => ctx.cAt("light", L, (pressedReq?.chromaMult ?? 1) * ctx.brandC) * ctaCMul;
|
|
2506
2506
|
const hL = stateFillL(light9L, "light", 1);
|
|
2507
2507
|
const pL = stateFillL(light9L, "light", 2);
|
|
2508
|
-
ctaHover = emitRole(
|
|
2509
|
-
ctaPressed = emitRole(
|
|
2508
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, hCFor(hL), ctaH);
|
|
2509
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, pCFor(pL), ctaH);
|
|
2510
2510
|
if (light9L !== ctx.scaleL) {
|
|
2511
2511
|
cta.enforced = true;
|
|
2512
2512
|
ctaHover.enforced = true;
|
|
@@ -2529,19 +2529,19 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2529
2529
|
const cta9L = ctx.opts?.darkCtaFlatApp !== void 0 ? flatDarkCtaL(d, (L) => ctx.cAt("dark", L, d.darkC9), ctx.darkCtaH, ctx.opts.darkCtaFlatApp) : d.dark9L;
|
|
2530
2530
|
const dCFor = (L) => ctx.cAt("dark", L, d.darkC9);
|
|
2531
2531
|
const dTrio = (baseL) => [stateFillL(baseL, "dark", 1), stateFillL(baseL, "dark", 2)];
|
|
2532
|
-
cta = emitRole(
|
|
2532
|
+
cta = emitRole(STAMP_FILL, cta9L, dCFor(cta9L), ctx.darkCtaH);
|
|
2533
2533
|
{
|
|
2534
2534
|
const [hL, pL] = dTrio(cta9L);
|
|
2535
|
-
ctaHover = emitRole(
|
|
2536
|
-
ctaPressed = emitRole(
|
|
2535
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2536
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2537
2537
|
}
|
|
2538
2538
|
onFillIsWhite = onFillIsWhiteDarkAt(cta.L, cta.C, cta.H, enforceLc !== void 0 ? false : onFillEnforce);
|
|
2539
2539
|
const enforcedL = enforceLc !== void 0 ? ctaDarkEnforcedLApca(ctx, cta, onFillIsWhite, onFillEnforce, enforceLc) : ctaDarkEnforcedL(ctx, cta, onFillIsWhite, onFillEnforce);
|
|
2540
2540
|
if (enforcedL !== null) {
|
|
2541
|
-
cta = emitRole(
|
|
2541
|
+
cta = emitRole(STAMP_FILL, enforcedL, dCFor(enforcedL), ctx.darkCtaH);
|
|
2542
2542
|
const [hL, pL] = dTrio(enforcedL);
|
|
2543
|
-
ctaHover = emitRole(
|
|
2544
|
-
ctaPressed = emitRole(
|
|
2543
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2544
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2545
2545
|
cta.enforced = true;
|
|
2546
2546
|
ctaHover.enforced = true;
|
|
2547
2547
|
ctaPressed.enforced = true;
|
|
@@ -2550,10 +2550,10 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2550
2550
|
const [capLoL, capHiL] = CTA_CLEARANCE_CAPS;
|
|
2551
2551
|
const clearedL = ctaDarkDualGateL(cta, ctx.darkCtaH, onFillIsWhite, onFillEnforce, coLc + APCA_ENFORCE_MARGIN_LC, capLoL, capHiL);
|
|
2552
2552
|
if (Math.abs(clearedL - cta.L) > 1e-9) {
|
|
2553
|
-
cta = emitRole(
|
|
2553
|
+
cta = emitRole(STAMP_FILL, clearedL, dCFor(clearedL), ctx.darkCtaH);
|
|
2554
2554
|
const [hL, pL] = dTrio(clearedL);
|
|
2555
|
-
ctaHover = emitRole(
|
|
2556
|
-
ctaPressed = emitRole(
|
|
2555
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2556
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2557
2557
|
cta.enforced = true;
|
|
2558
2558
|
ctaHover.enforced = true;
|
|
2559
2559
|
ctaPressed.enforced = true;
|
|
@@ -2562,10 +2562,10 @@ function resolveRamp(hex, mode, spec, opts) {
|
|
|
2562
2562
|
if (ctx.opts?.ctaSolve) {
|
|
2563
2563
|
const exitL = solveDarkCtaExit(cta, dCFor, ctx.darkCtaH, ctx.opts.ctaSolve.redDark, enforceLc, coLc);
|
|
2564
2564
|
if (exitL !== null) {
|
|
2565
|
-
cta = emitRole(
|
|
2565
|
+
cta = emitRole(STAMP_FILL, exitL, dCFor(exitL), ctx.darkCtaH);
|
|
2566
2566
|
const [hL, pL] = dTrio(exitL);
|
|
2567
|
-
ctaHover = emitRole(
|
|
2568
|
-
ctaPressed = emitRole(
|
|
2567
|
+
ctaHover = emitRole(STAMP_FILL_HOVER, hL, dCFor(hL), ctx.darkCtaH);
|
|
2568
|
+
ctaPressed = emitRole(STAMP_FILL_PRESSED, pL, dCFor(pL), ctx.darkCtaH);
|
|
2569
2569
|
cta.enforced = true;
|
|
2570
2570
|
ctaHover.enforced = true;
|
|
2571
2571
|
ctaPressed.enforced = true;
|
|
@@ -3898,9 +3898,14 @@ function themeToFigma(r, input) {
|
|
|
3898
3898
|
// src/engine/requirements/dtcg.ts
|
|
3899
3899
|
var EXT_KEY = "org.okchroma.requirement";
|
|
3900
3900
|
var RESOLVER_ID = "okchroma-reqtoken@2";
|
|
3901
|
+
var LEGACY_ROLE_NAMES = {
|
|
3902
|
+
"cta": STAMP_FILL,
|
|
3903
|
+
"cta-hover": STAMP_FILL_HOVER,
|
|
3904
|
+
"cta-pressed": STAMP_FILL_PRESSED
|
|
3905
|
+
};
|
|
3901
3906
|
var hexToValue = (hex) => {
|
|
3902
3907
|
const h = hex.replace("#", "");
|
|
3903
|
-
const c = (i) => parseInt(h.slice(i, i + 2), 16) / 255;
|
|
3908
|
+
const c = (i) => Math.round(parseInt(h.slice(i, i + 2), 16) / 255 * 1e4) / 1e4;
|
|
3904
3909
|
return { colorSpace: "srgb", components: [c(0), c(2), c(4)], alpha: 1, hex: hex.toLowerCase() };
|
|
3905
3910
|
};
|
|
3906
3911
|
function emitDtcgRamp(hex, mode, groupName, spec) {
|
|
@@ -3916,9 +3921,9 @@ function emitDtcgRamp(hex, mode, groupName, spec) {
|
|
|
3916
3921
|
group[String(sp.stop)] = { $type: "color", $value: hexToValue(st.hex), $extensions: { [EXT_KEY]: { resolver: RESOLVER_ID, seed: seedRef, mode, ...sp } } };
|
|
3917
3922
|
}
|
|
3918
3923
|
const roleVal = {
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3924
|
+
[STAMP_FILL]: ramp.roles.cta,
|
|
3925
|
+
[STAMP_FILL_HOVER]: ramp.roles.ctaHover,
|
|
3926
|
+
[STAMP_FILL_PRESSED]: ramp.roles.ctaPressed
|
|
3922
3927
|
};
|
|
3923
3928
|
for (const rr of spec.roles) {
|
|
3924
3929
|
const rv = roleVal[rr.role];
|
|
@@ -3938,6 +3943,7 @@ function parseToken(token) {
|
|
|
3938
3943
|
if (isRole) {
|
|
3939
3944
|
for (const k of ["role", "produce", "floorL", "chromaMult"]) if (req[k] === void 0) throw new Error(`role requirement missing "${k}"`);
|
|
3940
3945
|
}
|
|
3946
|
+
if (isRole && LEGACY_ROLE_NAMES[req.role]) req.role = LEGACY_ROLE_NAMES[req.role];
|
|
3941
3947
|
return { req, seedRef: seed, mode };
|
|
3942
3948
|
}
|
|
3943
3949
|
function resolveDtcgRamp(group) {
|
|
@@ -43,6 +43,7 @@ export type ResolvedRamp = {
|
|
|
43
43
|
};
|
|
44
44
|
};
|
|
45
45
|
export type { ResolveOpts };
|
|
46
|
+
export declare const CTA_CLEARANCE_CAPS: [number, number];
|
|
46
47
|
export declare const CHROMATIC_W80_WORST_SHIP_Y: {
|
|
47
48
|
readonly light: 0.459357;
|
|
48
49
|
readonly dark: 0.074936;
|
|
@@ -51,4 +52,12 @@ export declare const CHROMATIC_P3_WORST_SHIP_Y: {
|
|
|
51
52
|
readonly light: 0.836007;
|
|
52
53
|
readonly dark: 0.016041;
|
|
53
54
|
};
|
|
55
|
+
export declare const NEUTRAL_P3_WORST_SHIP_Y: {
|
|
56
|
+
readonly light: 0.845015;
|
|
57
|
+
readonly dark: 0.014247;
|
|
58
|
+
};
|
|
59
|
+
export declare const NEUTRAL_W80_WORST_SHIP_Y: {
|
|
60
|
+
readonly light: 0.506433;
|
|
61
|
+
readonly dark: 0.074262;
|
|
62
|
+
};
|
|
54
63
|
export declare function resolveRamp(hex: string, mode: 'light' | 'dark', spec?: ModeSpec, opts?: ResolveOpts): ResolvedRamp;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { STAMP_FILL, STAMP_FILL_HOVER, STAMP_FILL_PRESSED } from '../tokenNames';
|
|
1
2
|
export type Group = 'paper' | 'highlighter' | 'crayon' | 'pencil' | 'pen';
|
|
2
3
|
export declare const textLane: (g: string) => boolean;
|
|
3
4
|
export type Producer = {
|
|
@@ -31,7 +32,7 @@ export type StopReq = {
|
|
|
31
32
|
chromaFloor?: number;
|
|
32
33
|
require?: Require;
|
|
33
34
|
};
|
|
34
|
-
export type RoleName =
|
|
35
|
+
export type RoleName = typeof STAMP_FILL | typeof STAMP_FILL_HOVER | typeof STAMP_FILL_PRESSED;
|
|
35
36
|
export type RoleReq = {
|
|
36
37
|
role: RoleName;
|
|
37
38
|
produce: {
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { type GeneratedScale, type ContrastProfile } from './colorEngine';
|
|
2
2
|
import type { SignalDef } from './signals';
|
|
3
|
+
type Side = {
|
|
4
|
+
kind: 'swap';
|
|
5
|
+
note: string;
|
|
6
|
+
baseHex: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'shift';
|
|
9
|
+
note: string;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'none';
|
|
12
|
+
};
|
|
13
|
+
interface SignalShiftRule {
|
|
14
|
+
splitH: number;
|
|
15
|
+
below: Side;
|
|
16
|
+
atOrAbove: Side;
|
|
17
|
+
}
|
|
18
|
+
export declare const SHIFT_RULES: Partial<Record<SignalDef['name'], SignalShiftRule>>;
|
|
3
19
|
export interface ShiftResult {
|
|
4
20
|
scale: GeneratedScale;
|
|
5
21
|
note: string;
|
|
6
22
|
}
|
|
7
23
|
export declare function signalSwapVariants(def: SignalDef, contrastProfile?: ContrastProfile): ShiftResult[];
|
|
8
24
|
export declare function pickSignalShift(brand: GeneratedScale, canonicalSignalScale: GeneratedScale, def: SignalDef, contrastProfile?: ContrastProfile): ShiftResult | null;
|
|
25
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okchroma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Color token engine: one brand hex in, a full WCAG-enforced light+dark token system out (CSS variables, DTCG JSON, Figma variables)",
|
|
6
6
|
"repository": {
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"audit:desc": "esbuild scripts/desc-audit.ts --bundle --platform=node --outfile=dist/desc-audit.js && node dist/desc-audit.js",
|
|
54
54
|
"name-lint": "esbuild scripts/name-lint.ts --bundle --platform=node --outfile=dist/name-lint.js && node dist/name-lint.js",
|
|
55
55
|
"typecheck": "tsc --noEmit",
|
|
56
|
-
"typecheck:ext": "tsc --noEmit -p plugin-ext"
|
|
56
|
+
"typecheck:ext": "tsc --noEmit -p plugin-ext",
|
|
57
|
+
"docs:lint": "esbuild scripts/docs-lint.ts --bundle --platform=node --outfile=dist/docs-lint.js && node dist/docs-lint.js"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@types/node": "^25.9.2",
|