@memberjunction/theme-engine 0.0.0 → 5.49.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,45 +1,52 @@
1
1
  # @memberjunction/theme-engine
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/theme-engine`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
3
+ Framework-agnostic derivation of the full MemberJunction `--mj-*` design-token
4
+ contract from a small set of brand **seeds**. Zero runtime dependencies, so the same
5
+ code runs in the Angular theme builder (live preview) and in a future server-side
6
+ overlay endpoint.
7
+
8
+ A theme is a **brand**; light/dark is the user's mode layered under it. The entity
9
+ stores ~8 seeds — not tokens — and the full contract (OKLCH ramps, semantic tokens,
10
+ state families, dark re-point, chart palette, shadows, radii) is derived at load.
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ import { derive, emitOverlayCss, MJ_DEFAULT_SEEDS } from '@memberjunction/theme-engine';
16
+
17
+ const theme = derive({ primary: '#7c3aed', accent: '#22d3ee', radius: 12 });
18
+
19
+ theme.tokens.light; // resolved semantic tokens (hex) for light mode
20
+ theme.tokens.dark; // resolved semantic tokens (hex) for dark mode
21
+ theme.contrast; // per-mode + hover WCAG report, with clamp suggestions
22
+ emitOverlayCss('acme', theme); // CSS scoped to [data-theme-overlay="acme"]
23
+ ```
24
+
25
+ ## Seeds
26
+
27
+ | Seed | Meaning | Default |
28
+ |---|---|---|
29
+ | `primary` | primary brand hue anchor (→ brand-500) | required |
30
+ | `accent` | accent hue anchor (→ accent-400) | `primary` |
31
+ | `tertiary` | tertiary hue anchor (→ tertiary-500) | `accent` |
32
+ | `neutralChroma` | brand-hue bleed into the gray stack, OKLCH chroma 0..~0.08 (G2) | `0.037` |
33
+ | `vibrancy` | global saturation multiplier for brand ramps | `1` |
34
+ | `radius` | base corner radius in px → `--mj-radius-md` | `8` |
35
+ | `depth` | brand-shadow intensity 0..1 (G3) | `1` |
36
+ | `fontFamily` / `fontFamilyMono` | type stacks (G1) | MJ Inter / JetBrains Mono |
37
+ | `vizPalette` | explicit categorical chart palette override (G4) | derived from brand hue |
38
+
39
+ ## How derivation works
40
+
41
+ Each brand family (brand / accent / tertiary / neutral) carries a **measured OKLCH
42
+ shape** lifted from MJ's own default ramps. A seed supplies the hue and a chroma scale
43
+ relative to the family anchor; the shape is re-hued and its chroma rescaled. Feeding
44
+ `MJ_DEFAULT_SEEDS` therefore reproduces MJ's `_tokens.scss` within ~0.03 OKLab ΔE
45
+ (verified by the test suite), while any other hue inherits MJ's proven, perceptually
46
+ uniform lightness structure.
47
+
48
+ The emitted overlay only redefines the **primitive** ramps (plus viz, shadow, font,
49
+ radius). Every semantic token and the entire `[data-theme="dark"]` block in
50
+ `_tokens.scss` are `var(--mj-color-*)` references, so overriding primitives cascades
51
+ into both modes automatically — the dark re-point and hover families come for free,
52
+ with no parallel token mapping that could drift from the base stylesheet.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @fileoverview Color math for the theme engine: sRGB <-> OKLab <-> OKLCH
3
+ * conversions (Bjorn Ottosson's OKLab), gamut clamping, and WCAG 2.x contrast.
4
+ * Dependency-free so the derivation module stays importable in any runtime.
5
+ * @module @memberjunction/theme-engine
6
+ */
7
+ /** A color in the OKLCH space. `l` 0..1, `c` >= 0 (chroma), `h` 0..360 degrees. */
8
+ export interface OKLCH {
9
+ l: number;
10
+ c: number;
11
+ h: number;
12
+ }
13
+ /** Parse `#rgb` / `#rrggbb` into 0..255 channels. Throws on anything else. */
14
+ export declare function parseHex(hex: string): {
15
+ r: number;
16
+ g: number;
17
+ b: number;
18
+ };
19
+ /** Convert a hex color to OKLCH. */
20
+ export declare function hexToOKLCH(hex: string): OKLCH;
21
+ /**
22
+ * Convert OKLCH to a hex string. If the color falls outside the sRGB gamut we
23
+ * reduce chroma toward the achromatic axis (preserving L and H) until it fits,
24
+ * then hard-clamp any residual channel error. This keeps hues perceptually
25
+ * stable instead of the hue-shifting that naive per-channel clamping causes.
26
+ */
27
+ export declare function oklchToHex(color: OKLCH): string;
28
+ /** WCAG 2.x relative luminance (0..1) of a hex color. */
29
+ export declare function relativeLuminance(hex: string): number;
30
+ /** WCAG 2.x contrast ratio (1..21) between two hex colors. */
31
+ export declare function contrastRatio(a: string, b: string): number;
32
+ /** Perceptual distance (Euclidean in OKLab) between two hex colors. */
33
+ export declare function deltaEOK(a: string, b: string): number;
34
+ /** Mix two hex colors in linear-light space; `t` 0..1 is the weight of `b`.
35
+ * A non-finite `t` is treated as 0 (returns `a`) rather than emitting "#NaN…". */
36
+ export declare function mixHex(a: string, b: string, t: number): string;
37
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,mFAAmF;AACnF,MAAM,WAAW,KAAK;IACpB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAWD,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAOzE;AA6CD,oCAAoC;AACpC,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAM7C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAiC/C;AAED,yDAAyD;AACzD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;mFACmF;AACnF,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAO9D"}
package/dist/color.js ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @fileoverview Color math for the theme engine: sRGB <-> OKLab <-> OKLCH
3
+ * conversions (Bjorn Ottosson's OKLab), gamut clamping, and WCAG 2.x contrast.
4
+ * Dependency-free so the derivation module stays importable in any runtime.
5
+ * @module @memberjunction/theme-engine
6
+ */
7
+ const clamp01 = (x) => (x < 0 ? 0 : x > 1 ? 1 : x);
8
+ /** Parse `#rgb` / `#rrggbb` into 0..255 channels. Throws on anything else. */
9
+ export function parseHex(hex) {
10
+ const m = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.exec(hex.trim());
11
+ if (!m)
12
+ throw new Error(`Not a hex color: "${hex}"`);
13
+ let h = m[1];
14
+ if (h.length === 3)
15
+ h = h.split('').map((c) => c + c).join('');
16
+ const n = parseInt(h, 16);
17
+ return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
18
+ }
19
+ const toHex2 = (n) => Math.round(clamp01(n) * 255).toString(16).padStart(2, '0');
20
+ /** sRGB gamma-encoded channel (0..1) -> linear. */
21
+ const srgbToLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
22
+ /** Linear channel (0..1) -> sRGB gamma-encoded. */
23
+ const linearToSrgb = (c) => c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
24
+ function hexToLinear(hex) {
25
+ const { r, g, b } = parseHex(hex);
26
+ return { r: srgbToLinear(r / 255), g: srgbToLinear(g / 255), b: srgbToLinear(b / 255) };
27
+ }
28
+ function linearToOKLab(rgb) {
29
+ const l = 0.4122214708 * rgb.r + 0.5363325363 * rgb.g + 0.0514459929 * rgb.b;
30
+ const m = 0.2119034982 * rgb.r + 0.6806995451 * rgb.g + 0.1073969566 * rgb.b;
31
+ const s = 0.0883024619 * rgb.r + 0.2817188376 * rgb.g + 0.6299787005 * rgb.b;
32
+ const l_ = Math.cbrt(l);
33
+ const m_ = Math.cbrt(m);
34
+ const s_ = Math.cbrt(s);
35
+ return {
36
+ L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
37
+ a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
38
+ b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,
39
+ };
40
+ }
41
+ function okLabToLinear(L, a, b) {
42
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
43
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
44
+ const s_ = L - 0.0894841775 * a - 1.291485548 * b;
45
+ const l = l_ * l_ * l_;
46
+ const m = m_ * m_ * m_;
47
+ const s = s_ * s_ * s_;
48
+ return {
49
+ r: 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
50
+ g: -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
51
+ b: -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s,
52
+ };
53
+ }
54
+ /** Convert a hex color to OKLCH. */
55
+ export function hexToOKLCH(hex) {
56
+ const { L, a, b } = linearToOKLab(hexToLinear(hex));
57
+ const c = Math.sqrt(a * a + b * b);
58
+ let h = (Math.atan2(b, a) * 180) / Math.PI;
59
+ if (h < 0)
60
+ h += 360;
61
+ return { l: L, c, h };
62
+ }
63
+ /**
64
+ * Convert OKLCH to a hex string. If the color falls outside the sRGB gamut we
65
+ * reduce chroma toward the achromatic axis (preserving L and H) until it fits,
66
+ * then hard-clamp any residual channel error. This keeps hues perceptually
67
+ * stable instead of the hue-shifting that naive per-channel clamping causes.
68
+ */
69
+ export function oklchToHex(color) {
70
+ // Public surface: non-finite components (NaN/±Infinity from upstream math on
71
+ // degenerate inputs) would otherwise propagate into a "#NaNNaNNaN" string.
72
+ const l = Number.isFinite(color.l) ? color.l : 0;
73
+ let c = Number.isFinite(color.c) ? Math.max(0, color.c) : 0;
74
+ const h = Number.isFinite(color.h) ? color.h : 0;
75
+ const hRad = (h * Math.PI) / 180;
76
+ const cos = Math.cos(hRad);
77
+ const sin = Math.sin(hRad);
78
+ const fits = (chroma) => {
79
+ const rgb = okLabToLinear(l, chroma * cos, chroma * sin);
80
+ const eps = 1e-4;
81
+ if (rgb.r >= -eps && rgb.r <= 1 + eps && rgb.g >= -eps && rgb.g <= 1 + eps && rgb.b >= -eps && rgb.b <= 1 + eps) {
82
+ return rgb;
83
+ }
84
+ return null;
85
+ };
86
+ let rgb = fits(c);
87
+ if (!rgb) {
88
+ // Binary-search the largest in-gamut chroma at this L/H.
89
+ let lo = 0;
90
+ let hi = c;
91
+ for (let i = 0; i < 24; i++) {
92
+ const mid = (lo + hi) / 2;
93
+ if (fits(mid))
94
+ lo = mid;
95
+ else
96
+ hi = mid;
97
+ }
98
+ c = lo;
99
+ rgb = okLabToLinear(l, c * cos, c * sin);
100
+ }
101
+ return `#${toHex2(linearToSrgb(rgb.r))}${toHex2(linearToSrgb(rgb.g))}${toHex2(linearToSrgb(rgb.b))}`;
102
+ }
103
+ /** WCAG 2.x relative luminance (0..1) of a hex color. */
104
+ export function relativeLuminance(hex) {
105
+ const { r, g, b } = parseHex(hex);
106
+ const lin = (v) => srgbToLinear(v / 255);
107
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
108
+ }
109
+ /** WCAG 2.x contrast ratio (1..21) between two hex colors. */
110
+ export function contrastRatio(a, b) {
111
+ const la = relativeLuminance(a);
112
+ const lb = relativeLuminance(b);
113
+ const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
114
+ return (hi + 0.05) / (lo + 0.05);
115
+ }
116
+ /** Perceptual distance (Euclidean in OKLab) between two hex colors. */
117
+ export function deltaEOK(a, b) {
118
+ const A = linearToOKLab(hexToLinear(a));
119
+ const B = linearToOKLab(hexToLinear(b));
120
+ return Math.sqrt((A.L - B.L) ** 2 + (A.a - B.a) ** 2 + (A.b - B.b) ** 2);
121
+ }
122
+ /** Mix two hex colors in linear-light space; `t` 0..1 is the weight of `b`.
123
+ * A non-finite `t` is treated as 0 (returns `a`) rather than emitting "#NaN…". */
124
+ export function mixHex(a, b, t) {
125
+ const A = hexToLinear(a);
126
+ const B = hexToLinear(b);
127
+ const k = clamp01(Number.isFinite(t) ? t : 0);
128
+ return `#${toHex2(linearToSrgb(A.r + (B.r - A.r) * k))}${toHex2(linearToSrgb(A.g + (B.g - A.g) * k))}${toHex2(linearToSrgb(A.b + (B.b - A.b) * k))}`;
129
+ }
130
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","sourceRoot":"","sources":["../src/color.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview Per-mode accessibility validation (proposal 8 / 16.4.4). Runs WCAG
3
+ * contrast on the brand-critical foreground/background pairs — including hover
4
+ * states — and, when a pair fails, computes a concrete clamp: the nearest lightness
5
+ * for the offending color that reaches the target ratio.
6
+ * @module @memberjunction/theme-engine
7
+ */
8
+ /** WCAG AA thresholds. Normal text 4.5:1; large text / UI affordances 3:1. */
9
+ export declare const AA_TEXT = 4.5;
10
+ export declare const AA_LARGE = 3;
11
+ /** One evaluated contrast pair. */
12
+ export interface ContrastCheck {
13
+ /** Human-readable pair name, e.g. "text-on-primary (hover)". */
14
+ name: string;
15
+ /** Foreground hex. */
16
+ fg: string;
17
+ /** Background hex. */
18
+ bg: string;
19
+ /** Achieved ratio (1..21). */
20
+ ratio: number;
21
+ /** Required ratio for this pair. */
22
+ required: number;
23
+ passes: boolean;
24
+ /**
25
+ * When failing, a suggested replacement for `clampTarget` ('bg' or 'fg') that
26
+ * reaches `required` by shifting lightness, or null if unreachable in gamut.
27
+ */
28
+ suggestion?: {
29
+ adjust: 'fg' | 'bg';
30
+ hex: string;
31
+ } | null;
32
+ }
33
+ /** Full report across both user modes. `passes` is true only if every pair passes. */
34
+ export interface ContrastReport {
35
+ light: ContrastCheck[];
36
+ dark: ContrastCheck[];
37
+ passes: boolean;
38
+ }
39
+ /** A pair to evaluate; `clamp` names which side to move if it fails. */
40
+ export interface PairSpec {
41
+ name: string;
42
+ fg: string;
43
+ bg: string;
44
+ required: number;
45
+ clamp: 'fg' | 'bg';
46
+ }
47
+ /** Build and evaluate the report from resolved per-mode token maps. */
48
+ export declare function buildContrastReport(light: Record<string, string>, dark: Record<string, string>): ContrastReport;
49
+ //# sourceMappingURL=contrast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contrast.d.ts","sourceRoot":"","sources":["../src/contrast.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,8EAA8E;AAC9E,eAAO,MAAM,OAAO,MAAM,CAAC;AAC3B,eAAO,MAAM,QAAQ,IAAI,CAAC;AAE1B,mCAAmC;AACnC,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,IAAI,GAAG,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC1D;AAED,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wEAAwE;AACxE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB;AA2CD,uEAAuE;AACvE,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,cAAc,CAahB"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @fileoverview Per-mode accessibility validation (proposal 8 / 16.4.4). Runs WCAG
3
+ * contrast on the brand-critical foreground/background pairs — including hover
4
+ * states — and, when a pair fails, computes a concrete clamp: the nearest lightness
5
+ * for the offending color that reaches the target ratio.
6
+ * @module @memberjunction/theme-engine
7
+ */
8
+ import { contrastRatio, hexToOKLCH, oklchToHex, relativeLuminance } from './color.js';
9
+ /** WCAG AA thresholds. Normal text 4.5:1; large text / UI affordances 3:1. */
10
+ export const AA_TEXT = 4.5;
11
+ export const AA_LARGE = 3;
12
+ /**
13
+ * Search OKLCH lightness of `color` (away from `other`'s luminance) for the nearest
14
+ * value that reaches `target` contrast against `other`. Returns null if unreachable.
15
+ */
16
+ function clampLightness(color, other, target) {
17
+ const base = hexToOKLCH(color);
18
+ // Move away from the other color's luminance: if we're already the darker of the
19
+ // pair, get darker still; otherwise get lighter. (Moving toward it lowers contrast.)
20
+ const goDarker = relativeLuminance(color) <= relativeLuminance(other);
21
+ const steps = 100;
22
+ for (let i = 1; i <= steps; i++) {
23
+ const l = goDarker ? base.l - (base.l * i) / steps : base.l + ((1 - base.l) * i) / steps;
24
+ const candidate = oklchToHex({ l, c: base.c, h: base.h });
25
+ if (contrastRatio(candidate, other) >= target)
26
+ return candidate;
27
+ }
28
+ return null;
29
+ }
30
+ /** Evaluate one list of pairs. */
31
+ function evaluate(pairs) {
32
+ return pairs.map((p) => {
33
+ const ratio = contrastRatio(p.fg, p.bg);
34
+ const passes = ratio >= p.required;
35
+ const check = {
36
+ name: p.name,
37
+ fg: p.fg,
38
+ bg: p.bg,
39
+ ratio: Math.round(ratio * 100) / 100,
40
+ required: p.required,
41
+ passes,
42
+ };
43
+ if (!passes) {
44
+ const moving = p.clamp === 'fg' ? p.fg : p.bg;
45
+ const against = p.clamp === 'fg' ? p.bg : p.fg;
46
+ const hex = clampLightness(moving, against, p.required);
47
+ check.suggestion = hex ? { adjust: p.clamp, hex } : null;
48
+ }
49
+ return check;
50
+ });
51
+ }
52
+ /** Build and evaluate the report from resolved per-mode token maps. */
53
+ export function buildContrastReport(light, dark) {
54
+ const pairsFor = (t) => [
55
+ { name: 'text-on-primary', fg: t['--mj-brand-on-primary'], bg: t['--mj-brand-primary'], required: AA_LARGE, clamp: 'bg' },
56
+ { name: 'text-on-primary (hover)', fg: t['--mj-brand-on-primary'], bg: t['--mj-brand-primary-hover'], required: AA_LARGE, clamp: 'bg' },
57
+ { name: 'text-on-accent', fg: t['--mj-brand-on-accent'], bg: t['--mj-brand-accent'], required: AA_LARGE, clamp: 'bg' },
58
+ { name: 'text-primary-on-surface', fg: t['--mj-text-primary'], bg: t['--mj-bg-surface'], required: AA_TEXT, clamp: 'fg' },
59
+ { name: 'text-secondary-on-surface', fg: t['--mj-text-secondary'], bg: t['--mj-bg-surface'], required: AA_TEXT, clamp: 'fg' },
60
+ { name: 'link-on-surface', fg: t['--mj-text-link'], bg: t['--mj-bg-surface'], required: AA_TEXT, clamp: 'fg' },
61
+ { name: 'link-on-surface (hover)', fg: t['--mj-text-link-hover'], bg: t['--mj-bg-surface'], required: AA_TEXT, clamp: 'fg' },
62
+ ];
63
+ const l = evaluate(pairsFor(light));
64
+ const d = evaluate(pairsFor(dark));
65
+ return { light: l, dark: d, passes: [...l, ...d].every((c) => c.passes) };
66
+ }
67
+ //# sourceMappingURL=contrast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contrast.js","sourceRoot":"","sources":["../src/contrast.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @fileoverview The single derivation function shared by save, live preview, and
3
+ * validation (org theming decision #4). Given ~8 brand seeds it produces the full
4
+ * --mj-* token contract: generated OKLCH ramps, resolved light/dark semantic maps,
5
+ * a brand-derived chart palette (G4), brand-tinted shadows (G3), the font token (G1),
6
+ * shape radii, an a11y contrast report, and the minimal CSS overlay to emit at runtime.
7
+ *
8
+ * Overlay strategy: the emitted overlay only redefines the PRIMITIVE ramps (plus viz,
9
+ * shadow, font, radius). Every semantic token and the entire [data-theme="dark"] block
10
+ * in _tokens.scss are `var(--mj-color-*)` references, so overriding primitives cascades
11
+ * into both modes automatically — the dark re-point and hover families come for free,
12
+ * with no risk of a parallel mapping diverging from the base stylesheet.
13
+ * @module @memberjunction/theme-engine
14
+ */
15
+ import { ContrastReport } from './contrast.js';
16
+ import { ResolvedSeeds, ThemeSeeds } from './seeds.js';
17
+ /** Fully derived theme: ramps, resolved per-mode maps, overlay vars, and a11y report. */
18
+ export interface DerivedTheme {
19
+ seeds: ResolvedSeeds;
20
+ /** Generated primitive ramps, keyed by full CSS var name (`--mj-color-brand-500`). */
21
+ primitives: Record<string, string>;
22
+ /** Resolved semantic tokens as hex, per user mode (the M2 token cache / preview feed). */
23
+ tokens: {
24
+ light: Record<string, string>;
25
+ dark: Record<string, string>;
26
+ };
27
+ /** Minimal token overrides emitted into the runtime overlay. */
28
+ overlayVars: Record<string, string>;
29
+ contrast: ContrastReport;
30
+ }
31
+ /**
32
+ * Derive the full theme from brand seeds. Feeding {@link MJ_DEFAULT_SEEDS} reproduces
33
+ * MJ's default _tokens.scss within perceptual tolerance.
34
+ */
35
+ export declare function derive(seeds?: ThemeSeeds): DerivedTheme;
36
+ /** Optional advanced-customization layer applied on top of the seed-derived tokens. */
37
+ export interface OverlayOptions {
38
+ /** Per-token overrides merged over the derived overlay vars (override wins). Keys are
39
+ * `--mj-*` custom-property names; applied in the same mode-agnostic overlay block. */
40
+ overrides?: Record<string, string> | null;
41
+ /** Raw CSS appended after the token block, auto-scoped under the overlay selector via
42
+ * native CSS nesting so it only applies when this theme is active. Escape hatch. */
43
+ customCss?: string | null;
44
+ }
45
+ /**
46
+ * Serialize a derived theme into overlay CSS scoped to `[data-theme-overlay="<id>"]`.
47
+ * A single block suffices: it overrides primitives that _tokens.scss's semantic and
48
+ * dark blocks reference, so both modes re-point automatically.
49
+ *
50
+ * The optional advanced layer (decision: seed-first, with an escape hatch) is applied
51
+ * last: `overrides` merge over the derived vars; `customCss` is appended, wrapped in the
52
+ * overlay selector so raw rules stay scoped to this theme.
53
+ */
54
+ export declare function emitOverlayCss(themeId: string, derived: DerivedTheme, options?: OverlayOptions): string;
55
+ /**
56
+ * Scope raw custom CSS under `selector`, but hoist at-rules that MUST live at the
57
+ * stylesheet top level (`@keyframes`, `@font-face`, `@property`) out of the wrapper —
58
+ * CSS nesting forbids them inside a style rule, so nesting them would silently break
59
+ * animations/fonts. Everything else is wrapped so it only applies to this theme.
60
+ *
61
+ * `@import` is STRIPPED entirely, not hoisted: a scoped theme overlay has no
62
+ * legitimate need to pull another stylesheet, an org-wide `@import` of a remote URL
63
+ * is a cross-origin request from every user's session (data-exfiltration surface),
64
+ * and a hoisted `@import` would be dead anyway — CSS ignores `@import` after any
65
+ * other rule, and the overlay Blob always begins with the token block.
66
+ */
67
+ export declare function emitScopedCustomCss(selector: string, css: string): string;
68
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAuB,cAAc,EAAE,MAAM,eAAe,CAAC;AAUpE,OAAO,EAAoB,aAAa,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvF,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,0FAA0F;IAC1F,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACxE,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAwFD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,GAAE,UAA6B,GAAG,YAAY,CA4DzE;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B;2FACuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC1C;yFACqF;IACrF,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAW3G;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQzE"}