@actuate-media/cms-core 0.35.0 → 0.36.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.
Files changed (44) hide show
  1. package/dist/__tests__/api/appearance-routes.test.d.ts +2 -0
  2. package/dist/__tests__/api/appearance-routes.test.d.ts.map +1 -0
  3. package/dist/__tests__/api/appearance-routes.test.js +134 -0
  4. package/dist/__tests__/api/appearance-routes.test.js.map +1 -0
  5. package/dist/__tests__/api/globals-routes.test.d.ts +2 -0
  6. package/dist/__tests__/api/globals-routes.test.d.ts.map +1 -0
  7. package/dist/__tests__/api/globals-routes.test.js +100 -0
  8. package/dist/__tests__/api/globals-routes.test.js.map +1 -0
  9. package/dist/__tests__/appearance/appearance.test.d.ts +2 -0
  10. package/dist/__tests__/appearance/appearance.test.d.ts.map +1 -0
  11. package/dist/__tests__/appearance/appearance.test.js +273 -0
  12. package/dist/__tests__/appearance/appearance.test.js.map +1 -0
  13. package/dist/api/handlers.d.ts.map +1 -1
  14. package/dist/api/handlers.js +68 -4
  15. package/dist/api/handlers.js.map +1 -1
  16. package/dist/appearance/color.d.ts +24 -0
  17. package/dist/appearance/color.d.ts.map +1 -0
  18. package/dist/appearance/color.js +90 -0
  19. package/dist/appearance/color.js.map +1 -0
  20. package/dist/appearance/index.d.ts +13 -0
  21. package/dist/appearance/index.d.ts.map +1 -0
  22. package/dist/appearance/index.js +5 -0
  23. package/dist/appearance/index.js.map +1 -0
  24. package/dist/appearance/logo-shape.d.ts +26 -0
  25. package/dist/appearance/logo-shape.d.ts.map +1 -0
  26. package/dist/appearance/logo-shape.js +62 -0
  27. package/dist/appearance/logo-shape.js.map +1 -0
  28. package/dist/appearance/resolve.d.ts +46 -0
  29. package/dist/appearance/resolve.d.ts.map +1 -0
  30. package/dist/appearance/resolve.js +208 -0
  31. package/dist/appearance/resolve.js.map +1 -0
  32. package/dist/appearance/theme-tokens.d.ts +50 -0
  33. package/dist/appearance/theme-tokens.d.ts.map +1 -0
  34. package/dist/appearance/theme-tokens.js +198 -0
  35. package/dist/appearance/theme-tokens.js.map +1 -0
  36. package/dist/appearance/types.d.ts +110 -0
  37. package/dist/appearance/types.d.ts.map +1 -0
  38. package/dist/appearance/types.js +16 -0
  39. package/dist/appearance/types.js.map +1 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +2 -0
  43. package/dist/index.js.map +1 -1
  44. package/package.json +6 -1
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Color parsing + WCAG contrast.
3
+ *
4
+ * Accent colors are stored either as a preset name (resolved elsewhere) or as
5
+ * a raw CSS color string. To validate contrast we need a real luminance, so we
6
+ * parse both `#rgb`/`#rrggbb` and `oklch(L C H)` into LINEAR sRGB and apply the
7
+ * WCAG relative-luminance + contrast-ratio formulas. Linear RGB is exactly what
8
+ * the luminance formula wants, so the hex path linearizes gamma sRGB and the
9
+ * oklch path (via OKLab→linear sRGB, per Björn Ottosson) is already linear.
10
+ */
11
+ export interface LinearRgb {
12
+ r: number;
13
+ g: number;
14
+ b: number;
15
+ }
16
+ /** Parse a supported color string (`#hex` or `oklch(...)`) into linear sRGB. */
17
+ export declare function parseColorToLinear(input: string): LinearRgb | null;
18
+ /** WCAG relative luminance from linear sRGB. */
19
+ export declare function relativeLuminance(rgb: LinearRgb): number;
20
+ /** WCAG contrast ratio (1..21) between two colors. Returns 0 if unparseable. */
21
+ export declare function contrastRatio(a: string, b: string): number;
22
+ /** True when the color string is a parseable `#hex` or `oklch(...)` value. */
23
+ export declare function isParseableColor(input: string): boolean;
24
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/appearance/color.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAuDD,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAMlE;AAED,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAExD;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS1D;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvD"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Color parsing + WCAG contrast.
3
+ *
4
+ * Accent colors are stored either as a preset name (resolved elsewhere) or as
5
+ * a raw CSS color string. To validate contrast we need a real luminance, so we
6
+ * parse both `#rgb`/`#rrggbb` and `oklch(L C H)` into LINEAR sRGB and apply the
7
+ * WCAG relative-luminance + contrast-ratio formulas. Linear RGB is exactly what
8
+ * the luminance formula wants, so the hex path linearizes gamma sRGB and the
9
+ * oklch path (via OKLab→linear sRGB, per Björn Ottosson) is already linear.
10
+ */
11
+ function clamp01(v) {
12
+ return v < 0 ? 0 : v > 1 ? 1 : v;
13
+ }
14
+ /** sRGB gamma channel (0..1) → linear (0..1). */
15
+ function srgbToLinear(c) {
16
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
17
+ }
18
+ function parseHex(input) {
19
+ let hex = input.trim().replace(/^#/, '');
20
+ if (hex.length === 3) {
21
+ hex = hex
22
+ .split('')
23
+ .map((ch) => ch + ch)
24
+ .join('');
25
+ }
26
+ if (!/^[0-9a-fA-F]{6}$/.test(hex))
27
+ return null;
28
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
29
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
30
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
31
+ return { r: srgbToLinear(r), g: srgbToLinear(g), b: srgbToLinear(b) };
32
+ }
33
+ /** Parse `oklch(L C H)` (L 0..1 or 0%..100%, C ≥ 0, H degrees) → linear sRGB. */
34
+ function parseOklch(input) {
35
+ const m = input.trim().match(/^oklch\(\s*([\d.]+%?)\s+([\d.]+)\s+([\d.]+)\s*\)$/i);
36
+ if (!m)
37
+ return null;
38
+ let L = parseFloat(m[1]);
39
+ if (m[1].endsWith('%'))
40
+ L /= 100;
41
+ const C = parseFloat(m[2]);
42
+ const Hdeg = parseFloat(m[3]);
43
+ if (!Number.isFinite(L) || !Number.isFinite(C) || !Number.isFinite(Hdeg))
44
+ return null;
45
+ const h = (Hdeg * Math.PI) / 180;
46
+ const a = C * Math.cos(h);
47
+ const b = C * Math.sin(h);
48
+ const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
49
+ const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
50
+ const s_ = L - 0.0894841775 * a - 1.291485548 * b;
51
+ const l = l_ * l_ * l_;
52
+ const mm = m_ * m_ * m_;
53
+ const s = s_ * s_ * s_;
54
+ const r = 4.0767416621 * l - 3.3077115913 * mm + 0.2309699292 * s;
55
+ const g = -1.2684380046 * l + 2.6097574011 * mm - 0.3413193965 * s;
56
+ const bl = -0.0041960863 * l - 0.7034186147 * mm + 1.707614701 * s;
57
+ return { r: clamp01(r), g: clamp01(g), b: clamp01(bl) };
58
+ }
59
+ /** Parse a supported color string (`#hex` or `oklch(...)`) into linear sRGB. */
60
+ export function parseColorToLinear(input) {
61
+ if (!input)
62
+ return null;
63
+ const trimmed = input.trim();
64
+ if (trimmed.startsWith('#'))
65
+ return parseHex(trimmed);
66
+ if (/^oklch\(/i.test(trimmed))
67
+ return parseOklch(trimmed);
68
+ return null;
69
+ }
70
+ /** WCAG relative luminance from linear sRGB. */
71
+ export function relativeLuminance(rgb) {
72
+ return 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b;
73
+ }
74
+ /** WCAG contrast ratio (1..21) between two colors. Returns 0 if unparseable. */
75
+ export function contrastRatio(a, b) {
76
+ const ca = parseColorToLinear(a);
77
+ const cb = parseColorToLinear(b);
78
+ if (!ca || !cb)
79
+ return 0;
80
+ const la = relativeLuminance(ca);
81
+ const lb = relativeLuminance(cb);
82
+ const lighter = Math.max(la, lb);
83
+ const darker = Math.min(la, lb);
84
+ return (lighter + 0.05) / (darker + 0.05);
85
+ }
86
+ /** True when the color string is a parseable `#hex` or `oklch(...)` value. */
87
+ export function isParseableColor(input) {
88
+ return parseColorToLinear(input) !== null;
89
+ }
90
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.js","sourceRoot":"","sources":["../../src/appearance/color.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Appearance + brand module barrel.
3
+ *
4
+ * Single source of truth for admin theme preferences and brand assets, stored
5
+ * on the `settings` global and consumed by the admin shell, the public
6
+ * frontend (`GET /public/brand`), and Next.js favicon metadata.
7
+ */
8
+ export type { ColorMode, SidebarDensity, AccentColorName, LogoShape, AdminBrandingMode, BrandAssetRole, AppearanceSettings, BrandSettings, BrandAssetMetadata, ResolvedLogo, ResolvedAdminBrand, ResolvedPublicBrand, FaviconMetadata, BrandAssetLike, } from './types.js';
9
+ export { classifyLogoShape, logoShapeWarning } from './logo-shape.js';
10
+ export { parseColorToLinear, relativeLuminance, contrastRatio, isParseableColor, type LinearRgb, } from './color.js';
11
+ export { ACCENT_PRESETS, ACCENT_PRESET_LIST, MIN_ACCENT_CONTRAST, isAccentPreset, validateAccentColor, generateAdminThemeTokens, accentTokensToCssVars, type AccentTokens, type AccentPreset, type AccentValidation, } from './theme-tokens.js';
12
+ export { SETTINGS_GLOBAL_COLLECTION, DEFAULT_APPEARANCE, DEFAULT_BRAND, siteInitials, resolveAdminBrand, resolvePublicBrand, getFaviconMetadata, parseAppearance, parseBrand, brandAssetIds, gatherBrandAssets, getAppearanceSettings, getBrandSettings, resolveBrand, type ResolvedBrandBundle, } from './resolve.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/appearance/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAErE,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,KAAK,SAAS,GACf,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,wBAAwB,EACxB,qBAAqB,EACrB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,EACZ,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { classifyLogoShape, logoShapeWarning } from './logo-shape.js';
2
+ export { parseColorToLinear, relativeLuminance, contrastRatio, isParseableColor, } from './color.js';
3
+ export { ACCENT_PRESETS, ACCENT_PRESET_LIST, MIN_ACCENT_CONTRAST, isAccentPreset, validateAccentColor, generateAdminThemeTokens, accentTokensToCssVars, } from './theme-tokens.js';
4
+ export { SETTINGS_GLOBAL_COLLECTION, DEFAULT_APPEARANCE, DEFAULT_BRAND, siteInitials, resolveAdminBrand, resolvePublicBrand, getFaviconMetadata, parseAppearance, parseBrand, brandAssetIds, gatherBrandAssets, getAppearanceSettings, getBrandSettings, resolveBrand, } from './resolve.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/appearance/index.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAErE,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,GAEjB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,wBAAwB,EACxB,qBAAqB,GAItB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,YAAY,GAEb,MAAM,cAAc,CAAA"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Logo shape classification.
3
+ *
4
+ * Pure, dimension-based classification used to (a) pick the right rendering
5
+ * slot for an uploaded asset and (b) surface non-blocking warnings when an
6
+ * asset is uploaded into a role it doesn't suit (e.g. a square mark dropped
7
+ * into the full-width "Primary Logo" slot). We NEVER stretch or crop — these
8
+ * are advisory only; rendering always uses object-fit: contain.
9
+ */
10
+ import type { BrandAssetRole, LogoShape } from './types.js';
11
+ /**
12
+ * Classify a logo by aspect ratio (width / height):
13
+ * - square: 0.8 ≤ ratio ≤ 1.25 (marks, favicons, app icons)
14
+ * - horizontal: ratio > 1.8 (headers, full sidebar)
15
+ * - vertical: ratio < 0.8 (rarely fits headers/sidebars)
16
+ * - neutral: everything in between
17
+ * - unknown: missing/invalid dimensions
18
+ */
19
+ export declare function classifyLogoShape(width: number | null | undefined, height: number | null | undefined): LogoShape;
20
+ /**
21
+ * Advisory warning when an asset's shape doesn't suit its role. Returns null
22
+ * when the shape is fine (or unknown — we don't nag about SVGs without
23
+ * intrinsic dimensions).
24
+ */
25
+ export declare function logoShapeWarning(role: BrandAssetRole, shape: LogoShape): string | null;
26
+ //# sourceMappingURL=logo-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logo-shape.d.ts","sourceRoot":"","sources":["../../src/appearance/logo-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3D;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,SAAS,CAOX;AAiBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAwBtF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Classify a logo by aspect ratio (width / height):
3
+ * - square: 0.8 ≤ ratio ≤ 1.25 (marks, favicons, app icons)
4
+ * - horizontal: ratio > 1.8 (headers, full sidebar)
5
+ * - vertical: ratio < 0.8 (rarely fits headers/sidebars)
6
+ * - neutral: everything in between
7
+ * - unknown: missing/invalid dimensions
8
+ */
9
+ export function classifyLogoShape(width, height) {
10
+ if (!width || !height || width <= 0 || height <= 0)
11
+ return 'unknown';
12
+ const ratio = width / height;
13
+ if (ratio >= 0.8 && ratio <= 1.25)
14
+ return 'square';
15
+ if (ratio > 1.8)
16
+ return 'horizontal';
17
+ if (ratio < 0.8)
18
+ return 'vertical';
19
+ return 'neutral';
20
+ }
21
+ /** Roles that want a square asset (mark/favicon/app icon). */
22
+ const SQUARE_ROLES = new Set([
23
+ 'publicLogoMark',
24
+ 'adminLogoMark',
25
+ 'faviconSource',
26
+ ]);
27
+ /** Roles that want a wide lockup (header / full sidebar). */
28
+ const WIDE_ROLES = new Set([
29
+ 'publicPrimaryLogo',
30
+ 'adminPrimaryLogo',
31
+ 'publicDarkLogo',
32
+ 'adminDarkLogo',
33
+ ]);
34
+ /**
35
+ * Advisory warning when an asset's shape doesn't suit its role. Returns null
36
+ * when the shape is fine (or unknown — we don't nag about SVGs without
37
+ * intrinsic dimensions).
38
+ */
39
+ export function logoShapeWarning(role, shape) {
40
+ if (shape === 'unknown')
41
+ return null;
42
+ if (WIDE_ROLES.has(role)) {
43
+ if (shape === 'square') {
44
+ return 'This looks like a square mark. It will work well for icons and collapsed navigation. For full-width headers, upload a horizontal primary logo or use mark + site title.';
45
+ }
46
+ if (shape === 'vertical') {
47
+ return 'Vertical logos may not fit well in headers or sidebars. Consider uploading a horizontal logo and a square mark.';
48
+ }
49
+ }
50
+ if (SQUARE_ROLES.has(role)) {
51
+ if (shape === 'horizontal') {
52
+ return role === 'faviconSource'
53
+ ? 'This favicon source is not square. A square image works better for favicons and app icons and avoids cropping.'
54
+ : 'This looks like a horizontal logo. A square mark works better for favicons, collapsed sidebar, and app icons.';
55
+ }
56
+ if (shape === 'vertical') {
57
+ return 'Vertical images do not crop cleanly to a square icon. Upload a square mark for the best result.';
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ //# sourceMappingURL=logo-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logo-shape.js","sourceRoot":"","sources":["../../src/appearance/logo-shape.ts"],"names":[],"mappings":"AAWA;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAgC,EAChC,MAAiC;IAEjC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IACpE,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;IAC5B,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,QAAQ,CAAA;IAClD,IAAI,KAAK,GAAG,GAAG;QAAE,OAAO,YAAY,CAAA;IACpC,IAAI,KAAK,GAAG,GAAG;QAAE,OAAO,UAAU,CAAA;IAClC,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,8DAA8D;AAC9D,MAAM,YAAY,GAAgC,IAAI,GAAG,CAAC;IACxD,gBAAgB;IAChB,eAAe;IACf,eAAe;CAChB,CAAC,CAAA;AAEF,6DAA6D;AAC7D,MAAM,UAAU,GAAgC,IAAI,GAAG,CAAC;IACtD,mBAAmB;IACnB,kBAAkB;IAClB,gBAAgB;IAChB,eAAe;CAChB,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAoB,EAAE,KAAgB;IACrE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IAEpC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvB,OAAO,yKAAyK,CAAA;QAClL,CAAC;QACD,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;YACzB,OAAO,iHAAiH,CAAA;QAC1H,CAAC;IACH,CAAC;IAED,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;YAC3B,OAAO,IAAI,KAAK,eAAe;gBAC7B,CAAC,CAAC,gHAAgH;gBAClH,CAAC,CAAC,+GAA+G,CAAA;QACrH,CAAC;QACD,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;YACzB,OAAO,iGAAiG,CAAA;QAC1G,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,46 @@
1
+ import type { AppearanceSettings, BrandAssetLike, BrandSettings, FaviconMetadata, ResolvedAdminBrand, ResolvedPublicBrand } from './types.js';
2
+ export declare const SETTINGS_GLOBAL_COLLECTION = "settings";
3
+ export declare const DEFAULT_APPEARANCE: AppearanceSettings;
4
+ export declare const DEFAULT_BRAND: BrandSettings;
5
+ /** Up to two uppercase initials derived from the site title. */
6
+ export declare function siteInitials(siteTitle: string | null | undefined): string;
7
+ /**
8
+ * Resolve the admin sidebar brand with the spec's fallback chains.
9
+ *
10
+ * inheritPublicBrand → admin uses the public assets.
11
+ * customAdminBrand → admin-specific assets win, falling back to public.
12
+ * Collapsed mark falls back to favicon source, then site initials.
13
+ */
14
+ export declare function resolveAdminBrand(brand: BrandSettings, assets: Map<string, BrandAssetLike>, siteTitle: string | null | undefined): ResolvedAdminBrand;
15
+ /** Resolve the public site brand (header/footer/nav). */
16
+ export declare function resolvePublicBrand(brand: BrandSettings, assets: Map<string, BrandAssetLike>, siteTitle: string | null | undefined): ResolvedPublicBrand;
17
+ /**
18
+ * Favicon metadata for Next.js `metadata.icons`. Appends a cache-busting
19
+ * `?v=` token derived from the brand's `updatedAt` so changing the favicon
20
+ * invalidates the browser/CDN copy.
21
+ */
22
+ export declare function getFaviconMetadata(brand: BrandSettings, assets: Map<string, BrandAssetLike>): FaviconMetadata;
23
+ export declare function parseAppearance(blob: Record<string, unknown>): AppearanceSettings;
24
+ export declare function parseBrand(blob: Record<string, unknown>): BrandSettings;
25
+ /** Collect every asset ID referenced by a brand settings record. */
26
+ export declare function brandAssetIds(brand: BrandSettings): string[];
27
+ /** Fetch the referenced media rows and key them by asset ID. */
28
+ export declare function gatherBrandAssets(db: unknown, ids: string[]): Promise<Map<string, BrandAssetLike>>;
29
+ export declare function getAppearanceSettings(db: unknown): Promise<AppearanceSettings>;
30
+ export declare function getBrandSettings(db: unknown): Promise<BrandSettings>;
31
+ export interface ResolvedBrandBundle {
32
+ brand: BrandSettings;
33
+ admin: ResolvedAdminBrand;
34
+ public: ResolvedPublicBrand;
35
+ favicon: FaviconMetadata;
36
+ /** Every referenced asset, keyed for direct per-field editor previews. */
37
+ assets: BrandAssetLike[];
38
+ /** Site title used for alt text + initials (from General Settings). */
39
+ siteTitle: string | null;
40
+ }
41
+ /**
42
+ * One-shot resolver used by the public brand endpoint and the admin shell:
43
+ * read the settings blob, gather referenced assets, and resolve every view.
44
+ */
45
+ export declare function resolveBrand(db: unknown, siteTitle: string | null | undefined): Promise<ResolvedBrandBundle>;
46
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/appearance/resolve.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,eAAe,EACf,kBAAkB,EAElB,mBAAmB,EACpB,MAAM,YAAY,CAAA;AAEnB,eAAO,MAAM,0BAA0B,aAAa,CAAA;AAEpD,eAAO,MAAM,kBAAkB,EAAE,kBAMhC,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,aAE3B,CAAA;AAED,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAQzE;AAiBD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,EACnC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,kBAAkB,CA0BpB;AAED,yDAAyD;AACzD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,EACnC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,mBAAmB,CASrB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,GAClC,eAAe,CAajB;AAuBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,CAWjF;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,CAGvE;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,EAAE,CAY5D;AAqBD,gEAAgE;AAChE,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,OAAO,EACX,GAAG,EAAE,MAAM,EAAE,GACZ,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAWtC;AAED,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAEpF;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAE1E;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,aAAa,CAAA;IACpB,KAAK,EAAE,kBAAkB,CAAA;IACzB,MAAM,EAAE,mBAAmB,CAAA;IAC3B,OAAO,EAAE,eAAe,CAAA;IACxB,0EAA0E;IAC1E,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,uEAAuE;IACvE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,OAAO,EACX,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,OAAO,CAAC,mBAAmB,CAAC,CAa9B"}
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Brand resolution.
3
+ *
4
+ * Turns stored {@link BrandSettings} (asset IDs) + Media Center rows into the
5
+ * concrete logo/favicon URLs the admin shell, public site, and Next.js
6
+ * metadata consume. Resolution encodes the fallback chains from the spec so
7
+ * callers never hardcode logo paths and always degrade gracefully to site
8
+ * initials.
9
+ *
10
+ * The pure functions (`resolveAdminBrand`, `resolvePublicBrand`,
11
+ * `getFaviconMetadata`, `siteInitials`) take an already-fetched asset map and
12
+ * are unit-tested; the async gatherers read the `settings` global + media rows.
13
+ */
14
+ import { classifyLogoShape } from './logo-shape.js';
15
+ export const SETTINGS_GLOBAL_COLLECTION = 'settings';
16
+ export const DEFAULT_APPEARANCE = {
17
+ adminColorModeDefault: 'system',
18
+ adminAccentColor: 'purple',
19
+ sidebarDensity: 'default',
20
+ contentFont: 'DM Sans',
21
+ reduceMotion: false,
22
+ };
23
+ export const DEFAULT_BRAND = {
24
+ mode: 'inheritPublicBrand',
25
+ };
26
+ /** Up to two uppercase initials derived from the site title. */
27
+ export function siteInitials(siteTitle) {
28
+ const cleaned = (siteTitle ?? '').trim();
29
+ if (!cleaned)
30
+ return 'A';
31
+ const words = cleaned.split(/\s+/).filter(Boolean);
32
+ if (words.length === 1) {
33
+ return words[0].slice(0, 2).toUpperCase();
34
+ }
35
+ return (words[0][0] + words[1][0]).toUpperCase();
36
+ }
37
+ function toResolvedLogo(asset, alt) {
38
+ if (!asset || !asset.publicUrl)
39
+ return null;
40
+ return {
41
+ url: asset.publicUrl,
42
+ alt,
43
+ shape: classifyLogoShape(asset.width ?? undefined, asset.height ?? undefined),
44
+ width: asset.width ?? undefined,
45
+ height: asset.height ?? undefined,
46
+ };
47
+ }
48
+ function altFor(brand, siteTitle) {
49
+ return brand.defaultLogoAltText?.trim() || (siteTitle ?? '').trim() || 'Logo';
50
+ }
51
+ /**
52
+ * Resolve the admin sidebar brand with the spec's fallback chains.
53
+ *
54
+ * inheritPublicBrand → admin uses the public assets.
55
+ * customAdminBrand → admin-specific assets win, falling back to public.
56
+ * Collapsed mark falls back to favicon source, then site initials.
57
+ */
58
+ export function resolveAdminBrand(brand, assets, siteTitle) {
59
+ const alt = altFor(brand, siteTitle);
60
+ const get = (id) => (id ? assets.get(id) : undefined);
61
+ const custom = brand.mode === 'customAdminBrand';
62
+ const primaryAsset = custom
63
+ ? (get(brand.adminPrimaryLogoAssetId) ?? get(brand.publicPrimaryLogoAssetId))
64
+ : get(brand.publicPrimaryLogoAssetId);
65
+ const markAsset = custom
66
+ ? (get(brand.adminLogoMarkAssetId) ??
67
+ get(brand.publicLogoMarkAssetId) ??
68
+ get(brand.faviconSourceAssetId))
69
+ : (get(brand.publicLogoMarkAssetId) ?? get(brand.faviconSourceAssetId));
70
+ const darkAsset = custom
71
+ ? (get(brand.adminDarkLogoAssetId) ?? get(brand.publicDarkLogoAssetId))
72
+ : get(brand.publicDarkLogoAssetId);
73
+ return {
74
+ primary: toResolvedLogo(primaryAsset, alt),
75
+ mark: toResolvedLogo(markAsset, alt),
76
+ dark: toResolvedLogo(darkAsset, alt),
77
+ initials: siteInitials(siteTitle),
78
+ inherited: !custom,
79
+ };
80
+ }
81
+ /** Resolve the public site brand (header/footer/nav). */
82
+ export function resolvePublicBrand(brand, assets, siteTitle) {
83
+ const alt = altFor(brand, siteTitle);
84
+ const get = (id) => (id ? assets.get(id) : undefined);
85
+ return {
86
+ primary: toResolvedLogo(get(brand.publicPrimaryLogoAssetId), alt),
87
+ mark: toResolvedLogo(get(brand.publicLogoMarkAssetId) ?? get(brand.faviconSourceAssetId), alt),
88
+ dark: toResolvedLogo(get(brand.publicDarkLogoAssetId), alt),
89
+ initials: siteInitials(siteTitle),
90
+ };
91
+ }
92
+ /**
93
+ * Favicon metadata for Next.js `metadata.icons`. Appends a cache-busting
94
+ * `?v=` token derived from the brand's `updatedAt` so changing the favicon
95
+ * invalidates the browser/CDN copy.
96
+ */
97
+ export function getFaviconMetadata(brand, assets) {
98
+ const source = brand.faviconSourceAssetId ? assets.get(brand.faviconSourceAssetId) : undefined;
99
+ const generated = (brand.generatedFaviconAssetIds ?? [])
100
+ .map((id) => assets.get(id)?.publicUrl)
101
+ .filter((u) => Boolean(u));
102
+ if (!source || !source.publicUrl) {
103
+ return { icon: null, apple: null, type: null, generated };
104
+ }
105
+ const version = brand.updatedAt ? Date.parse(brand.updatedAt) : null;
106
+ const bust = version && !Number.isNaN(version) ? `?v=${version}` : '';
107
+ const url = `${source.publicUrl}${bust}`;
108
+ return { icon: url, apple: url, type: source.mimeType || null, generated };
109
+ }
110
+ async function readSettingsBlob(db) {
111
+ const d = db;
112
+ if (!d?.document?.findFirst)
113
+ return {};
114
+ const doc = await d.document.findFirst({
115
+ where: { collection: SETTINGS_GLOBAL_COLLECTION, deletedAt: null },
116
+ });
117
+ const data = doc?.data;
118
+ return data && typeof data === 'object' ? data : {};
119
+ }
120
+ export function parseAppearance(blob) {
121
+ const a = blob.appearance ?? {};
122
+ return {
123
+ adminColorModeDefault: a.adminColorModeDefault ?? DEFAULT_APPEARANCE.adminColorModeDefault,
124
+ adminAccentColor: a.adminAccentColor ?? DEFAULT_APPEARANCE.adminAccentColor,
125
+ sidebarDensity: a.sidebarDensity ?? DEFAULT_APPEARANCE.sidebarDensity,
126
+ contentFont: a.contentFont ?? DEFAULT_APPEARANCE.contentFont,
127
+ reduceMotion: a.reduceMotion ?? DEFAULT_APPEARANCE.reduceMotion,
128
+ updatedAt: a.updatedAt,
129
+ updatedBy: a.updatedBy,
130
+ };
131
+ }
132
+ export function parseBrand(blob) {
133
+ const b = blob.brand ?? {};
134
+ return { ...DEFAULT_BRAND, ...b };
135
+ }
136
+ /** Collect every asset ID referenced by a brand settings record. */
137
+ export function brandAssetIds(brand) {
138
+ const ids = [
139
+ brand.publicPrimaryLogoAssetId,
140
+ brand.publicLogoMarkAssetId,
141
+ brand.publicDarkLogoAssetId,
142
+ brand.adminPrimaryLogoAssetId,
143
+ brand.adminLogoMarkAssetId,
144
+ brand.adminDarkLogoAssetId,
145
+ brand.faviconSourceAssetId,
146
+ ...(brand.generatedFaviconAssetIds ?? []),
147
+ ].filter((id) => Boolean(id));
148
+ return Array.from(new Set(ids));
149
+ }
150
+ function mediaRowToAsset(row) {
151
+ const id = typeof row.id === 'string' ? row.id : null;
152
+ if (!id)
153
+ return null;
154
+ const publicUrl = (typeof row.publicUrl === 'string' && row.publicUrl) ||
155
+ (typeof row.storageKey === 'string' && /^(https?:\/\/|\/)/.test(row.storageKey)
156
+ ? row.storageKey
157
+ : '');
158
+ return {
159
+ id,
160
+ publicUrl: publicUrl || '',
161
+ mimeType: typeof row.mimeType === 'string' ? row.mimeType : '',
162
+ width: typeof row.width === 'number' ? row.width : null,
163
+ height: typeof row.height === 'number' ? row.height : null,
164
+ fileName: typeof row.filename === 'string' ? row.filename : null,
165
+ sizeBytes: typeof row.fileSize === 'number' ? row.fileSize : null,
166
+ };
167
+ }
168
+ /** Fetch the referenced media rows and key them by asset ID. */
169
+ export async function gatherBrandAssets(db, ids) {
170
+ const map = new Map();
171
+ if (ids.length === 0)
172
+ return map;
173
+ const d = db;
174
+ if (!d?.media?.findMany)
175
+ return map;
176
+ const rows = await d.media.findMany({ where: { id: { in: ids }, deletedAt: null } });
177
+ for (const row of rows) {
178
+ const asset = mediaRowToAsset(row);
179
+ if (asset)
180
+ map.set(asset.id, asset);
181
+ }
182
+ return map;
183
+ }
184
+ export async function getAppearanceSettings(db) {
185
+ return parseAppearance(await readSettingsBlob(db));
186
+ }
187
+ export async function getBrandSettings(db) {
188
+ return parseBrand(await readSettingsBlob(db));
189
+ }
190
+ /**
191
+ * One-shot resolver used by the public brand endpoint and the admin shell:
192
+ * read the settings blob, gather referenced assets, and resolve every view.
193
+ */
194
+ export async function resolveBrand(db, siteTitle) {
195
+ const blob = await readSettingsBlob(db);
196
+ const brand = parseBrand(blob);
197
+ const title = siteTitle ?? (typeof blob.siteTitle === 'string' ? blob.siteTitle : null);
198
+ const assets = await gatherBrandAssets(db, brandAssetIds(brand));
199
+ return {
200
+ brand,
201
+ admin: resolveAdminBrand(brand, assets, title),
202
+ public: resolvePublicBrand(brand, assets, title),
203
+ favicon: getFaviconMetadata(brand, assets),
204
+ assets: Array.from(assets.values()),
205
+ siteTitle: title ?? null,
206
+ };
207
+ }
208
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/appearance/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { AccentColorName, AppearanceSettings } from './types.js';
2
+ export interface AccentTokens {
3
+ brand: string;
4
+ brandForeground: string;
5
+ primary: string;
6
+ primaryForeground: string;
7
+ sidebarPrimary: string;
8
+ ring: string;
9
+ }
10
+ export interface AccentPreset {
11
+ name: AccentColorName;
12
+ label: string;
13
+ /** Swatch color for the picker (the light-mode brand value). */
14
+ swatch: string;
15
+ light: AccentTokens;
16
+ dark: AccentTokens;
17
+ }
18
+ /**
19
+ * Curated accent presets. Light/dark brand values mirror the existing
20
+ * `theme.css` violet so "purple" is a visual no-op; the others are tuned to the
21
+ * same lightness/chroma envelope so they read as text on cards and as solid
22
+ * fills, and pass the >=3:1 UI-contrast bar against their foreground.
23
+ */
24
+ export declare const ACCENT_PRESETS: Record<AccentColorName, AccentPreset>;
25
+ export declare const ACCENT_PRESET_LIST: AccentPreset[];
26
+ /** Minimum WCAG contrast for a primary fill vs its foreground (UI/large text). */
27
+ export declare const MIN_ACCENT_CONTRAST = 3;
28
+ export declare function isAccentPreset(value: string): value is AccentColorName;
29
+ export interface AccentValidation {
30
+ valid: boolean;
31
+ /** Computed contrast of the accent fill against white (light mode). */
32
+ contrast: number;
33
+ reason?: string;
34
+ }
35
+ /**
36
+ * Validate an accent value. Presets are always valid (pre-vetted). A custom
37
+ * string must parse AND clear {@link MIN_ACCENT_CONTRAST} against white so
38
+ * white text on the primary button stays legible.
39
+ */
40
+ export declare function validateAccentColor(value: string): AccentValidation;
41
+ /**
42
+ * Resolve the accent tokens for a settings value + resolved color mode.
43
+ * Falls back to the purple preset when the stored value is a custom color
44
+ * (custom colors only set `--brand`/`--primary` directly; we keep purple's
45
+ * foreground/ring envelope). Unknown names fall back to purple.
46
+ */
47
+ export declare function generateAdminThemeTokens(settings: Pick<AppearanceSettings, 'adminAccentColor'>, resolvedMode: 'light' | 'dark'): AccentTokens;
48
+ /** Map accent tokens to the CSS custom properties the theme consumes. */
49
+ export declare function accentTokensToCssVars(tokens: AccentTokens): Record<string, string>;
50
+ //# sourceMappingURL=theme-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-tokens.d.ts","sourceRoot":"","sources":["../../src/appearance/theme-tokens.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAErE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,eAAe,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,YAAY,CAAA;IACnB,IAAI,EAAE,YAAY,CAAA;CACnB;AAKD;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,eAAe,EAAE,YAAY,CA0GhE,CAAA;AAED,eAAO,MAAM,kBAAkB,EAAE,YAAY,EAAkC,CAAA;AAE/E,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,eAAe,CAEtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAA;IACd,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAiBnE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,IAAI,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,EACtD,YAAY,EAAE,OAAO,GAAG,MAAM,GAC7B,YAAY,CAmBd;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUlF"}