@memberjunction/theme-engine 0.0.0 → 5.50.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/dist/derive.js ADDED
@@ -0,0 +1,238 @@
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 { mixHex, parseHex } from './color.js';
16
+ import { buildContrastReport } from './contrast.js';
17
+ import { ACCENT_SHAPE, BRAND_SHAPE, FAMILY_ANCHOR, generateBrandRamp, generateNeutralRamp, TERTIARY_SHAPE, } from './ramps.js';
18
+ import { hexToOKLCH, oklchToHex } from './color.js';
19
+ import { MJ_DEFAULT_SEEDS, resolveSeeds } from './seeds.js';
20
+ /** Fixed (non-brand-derived) status ramp steps referenced by the semantic maps. */
21
+ const STATUS = {
22
+ successBg: '#f0fdf4', success: '#22c55e', successText: '#15803d', successText100: '#dcfce7',
23
+ warningBg: '#fffbeb', warning: '#f59e0b', warningText: '#b45309', warningText100: '#fef3c7',
24
+ errorBg: '#fef2f2', error: '#ef4444', errorText: '#b91c1c', errorText100: '#fee2e2', error400: '#f87171',
25
+ infoBg: '#eff6ff', info: '#3b82f6', infoText: '#1d4ed8', infoText100: '#dbeafe',
26
+ };
27
+ /** Build the resolved semantic map for one mode from the generated primitive ramps. */
28
+ function resolveSemantics(brand, accent, tertiary, neutral, mode) {
29
+ if (mode === 'light') {
30
+ return {
31
+ '--mj-bg-page': neutral[50], '--mj-bg-surface': neutral[0], '--mj-bg-surface-elevated': neutral[0],
32
+ '--mj-bg-surface-card': neutral[50], '--mj-bg-surface-sunken': neutral[100],
33
+ '--mj-bg-surface-hover': neutral[100], '--mj-bg-surface-active': neutral[200],
34
+ '--mj-text-primary': neutral[800], '--mj-text-secondary': neutral[600], '--mj-text-muted': neutral[500],
35
+ '--mj-text-disabled': neutral[400], '--mj-text-inverse': neutral[0],
36
+ '--mj-text-link': brand[500], '--mj-text-link-hover': brand[600],
37
+ '--mj-border-default': neutral[200], '--mj-border-subtle': neutral[100], '--mj-border-strong': neutral[300],
38
+ '--mj-border-focus': brand[500], '--mj-border-error': STATUS.error,
39
+ '--mj-brand-primary': brand[500], '--mj-brand-primary-hover': brand[600], '--mj-brand-primary-active': brand[700],
40
+ '--mj-brand-primary-light': brand[350], '--mj-brand-secondary': brand[900], '--mj-brand-on-primary': neutral[0],
41
+ '--mj-brand-accent': accent[400], '--mj-brand-accent-hover': accent[500], '--mj-brand-accent-active': accent[600],
42
+ '--mj-brand-accent-subtle': accent[50], '--mj-brand-on-accent': neutral[900],
43
+ '--mj-brand-tertiary': tertiary[500], '--mj-brand-tertiary-hover': tertiary[600],
44
+ '--mj-brand-tertiary-active': tertiary[700], '--mj-brand-tertiary-subtle': tertiary[50], '--mj-brand-on-tertiary': neutral[0],
45
+ '--mj-status-success': STATUS.success, '--mj-status-success-bg': STATUS.successBg, '--mj-status-success-text': STATUS.successText,
46
+ '--mj-status-warning': STATUS.warning, '--mj-status-warning-bg': STATUS.warningBg, '--mj-status-warning-text': STATUS.warningText,
47
+ '--mj-status-error': STATUS.error, '--mj-status-error-bg': STATUS.errorBg, '--mj-status-error-text': STATUS.errorText,
48
+ '--mj-status-info': STATUS.info, '--mj-status-info-bg': STATUS.infoBg, '--mj-status-info-text': STATUS.infoText,
49
+ };
50
+ }
51
+ return {
52
+ '--mj-bg-page': neutral[900], '--mj-bg-surface': neutral[800], '--mj-bg-surface-elevated': neutral[700],
53
+ // Card sits between surface (800) and elevated (700). The 0.32 weight is calibrated
54
+ // so MJ's default neutrals reproduce the historical #253347 (ΔE ≈ 0.011); the base
55
+ // stylesheet's dark block uses the equivalent color-mix(in srgb-linear, 800 68%, 700)
56
+ // so preview and runtime stay in exact lockstep under a brand overlay.
57
+ '--mj-bg-surface-card': mixHex(neutral[800], neutral[700], 0.32), '--mj-bg-surface-sunken': neutral[950],
58
+ '--mj-bg-surface-hover': neutral[600], '--mj-bg-surface-active': neutral[500],
59
+ '--mj-text-primary': neutral[100], '--mj-text-secondary': neutral[300], '--mj-text-muted': neutral[400],
60
+ '--mj-text-disabled': neutral[600], '--mj-text-inverse': neutral[900],
61
+ '--mj-text-link': brand[300], '--mj-text-link-hover': brand[200],
62
+ '--mj-border-default': neutral[700], '--mj-border-subtle': neutral[800], '--mj-border-strong': neutral[600],
63
+ '--mj-border-focus': brand[400], '--mj-border-error': STATUS.error400,
64
+ '--mj-brand-primary': brand[400], '--mj-brand-primary-hover': brand[300], '--mj-brand-primary-active': brand[200],
65
+ '--mj-brand-primary-light': brand[350], '--mj-brand-secondary': brand[900], '--mj-brand-on-primary': neutral[0],
66
+ '--mj-brand-accent': accent[300], '--mj-brand-accent-hover': accent[200], '--mj-brand-accent-active': accent[100],
67
+ '--mj-brand-accent-subtle': `color-mix(in srgb, ${accent[400]} 15%, transparent)`, '--mj-brand-on-accent': neutral[900],
68
+ '--mj-brand-tertiary': tertiary[400], '--mj-brand-tertiary-hover': tertiary[300], '--mj-brand-tertiary-active': tertiary[200],
69
+ '--mj-brand-tertiary-subtle': `color-mix(in srgb, ${tertiary[500]} 15%, transparent)`, '--mj-brand-on-tertiary': neutral[0],
70
+ '--mj-status-success': STATUS.success, '--mj-status-success-bg': `rgba(${rgbTriplet(STATUS.success)}, 0.15)`, '--mj-status-success-text': STATUS.successText100,
71
+ '--mj-status-warning': STATUS.warning, '--mj-status-warning-bg': `rgba(${rgbTriplet(STATUS.warning)}, 0.15)`, '--mj-status-warning-text': STATUS.warningText100,
72
+ '--mj-status-error': STATUS.error, '--mj-status-error-bg': `rgba(${rgbTriplet(STATUS.error)}, 0.15)`, '--mj-status-error-text': STATUS.errorText100,
73
+ '--mj-status-info': STATUS.info, '--mj-status-info-bg': `rgba(${rgbTriplet(STATUS.info)}, 0.15)`, '--mj-status-info-text': STATUS.infoText100,
74
+ };
75
+ }
76
+ /** Derive a categorical chart palette (G4) by rotating hue around the brand. */
77
+ function deriveVizPalette(brandHueDeg, override) {
78
+ if (override && override.length) {
79
+ const out = override.slice(0, 10);
80
+ while (out.length < 10)
81
+ out.push(out[out.length % override.length]);
82
+ return out;
83
+ }
84
+ // Ten evenly-spaced hues at a categorical-friendly lightness/chroma, anchored at brand.
85
+ return Array.from({ length: 10 }, (_, i) => oklchToHex({ l: 0.63, c: 0.15, h: (brandHueDeg + i * 36) % 360 }));
86
+ }
87
+ /** `#rrggbb` -> `r, g, b` for rgba() shadow emission. */
88
+ function rgbTriplet(hex) {
89
+ const { r, g, b } = parseHex(hex);
90
+ return `${r}, ${g}, ${b}`;
91
+ }
92
+ /** Round to at most 2 decimals and drop trailing zeros. */
93
+ const px = (n) => `${Math.round(n)}px`;
94
+ /**
95
+ * Derive the full theme from brand seeds. Feeding {@link MJ_DEFAULT_SEEDS} reproduces
96
+ * MJ's default _tokens.scss within perceptual tolerance.
97
+ */
98
+ export function derive(seeds = MJ_DEFAULT_SEEDS) {
99
+ const s = resolveSeeds(seeds);
100
+ const brandHue = hexToOKLCH(s.primary).h;
101
+ const brand = generateBrandRamp(BRAND_SHAPE, FAMILY_ANCHOR.brand, s.primary, s.vibrancy);
102
+ const accent = generateBrandRamp(ACCENT_SHAPE, FAMILY_ANCHOR.accent, s.accent, s.vibrancy);
103
+ const tertiary = generateBrandRamp(TERTIARY_SHAPE, FAMILY_ANCHOR.tertiary, s.tertiary, s.vibrancy);
104
+ const neutral = generateNeutralRamp(brandHue, s.neutralChroma);
105
+ const primitives = {};
106
+ const addRamp = (name, ramp) => {
107
+ for (const step of Object.keys(ramp))
108
+ primitives[`--mj-color-${name}-${step}`] = ramp[Number(step)];
109
+ };
110
+ addRamp('brand', brand);
111
+ addRamp('accent', accent);
112
+ addRamp('tertiary', tertiary);
113
+ addRamp('neutral', neutral);
114
+ const viz = deriveVizPalette(brandHue, s.vizPalette);
115
+ const vizVars = {};
116
+ viz.forEach((hex, i) => (vizVars[`--mj-viz-${i + 1}`] = hex));
117
+ // Brand-tinted shadows (G3): re-color MJ's brand shadow tokens; alpha scaled by depth.
118
+ const brandRgb = rgbTriplet(brand[500]);
119
+ const shadowVars = {
120
+ '--mj-shadow-brand-sm': `0 2px 8px rgba(${brandRgb}, ${(0.3 * s.depth).toFixed(2)})`,
121
+ '--mj-shadow-brand-md': `0 4px 12px rgba(${brandRgb}, ${(0.4 * s.depth).toFixed(2)})`,
122
+ };
123
+ // Shape (radius) and type (font, G1).
124
+ const shapeVars = {
125
+ '--mj-radius-sm': px(s.radius / 2),
126
+ '--mj-radius-md': px(s.radius),
127
+ '--mj-radius-lg': px(s.radius * 1.5),
128
+ '--mj-radius-xl': px(s.radius * 2),
129
+ '--mj-radius-2xl': px(s.radius * 3),
130
+ };
131
+ const typeVars = {
132
+ '--mj-font-family': s.fontFamily,
133
+ '--mj-font-family-mono': s.fontFamilyMono,
134
+ };
135
+ const light = resolveSemantics(brand, accent, tertiary, neutral, 'light');
136
+ const dark = resolveSemantics(brand, accent, tertiary, neutral, 'dark');
137
+ const overlayVars = {
138
+ ...primitives,
139
+ ...vizVars,
140
+ ...shadowVars,
141
+ ...shapeVars,
142
+ ...typeVars,
143
+ };
144
+ return {
145
+ seeds: s,
146
+ primitives,
147
+ tokens: { light, dark },
148
+ overlayVars,
149
+ contrast: buildContrastReport(light, dark),
150
+ };
151
+ }
152
+ /**
153
+ * Serialize a derived theme into overlay CSS scoped to `[data-theme-overlay="<id>"]`.
154
+ * A single block suffices: it overrides primitives that _tokens.scss's semantic and
155
+ * dark blocks reference, so both modes re-point automatically.
156
+ *
157
+ * The optional advanced layer (decision: seed-first, with an escape hatch) is applied
158
+ * last: `overrides` merge over the derived vars; `customCss` is appended, wrapped in the
159
+ * overlay selector so raw rules stay scoped to this theme.
160
+ */
161
+ export function emitOverlayCss(themeId, derived, options = {}) {
162
+ const vars = { ...derived.overlayVars, ...(options.overrides ?? {}) };
163
+ const body = Object.entries(vars)
164
+ .map(([k, v]) => ` ${k}: ${v};`)
165
+ .join('\n');
166
+ let css = `[data-theme-overlay="${themeId}"] {\n${body}\n}\n`;
167
+ const custom = (options.customCss ?? '').trim();
168
+ if (custom) {
169
+ css += '\n' + emitScopedCustomCss(`[data-theme-overlay="${themeId}"]`, custom);
170
+ }
171
+ return css;
172
+ }
173
+ /**
174
+ * Scope raw custom CSS under `selector`, but hoist at-rules that MUST live at the
175
+ * stylesheet top level (`@keyframes`, `@font-face`, `@property`) out of the wrapper —
176
+ * CSS nesting forbids them inside a style rule, so nesting them would silently break
177
+ * animations/fonts. Everything else is wrapped so it only applies to this theme.
178
+ *
179
+ * `@import` is STRIPPED entirely, not hoisted: a scoped theme overlay has no
180
+ * legitimate need to pull another stylesheet, an org-wide `@import` of a remote URL
181
+ * is a cross-origin request from every user's session (data-exfiltration surface),
182
+ * and a hoisted `@import` would be dead anyway — CSS ignores `@import` after any
183
+ * other rule, and the overlay Blob always begins with the token block.
184
+ */
185
+ export function emitScopedCustomCss(selector, css) {
186
+ const trimmed = css.trim();
187
+ if (!trimmed)
188
+ return '';
189
+ const { hoisted, scoped } = splitHoistedAtRules(trimmed);
190
+ let out = '';
191
+ if (hoisted.trim())
192
+ out += `${hoisted.trim()}\n`;
193
+ if (scoped.trim())
194
+ out += `${selector} {\n${scoped.trim()}\n}\n`;
195
+ return out;
196
+ }
197
+ const HOIST_AT_RULE = /^@(?:-webkit-|-moz-|-o-)?(?:keyframes|font-face|property)\b/i;
198
+ const STRIP_AT_RULE = /^@(?:-webkit-|-moz-|-o-)?import\b/i;
199
+ /** Separate top-level hoistable at-rules from the rest of a custom-CSS string. */
200
+ function splitHoistedAtRules(css) {
201
+ const hoisted = [];
202
+ let scoped = '';
203
+ let i = 0;
204
+ const n = css.length;
205
+ while (i < n) {
206
+ if (css[i] === '@' && (HOIST_AT_RULE.test(css.slice(i)) || STRIP_AT_RULE.test(css.slice(i)))) {
207
+ // @import is a statement ending at ';' — consumed and DISCARDED (see emitScopedCustomCss).
208
+ if (STRIP_AT_RULE.test(css.slice(i))) {
209
+ const semi = css.indexOf(';', i);
210
+ const end = semi === -1 ? n : semi + 1;
211
+ i = end;
212
+ continue;
213
+ }
214
+ const open = css.indexOf('{', i);
215
+ if (open === -1) {
216
+ scoped += css.slice(i);
217
+ break;
218
+ }
219
+ let depth = 0;
220
+ let j = open;
221
+ for (; j < n; j++) {
222
+ if (css[j] === '{')
223
+ depth++;
224
+ else if (css[j] === '}' && --depth === 0) {
225
+ j++;
226
+ break;
227
+ }
228
+ }
229
+ hoisted.push(css.slice(i, j));
230
+ i = j;
231
+ continue;
232
+ }
233
+ scoped += css[i];
234
+ i++;
235
+ }
236
+ return { hoisted: hoisted.join('\n'), scoped };
237
+ }
238
+ //# sourceMappingURL=derive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.js","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview MemberJunction Theme Engine — framework-agnostic derivation of the
3
+ * full --mj-* token contract from a small set of brand seeds. Shared by the Angular
4
+ * theme builder (live preview) and a future server overlay endpoint.
5
+ * @module @memberjunction/theme-engine
6
+ */
7
+ export * from './color.js';
8
+ export * from './seeds.js';
9
+ export * from './ramps.js';
10
+ export * from './contrast.js';
11
+ export * from './derive.js';
12
+ export * from './logo.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview MemberJunction Theme Engine — framework-agnostic derivation of the
3
+ * full --mj-* token contract from a small set of brand seeds. Shared by the Angular
4
+ * theme builder (live preview) and a future server overlay endpoint.
5
+ * @module @memberjunction/theme-engine
6
+ */
7
+ export * from './color.js';
8
+ export * from './seeds.js';
9
+ export * from './ramps.js';
10
+ export * from './contrast.js';
11
+ export * from './derive.js';
12
+ export * from './logo.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
package/dist/logo.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @fileoverview Logo validation for the theme pipeline (proposal 16.6). Logos are
3
+ * variant uploads, never recolored — so validation only checks that an upload FITS
4
+ * its slot (type, size, dimensions, aspect ratio) and, for SVGs, reports whether the
5
+ * artwork is detectably single-fill (the only case where "treat as monochrome" is
6
+ * offered). Framework-agnostic: no DOM, no network — it takes already-extracted file
7
+ * facts, so the Angular uploader and a future server endpoint share one rule set.
8
+ * @module @memberjunction/theme-engine
9
+ */
10
+ /** A theme's logo variant URLs + header geometry, for overlay emission (16.6). */
11
+ export interface ThemeLogos {
12
+ /** Logo mark for light surfaces -> --mj-logo-mark (light mode). */
13
+ lightMarkURL?: string;
14
+ /** Logo mark for dark surfaces -> --mj-logo-mark (dark mode). Enables the mode swap. */
15
+ darkMarkURL?: string;
16
+ /** Full wordmark -> --mj-logo-wordmark. */
17
+ wordmarkURL?: string;
18
+ /** Single-fill monochrome variant -> --mj-logo-mono. */
19
+ monochromeURL?: string;
20
+ /** Header mark box width in px -> --mj-logo-width. */
21
+ width?: number;
22
+ /** Header mark box height in px -> --mj-logo-height. */
23
+ height?: number;
24
+ }
25
+ /**
26
+ * Emit the logo overlay CSS for a theme (org-theming Phase 4). Logos are variant
27
+ * uploads, never recolored (16.6): dark mode *swaps artwork* via a mode-specific
28
+ * block, and when only one variant exists we emit a light `--mj-logo-plate-bg` in
29
+ * dark mode so the single mark stays legible on a plate (no filter transforms).
30
+ *
31
+ * Returns CSS scoped to `[data-theme-overlay="<id>"]` (+ a dark companion block);
32
+ * append it to the color overlay so one Blob carries the whole brand.
33
+ */
34
+ export declare function emitLogoOverlayCss(themeId: string, logos: ThemeLogos): string;
35
+ /** The four logo variant slots a theme can carry (16.6). */
36
+ export type LogoSlot = 'mark' | 'wordmark' | 'monochrome';
37
+ /** File facts the caller extracts (byte length, mime, intrinsic size, SVG source). */
38
+ export interface LogoInput {
39
+ slot: LogoSlot;
40
+ /** MIME type, e.g. 'image/svg+xml', 'image/png', 'image/webp', 'image/jpeg'. */
41
+ mime: string;
42
+ /** File size in bytes. */
43
+ byteLength: number;
44
+ /** Raw SVG markup (when the upload is an SVG) — enables single-fill detection. */
45
+ svgText?: string;
46
+ /** Intrinsic width in px (raster pixels, or the SVG viewBox/width). */
47
+ width?: number;
48
+ /** Intrinsic height in px. */
49
+ height?: number;
50
+ }
51
+ /** Outcome of validating one upload. `ok` is false when there is any error. */
52
+ export interface LogoValidationResult {
53
+ ok: boolean;
54
+ errors: string[];
55
+ warnings: string[];
56
+ /** True only for an SVG with at most one distinct fill/stroke color. */
57
+ isSingleFillSvg: boolean;
58
+ /** width / height when both are known. */
59
+ aspectRatio?: number;
60
+ }
61
+ /** Pull an intrinsic width/height from an SVG's viewBox (preferred) or width/height. */
62
+ export declare function svgDimensions(svgText: string): {
63
+ width?: number;
64
+ height?: number;
65
+ };
66
+ /**
67
+ * Detect whether an SVG paints with at most one color — the only case where
68
+ * recoloring to a single monochrome tint is safe. `none` and `currentColor` are
69
+ * ignored (they carry no fixed hue); a gradient or >1 distinct color is not single-fill.
70
+ */
71
+ export declare function isSingleFillSvg(svgText: string): boolean;
72
+ /** Validate one logo upload against its slot's rules. */
73
+ export declare function validateLogo(input: LogoInput): LogoValidationResult;
74
+ //# sourceMappingURL=logo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logo.d.ts","sourceRoot":"","sources":["../src/logo.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACzB,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAwB7E;AAED,4DAA4D;AAC5D,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAC;AAE1D,sFAAsF;AACtF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,QAAQ,CAAC;IACf,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,+EAA+E;AAC/E,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wEAAwE;IACxE,eAAe,EAAE,OAAO,CAAC;IACzB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAuBD,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAMlF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAaxD;AAED,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,oBAAoB,CAiDnE"}
package/dist/logo.js ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @fileoverview Logo validation for the theme pipeline (proposal 16.6). Logos are
3
+ * variant uploads, never recolored — so validation only checks that an upload FITS
4
+ * its slot (type, size, dimensions, aspect ratio) and, for SVGs, reports whether the
5
+ * artwork is detectably single-fill (the only case where "treat as monochrome" is
6
+ * offered). Framework-agnostic: no DOM, no network — it takes already-extracted file
7
+ * facts, so the Angular uploader and a future server endpoint share one rule set.
8
+ * @module @memberjunction/theme-engine
9
+ */
10
+ /** Escape a URL for safe embedding inside a CSS `url("...")` value. */
11
+ function cssUrl(u) {
12
+ return `url("${u.replace(/["\\\n]/g, encodeURIComponent)}")`;
13
+ }
14
+ /**
15
+ * Emit the logo overlay CSS for a theme (org-theming Phase 4). Logos are variant
16
+ * uploads, never recolored (16.6): dark mode *swaps artwork* via a mode-specific
17
+ * block, and when only one variant exists we emit a light `--mj-logo-plate-bg` in
18
+ * dark mode so the single mark stays legible on a plate (no filter transforms).
19
+ *
20
+ * Returns CSS scoped to `[data-theme-overlay="<id>"]` (+ a dark companion block);
21
+ * append it to the color overlay so one Blob carries the whole brand.
22
+ */
23
+ export function emitLogoOverlayCss(themeId, logos) {
24
+ const sel = `[data-theme-overlay="${themeId}"]`;
25
+ const base = [];
26
+ if (logos.lightMarkURL) {
27
+ base.push(`--mj-logo-mark: ${cssUrl(logos.lightMarkURL)};`);
28
+ // --mj-logo-mark-inverse is the light-on-dark artwork; fall back to the light mark.
29
+ base.push(`--mj-logo-mark-inverse: ${cssUrl(logos.darkMarkURL ?? logos.lightMarkURL)};`);
30
+ }
31
+ if (logos.wordmarkURL)
32
+ base.push(`--mj-logo-wordmark: ${cssUrl(logos.wordmarkURL)};`);
33
+ if (logos.monochromeURL)
34
+ base.push(`--mj-logo-mono: ${cssUrl(logos.monochromeURL)};`);
35
+ if (logos.width)
36
+ base.push(`--mj-logo-width: ${Math.round(logos.width)}px;`);
37
+ if (logos.height)
38
+ base.push(`--mj-logo-height: ${Math.round(logos.height)}px;`);
39
+ let css = base.length ? `${sel} {\n ${base.join('\n ')}\n}\n` : '';
40
+ if (logos.darkMarkURL) {
41
+ // Dark mode swaps to the dark-surface artwork.
42
+ css += `${sel}[data-theme="dark"] {\n --mj-logo-mark: ${cssUrl(logos.darkMarkURL)};\n}\n`;
43
+ }
44
+ else if (logos.lightMarkURL) {
45
+ // Only a light-surface mark exists: plate it on white in dark mode so it stays legible.
46
+ css += `${sel}[data-theme="dark"] {\n --mj-logo-plate-bg: #ffffff;\n}\n`;
47
+ }
48
+ return css;
49
+ }
50
+ const RULES = {
51
+ // Header mark: roughly square up to ~2:1 landscape.
52
+ mark: { maxBytes: 512 * 1024, rasterMinPx: 64, aspect: [0.5, 2.0] },
53
+ // Wordmark: wide lockup.
54
+ wordmark: { maxBytes: 1024 * 1024, rasterMinPx: 24, aspect: [2.0, 10.0] },
55
+ // Monochrome: same envelope as the mark.
56
+ monochrome: { maxBytes: 512 * 1024, rasterMinPx: 64, aspect: [0.5, 2.0] },
57
+ };
58
+ const SVG_MIME = 'image/svg+xml';
59
+ const ACCEPTED_RASTER = ['image/png', 'image/webp', 'image/jpeg'];
60
+ /** Pull an intrinsic width/height from an SVG's viewBox (preferred) or width/height. */
61
+ export function svgDimensions(svgText) {
62
+ const vb = /viewBox\s*=\s*["']\s*[-\d.]+\s+[-\d.]+\s+([-\d.]+)\s+([-\d.]+)\s*["']/i.exec(svgText);
63
+ if (vb)
64
+ return { width: parseFloat(vb[1]), height: parseFloat(vb[2]) };
65
+ const w = /\bwidth\s*=\s*["']\s*([\d.]+)/i.exec(svgText);
66
+ const h = /\bheight\s*=\s*["']\s*([\d.]+)/i.exec(svgText);
67
+ return { width: w ? parseFloat(w[1]) : undefined, height: h ? parseFloat(h[1]) : undefined };
68
+ }
69
+ /**
70
+ * Detect whether an SVG paints with at most one color — the only case where
71
+ * recoloring to a single monochrome tint is safe. `none` and `currentColor` are
72
+ * ignored (they carry no fixed hue); a gradient or >1 distinct color is not single-fill.
73
+ */
74
+ export function isSingleFillSvg(svgText) {
75
+ if (/<(linear|radial)Gradient|url\(#/i.test(svgText))
76
+ return false;
77
+ const colors = new Set();
78
+ const add = (raw) => {
79
+ if (!raw)
80
+ return;
81
+ const v = raw.trim().toLowerCase();
82
+ if (!v || v === 'none' || v === 'currentcolor' || v === 'transparent' || v === 'inherit')
83
+ return;
84
+ colors.add(v);
85
+ };
86
+ // fill="..."/stroke="..." attributes and fill:/stroke: within style="..."
87
+ for (const m of svgText.matchAll(/\b(?:fill|stroke)\s*=\s*["']([^"']+)["']/gi))
88
+ add(m[1]);
89
+ for (const m of svgText.matchAll(/(?:fill|stroke)\s*:\s*([^;"'}]+)/gi))
90
+ add(m[1]);
91
+ return colors.size <= 1;
92
+ }
93
+ /** Validate one logo upload against its slot's rules. */
94
+ export function validateLogo(input) {
95
+ const rule = RULES[input.slot];
96
+ const errors = [];
97
+ const warnings = [];
98
+ const isSvg = input.mime === SVG_MIME;
99
+ if (!isSvg && !ACCEPTED_RASTER.includes(input.mime)) {
100
+ errors.push(`Unsupported file type "${input.mime}". Use SVG (preferred), PNG, or WebP.`);
101
+ }
102
+ if (input.byteLength > rule.maxBytes) {
103
+ errors.push(`File is ${(input.byteLength / 1024).toFixed(0)} KB; the ${input.slot} limit is ${(rule.maxBytes / 1024).toFixed(0)} KB.`);
104
+ }
105
+ let { width, height } = input;
106
+ if (isSvg && input.svgText && (width === undefined || height === undefined)) {
107
+ const d = svgDimensions(input.svgText);
108
+ width = width ?? d.width;
109
+ height = height ?? d.height;
110
+ }
111
+ if (!isSvg) {
112
+ warnings.push('SVG is preferred for logos — it stays crisp at every size.');
113
+ if (input.mime === 'image/jpeg') {
114
+ warnings.push('JPEG has no transparency; it will show a rectangular background on colored surfaces.');
115
+ }
116
+ if (width !== undefined && height !== undefined) {
117
+ if (Math.min(width, height) < rule.rasterMinPx) {
118
+ errors.push(`Raster logo is ${width}×${height}px; the ${input.slot} needs at least ${rule.rasterMinPx}px on the smaller side.`);
119
+ }
120
+ }
121
+ else {
122
+ warnings.push('Could not read the image dimensions to validate size/aspect ratio.');
123
+ }
124
+ }
125
+ let aspectRatio;
126
+ if (width && height) {
127
+ aspectRatio = width / height;
128
+ const [lo, hi] = rule.aspect;
129
+ if (aspectRatio < lo || aspectRatio > hi) {
130
+ errors.push(`Aspect ratio ${aspectRatio.toFixed(2)}:1 does not fit the ${input.slot} slot (expected ${lo}:1 to ${hi}:1). ` +
131
+ (input.slot === 'wordmark' ? 'Wordmarks should be a wide lockup.' : 'Use a squarer mark for the header.'));
132
+ }
133
+ }
134
+ const singleFill = isSvg && !!input.svgText && isSingleFillSvg(input.svgText);
135
+ return { ok: errors.length === 0, errors, warnings, isSingleFillSvg: singleFill, aspectRatio };
136
+ }
137
+ //# sourceMappingURL=logo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logo.js","sourceRoot":"","sources":["../src/logo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @fileoverview Perceptual ramp generation (audit G5). Each brand family carries a
3
+ * measured OKLCH "shape" (per-step lightness + chroma) lifted from MJ's own default
4
+ * ramps. A seed supplies the HUE (and a chroma scale relative to the family anchor);
5
+ * we re-hue the shape and rescale its chroma. Feeding MJ's own seeds back therefore
6
+ * reproduces MJ's ramps within rounding tolerance, while any other hue inherits MJ's
7
+ * proven, perceptually-uniform lightness structure.
8
+ * @module @memberjunction/theme-engine
9
+ */
10
+ /** One step of a ramp: its numeric key (50..950) and its measured OKLCH L/C. */
11
+ export interface RampStop {
12
+ step: number;
13
+ L: number;
14
+ C: number;
15
+ }
16
+ /** Measured shape of MJ's primary blue ramp. `anchor` is the step the seed maps to. */
17
+ export declare const BRAND_SHAPE: RampStop[];
18
+ /** Measured shape of MJ's accent ramp. Accent seed maps to the 400 slot. */
19
+ export declare const ACCENT_SHAPE: RampStop[];
20
+ /** Measured shape of MJ's tertiary cyan ramp. Tertiary seed maps to the 500 slot. */
21
+ export declare const TERTIARY_SHAPE: RampStop[];
22
+ /** Measured shape of MJ's slate neutral ramp. Chroma is driven by the `neutralChroma` seed. */
23
+ export declare const NEUTRAL_SHAPE: RampStop[];
24
+ /** Chroma of the neutral shape at its mid step, i.e. the `neutralChroma` reference point. */
25
+ export declare const NEUTRAL_REFERENCE_CHROMA = 0.0407;
26
+ /** The step in each brand family that its seed color represents. */
27
+ export declare const FAMILY_ANCHOR: Record<'brand' | 'accent' | 'tertiary', number>;
28
+ /**
29
+ * Re-hue and re-scale a family shape from a seed color. Every step keeps the shape's
30
+ * lightness; its hue becomes the seed's hue; its chroma is the shape chroma scaled so
31
+ * the anchor step matches the seed's chroma, times `vibrancy`.
32
+ */
33
+ export declare function generateBrandRamp(shape: RampStop[], anchorStep: number, seedHex: string, vibrancy: number): Record<number, string>;
34
+ /**
35
+ * The neutral ramp: shape lightness preserved, hue bled from the brand (G2), chroma
36
+ * scaled to the requested `neutralChroma` mid-target.
37
+ */
38
+ export declare function generateNeutralRamp(brandHueDeg: number, neutralChroma: number): Record<number, string>;
39
+ //# sourceMappingURL=ramps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ramps.d.ts","sourceRoot":"","sources":["../src/ramps.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,gFAAgF;AAChF,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,uFAAuF;AACvF,eAAO,MAAM,WAAW,EAAE,QAAQ,EAajC,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,YAAY,EAAE,QAAQ,EAWlC,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,cAAc,EAAE,QAAQ,EAWpC,CAAC;AAEF,+FAA+F;AAC/F,eAAO,MAAM,aAAa,EAAE,QAAQ,EAanC,CAAC;AAEF,6FAA6F;AAC7F,eAAO,MAAM,wBAAwB,SAAS,CAAC;AAE/C,oEAAoE;AACpE,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,OAAO,GAAG,QAAQ,GAAG,UAAU,EAAE,MAAM,CAIzE,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,QAAQ,EAAE,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUxB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOtG"}