@mintlify/og 0.0.20 → 0.0.21

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.
@@ -0,0 +1,9 @@
1
+ import type { OgCardInput, OgImageAsset } from '../types.js';
2
+ import type { OgPageParams } from './page-params.js';
3
+ import type { OgThemeParams } from './theme-params.js';
4
+ export type OgAssetLoader = (src: string) => Promise<OgImageAsset | null>;
5
+ export type OgAssetLoaders = {
6
+ image: OgAssetLoader;
7
+ font: OgAssetLoader;
8
+ };
9
+ export declare function buildOgCardInput(pageParams: OgPageParams, themeParams: OgThemeParams, loaders: OgAssetLoaders): Promise<OgCardInput>;
@@ -0,0 +1,59 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { DEFAULT_PRESET } from '../constants.js';
11
+ import { loadGoogleFont } from '../inputs/fonts.js';
12
+ import { resolveOgLogoAppearance } from '../inputs/palette.js';
13
+ const SELF_HOSTED_FONT_WEIGHT = 400;
14
+ const loadCardFont = (themeParams, text, loadFont) => __awaiter(void 0, void 0, void 0, function* () {
15
+ const { fontFamily, fontSource } = themeParams;
16
+ if (!fontFamily)
17
+ return null;
18
+ if (!fontSource)
19
+ return loadGoogleFont(fontFamily, text);
20
+ const asset = yield loadFont(fontSource);
21
+ if (!asset)
22
+ return null;
23
+ const data = Buffer.from(asset.bytes.buffer, asset.bytes.byteOffset, asset.bytes.byteLength);
24
+ return { family: fontFamily, faces: [{ weight: SELF_HOSTED_FONT_WEIGHT, data }] };
25
+ });
26
+ export function buildOgCardInput(pageParams, themeParams, loaders) {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ var _a;
29
+ const { division, title, description } = pageParams;
30
+ const theme = {
31
+ appearance: themeParams.appearance,
32
+ primaryColor: themeParams.primaryColor,
33
+ lightColor: themeParams.lightColor,
34
+ backgroundLight: themeParams.backgroundLight,
35
+ backgroundDark: themeParams.backgroundDark,
36
+ surface: themeParams.surface,
37
+ eyebrowColor: themeParams.eyebrowColor,
38
+ };
39
+ const isLight = resolveOgLogoAppearance(theme, (_a = themeParams.preset) !== null && _a !== void 0 ? _a : DEFAULT_PRESET) === 'light';
40
+ const logoSrc = isLight ? themeParams.logoLight : themeParams.logoDark;
41
+ const fontText = [division, title, description].filter(Boolean).join(' ');
42
+ const [logo, background, font] = yield Promise.all([
43
+ logoSrc ? loaders.image(logoSrc) : null,
44
+ themeParams.background ? loaders.image(themeParams.background) : null,
45
+ loadCardFont(themeParams, fontText, loaders.font),
46
+ ]);
47
+ return {
48
+ preset: themeParams.preset,
49
+ eyebrowStyle: themeParams.eyebrowStyle,
50
+ eyebrow: division,
51
+ title,
52
+ description,
53
+ theme,
54
+ logo,
55
+ background,
56
+ font,
57
+ };
58
+ });
59
+ }
@@ -0,0 +1,3 @@
1
+ import type { OgCardInput } from '../types.js';
2
+ import type { OgThemeParams } from './theme-params.js';
3
+ export declare function checkOgCard(input: OgCardInput, themeParams: OgThemeParams): string[];
@@ -0,0 +1,36 @@
1
+ import { DEFAULT_PRESET } from '../constants.js';
2
+ import { contrast, isBrandSurface, resolveOgPalette } from '../inputs/palette.js';
3
+ import { getTitleBox } from '../layouts/index.js';
4
+ const AVERAGE_CHAR_WIDTH_EM = 0.55;
5
+ const MAX_TITLE_LINES = 2;
6
+ const MIN_BRAND_CONTRAST = 3;
7
+ const estimateTitleLines = (input) => {
8
+ var _a, _b, _c;
9
+ const { width, size } = getTitleBox((_a = input.preset) !== null && _a !== void 0 ? _a : DEFAULT_PRESET);
10
+ const length = (_c = (_b = input.title) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0;
11
+ return Math.ceil((length * AVERAGE_CHAR_WIDTH_EM * size) / width);
12
+ };
13
+ export function checkOgCard(input, themeParams) {
14
+ const warnings = [];
15
+ const titleLines = estimateTitleLines(input);
16
+ if (titleLines > MAX_TITLE_LINES) {
17
+ warnings.push(`The title needs about ${titleLines} lines but the card shows ${MAX_TITLE_LINES}, so it will be cut off.`);
18
+ }
19
+ if (!themeParams.logoLight && !themeParams.logoDark) {
20
+ warnings.push('No logo is configured in docs.json, so the card has no logo.');
21
+ }
22
+ else if (!input.logo) {
23
+ warnings.push("The logo couldn't be loaded, so the card has no logo.");
24
+ }
25
+ if (themeParams.fontFamily && !input.font) {
26
+ warnings.push(`The font "${themeParams.fontFamily}" couldn't be loaded, so the card uses Inter.`);
27
+ }
28
+ if (input.preset && input.preset !== DEFAULT_PRESET && isBrandSurface(input.theme)) {
29
+ const palette = resolveOgPalette(input.theme);
30
+ const lowContrast = [palette.eyebrow, palette.description].some((color) => contrast(color, palette.background) < MIN_BRAND_CONTRAST);
31
+ if (lowContrast) {
32
+ warnings.push('Some text has low contrast on the brand surface. Try another eyebrow color.');
33
+ }
34
+ }
35
+ return warnings;
36
+ }
@@ -0,0 +1,7 @@
1
+ export { buildOgCardInput, type OgAssetLoader, type OgAssetLoaders } from './card-input.js';
2
+ export { checkOgCard } from './checks.js';
3
+ export { createLocalAssetLoaders, MAX_OG_ASSET_BYTES, toOgFontAsset, toOgImageAsset, } from './local-assets.js';
4
+ export { findPageAncestors, findPageDivisionName, findPageGroupName } from './navigation.js';
5
+ export { getOgPageParams, type OgPageInput, type OgPageParams } from './page-params.js';
6
+ export { deriveOgThemeParams, getOgThemeRevision, type OgThemeParams } from './theme-params.js';
7
+ export { applyCardThemeOverride, applyThumbnailOverride, resolvePageThumbnailOverride, serializeCardThemeOverride, type CardThemeOverride, } from './thumbnail-override.js';
@@ -0,0 +1,7 @@
1
+ export { buildOgCardInput } from './card-input.js';
2
+ export { checkOgCard } from './checks.js';
3
+ export { createLocalAssetLoaders, MAX_OG_ASSET_BYTES, toOgFontAsset, toOgImageAsset, } from './local-assets.js';
4
+ export { findPageAncestors, findPageDivisionName, findPageGroupName } from './navigation.js';
5
+ export { getOgPageParams } from './page-params.js';
6
+ export { deriveOgThemeParams, getOgThemeRevision } from './theme-params.js';
7
+ export { applyCardThemeOverride, applyThumbnailOverride, resolvePageThumbnailOverride, serializeCardThemeOverride, } from './thumbnail-override.js';
@@ -0,0 +1,6 @@
1
+ import type { OgImageAsset } from '../types.js';
2
+ import type { OgAssetLoaders } from './card-input.js';
3
+ export declare const MAX_OG_ASSET_BYTES: number;
4
+ export declare function toOgImageAsset(bytes: Uint8Array, contentTypeHeader: string | undefined): OgImageAsset | null;
5
+ export declare function toOgFontAsset(bytes: Uint8Array): OgImageAsset | null;
6
+ export declare function createLocalAssetLoaders(root: string, remote: OgAssetLoaders): OgAssetLoaders;
@@ -0,0 +1,71 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { isAbsoluteUrl } from '@mintlify/common';
11
+ import { readFile } from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ import { getFontContentType } from '../inputs/font-file.js';
14
+ export const MAX_OG_ASSET_BYTES = 5 * 1024 * 1024;
15
+ const IMAGE_CONTENT_TYPES = {
16
+ '.avif': 'image/avif',
17
+ '.gif': 'image/gif',
18
+ '.jpeg': 'image/jpeg',
19
+ '.jpg': 'image/jpeg',
20
+ '.png': 'image/png',
21
+ '.svg': 'image/svg+xml',
22
+ '.webp': 'image/webp',
23
+ };
24
+ const SUPPORTED_IMAGE_CONTENT_TYPES = new Set(Object.values(IMAGE_CONTENT_TYPES));
25
+ export function toOgImageAsset(bytes, contentTypeHeader) {
26
+ var _a;
27
+ const contentType = (_a = contentTypeHeader === null || contentTypeHeader === void 0 ? void 0 : contentTypeHeader.split(';')[0]) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
28
+ if (bytes.byteLength === 0 || !contentType || !SUPPORTED_IMAGE_CONTENT_TYPES.has(contentType)) {
29
+ return null;
30
+ }
31
+ return { bytes, contentType };
32
+ }
33
+ export function toOgFontAsset(bytes) {
34
+ const contentType = getFontContentType(bytes);
35
+ return contentType ? { bytes, contentType } : null;
36
+ }
37
+ const resolveInside = (rootDir, src) => {
38
+ const filePath = path.resolve(rootDir, `.${path.posix.normalize(`/${src}`)}`);
39
+ return filePath.startsWith(`${rootDir}${path.sep}`) ? filePath : null;
40
+ };
41
+ const readInside = (rootDir, src) => __awaiter(void 0, void 0, void 0, function* () {
42
+ const filePath = resolveInside(rootDir, src);
43
+ if (!filePath)
44
+ return null;
45
+ try {
46
+ const bytes = new Uint8Array(yield readFile(filePath));
47
+ return bytes.byteLength === 0 || bytes.byteLength > MAX_OG_ASSET_BYTES ? null : bytes;
48
+ }
49
+ catch (_a) {
50
+ return null;
51
+ }
52
+ });
53
+ export function createLocalAssetLoaders(root, remote) {
54
+ const rootDir = path.resolve(root);
55
+ const image = (src) => __awaiter(this, void 0, void 0, function* () {
56
+ if (isAbsoluteUrl(src))
57
+ return remote.image(src);
58
+ const contentType = IMAGE_CONTENT_TYPES[path.extname(src).toLowerCase()];
59
+ if (!contentType)
60
+ return null;
61
+ const bytes = yield readInside(rootDir, src);
62
+ return bytes && toOgImageAsset(bytes, contentType);
63
+ });
64
+ const font = (src) => __awaiter(this, void 0, void 0, function* () {
65
+ if (isAbsoluteUrl(src))
66
+ return remote.font(src);
67
+ const bytes = yield readInside(rootDir, src);
68
+ return bytes && toOgFontAsset(bytes);
69
+ });
70
+ return { image, font };
71
+ }
@@ -0,0 +1,6 @@
1
+ import type { DivisionType, DocsConfig } from '@mintlify/validation';
2
+ type NavigationNode = Record<string, unknown>;
3
+ export declare const findPageAncestors: (path: string, docsConfig: DocsConfig) => NavigationNode[];
4
+ export declare const findPageGroupName: (path: string, docsConfig: DocsConfig) => string;
5
+ export declare const findPageDivisionName: (path: string, docsConfig: DocsConfig, division: DivisionType) => string | undefined;
6
+ export {};
@@ -0,0 +1,45 @@
1
+ import { isEqualIgnoringLeadingSlash } from '@mintlify/common';
2
+ const DEFAULT_GROUP_NAME = 'Documentation';
3
+ const isNavigationNode = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
4
+ const isPage = (value, path) => typeof value === 'string' && isEqualIgnoringLeadingSlash(value, path);
5
+ const searchAncestors = (value, path, ancestors, includeRoot) => {
6
+ if (Array.isArray(value)) {
7
+ for (const item of value) {
8
+ const found = searchAncestors(item, path, ancestors, includeRoot);
9
+ if (found)
10
+ return found;
11
+ }
12
+ return null;
13
+ }
14
+ if (!isNavigationNode(value))
15
+ return null;
16
+ const lineage = [value, ...ancestors];
17
+ for (const [key, child] of Object.entries(value)) {
18
+ if (includeRoot && key === 'root' && isPage(child, path))
19
+ return lineage;
20
+ if (key === 'pages' && Array.isArray(child)) {
21
+ for (const page of child) {
22
+ if (isPage(page, path))
23
+ return lineage;
24
+ const found = searchAncestors(page, path, lineage, includeRoot);
25
+ if (found)
26
+ return found;
27
+ }
28
+ continue;
29
+ }
30
+ const found = searchAncestors(child, path, lineage, includeRoot);
31
+ if (found)
32
+ return found;
33
+ }
34
+ return null;
35
+ };
36
+ export const findPageAncestors = (path, docsConfig) => { var _a; return (_a = searchAncestors(docsConfig.navigation, path, [], true)) !== null && _a !== void 0 ? _a : []; };
37
+ export const findPageGroupName = (path, docsConfig) => {
38
+ var _a;
39
+ const [parent] = (_a = searchAncestors(docsConfig.navigation, path, [], false)) !== null && _a !== void 0 ? _a : [];
40
+ const group = parent === null || parent === void 0 ? void 0 : parent.group;
41
+ return typeof group === 'string' && group ? group : DEFAULT_GROUP_NAME;
42
+ };
43
+ export const findPageDivisionName = (path, docsConfig, division) => findPageAncestors(path, docsConfig)
44
+ .map((node) => node[division])
45
+ .find((name) => typeof name === 'string');
@@ -0,0 +1,14 @@
1
+ import type { DocsConfig } from '@mintlify/validation';
2
+ export type OgPageParams = {
3
+ division?: string;
4
+ title?: string;
5
+ description?: string;
6
+ thumbnail?: string;
7
+ };
8
+ export type OgPageInput = {
9
+ path: string;
10
+ title: string;
11
+ description?: string;
12
+ thumbnail?: unknown;
13
+ };
14
+ export declare function getOgPageParams(page: OgPageInput, docsConfig: DocsConfig): OgPageParams;
@@ -0,0 +1,27 @@
1
+ import { findPageDivisionName, findPageGroupName } from './navigation.js';
2
+ import { resolvePageThumbnailOverride, serializeCardThemeOverride } from './thumbnail-override.js';
3
+ const MAX_DESCRIPTION_LENGTH = 160;
4
+ const resolveEyebrow = (path, docsConfig, group) => {
5
+ var _a, _b, _c, _d;
6
+ const source = (_c = (_b = (_a = docsConfig.thumbnails) === null || _a === void 0 ? void 0 : _a.eyebrow) === null || _b === void 0 ? void 0 : _b.source) !== null && _c !== void 0 ? _c : 'group';
7
+ if (source === 'group')
8
+ return group;
9
+ if (source === 'none')
10
+ return '';
11
+ return (_d = findPageDivisionName(path, docsConfig, source)) !== null && _d !== void 0 ? _d : group;
12
+ };
13
+ export function getOgPageParams(page, docsConfig) {
14
+ var _a, _b;
15
+ const { path, title } = page;
16
+ const group = findPageGroupName(path, docsConfig);
17
+ const override = resolvePageThumbnailOverride(page.thumbnail, docsConfig, path);
18
+ const thumbnail = serializeCardThemeOverride(override);
19
+ const description = page.description
20
+ ? Array.from(page.description).slice(0, MAX_DESCRIPTION_LENGTH).join('')
21
+ : page.description;
22
+ if (title.toLowerCase() === 'index') {
23
+ return { division: (_a = override.eyebrow) !== null && _a !== void 0 ? _a : '', title: group, description, thumbnail };
24
+ }
25
+ const division = (_b = override.eyebrow) !== null && _b !== void 0 ? _b : resolveEyebrow(path, docsConfig, group);
26
+ return { division, title, description, thumbnail };
27
+ }
@@ -0,0 +1,16 @@
1
+ import type { DocsConfig } from '@mintlify/validation';
2
+ import type { OgPresetName } from '../layouts/types.js';
3
+ import type { OgEyebrowStyle, OgThemeInput } from '../types.js';
4
+ export type OgThemeParams = OgThemeInput & {
5
+ background?: string;
6
+ eyebrowStyle?: OgEyebrowStyle;
7
+ fontFamily?: string;
8
+ fontSource?: string;
9
+ logoDark?: string;
10
+ logoLight?: string;
11
+ preset?: OgPresetName;
12
+ };
13
+ export declare function getOgThemeRevision(themeParams: OgThemeParams, options?: {
14
+ includeRendererRevision?: boolean;
15
+ }): Promise<string>;
16
+ export declare function deriveOgThemeParams(docsConfig: DocsConfig): OgThemeParams;
@@ -0,0 +1,46 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { getBackgroundColors } from '@mintlify/common/colors';
11
+ import { OG_RENDERER_REVISION } from '../constants.js';
12
+ export function getOgThemeRevision(themeParams_1) {
13
+ return __awaiter(this, arguments, void 0, function* (themeParams, options = {}) {
14
+ const revisionParams = options.includeRendererRevision === false
15
+ ? themeParams
16
+ : Object.assign(Object.assign({}, themeParams), { rendererRevision: OG_RENDERER_REVISION });
17
+ const input = new TextEncoder().encode(JSON.stringify(revisionParams));
18
+ const digest = yield globalThis.crypto.subtle.digest('SHA-256', input);
19
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, '0'))
20
+ .join('')
21
+ .slice(0, 24);
22
+ });
23
+ }
24
+ export function deriveOgThemeParams(docsConfig) {
25
+ var _a, _b, _c, _d, _e, _f, _g, _h;
26
+ const { lightHex, darkHex, background } = getBackgroundColors(docsConfig);
27
+ const logoLight = typeof docsConfig.logo === 'string' ? docsConfig.logo : (_a = docsConfig.logo) === null || _a === void 0 ? void 0 : _a.light;
28
+ const logoDark = typeof docsConfig.logo === 'string' ? docsConfig.logo : (_b = docsConfig.logo) === null || _b === void 0 ? void 0 : _b.dark;
29
+ const { thumbnails } = docsConfig;
30
+ return {
31
+ appearance: (_c = thumbnails === null || thumbnails === void 0 ? void 0 : thumbnails.appearance) !== null && _c !== void 0 ? _c : (_d = docsConfig.appearance) === null || _d === void 0 ? void 0 : _d.default,
32
+ background,
33
+ backgroundDark: darkHex,
34
+ backgroundLight: lightHex,
35
+ eyebrowColor: (_e = thumbnails === null || thumbnails === void 0 ? void 0 : thumbnails.eyebrow) === null || _e === void 0 ? void 0 : _e.color,
36
+ eyebrowStyle: (_f = thumbnails === null || thumbnails === void 0 ? void 0 : thumbnails.eyebrow) === null || _f === void 0 ? void 0 : _f.style,
37
+ fontFamily: (_g = thumbnails === null || thumbnails === void 0 ? void 0 : thumbnails.fonts) === null || _g === void 0 ? void 0 : _g.family,
38
+ fontSource: (_h = thumbnails === null || thumbnails === void 0 ? void 0 : thumbnails.fonts) === null || _h === void 0 ? void 0 : _h.source,
39
+ lightColor: docsConfig.colors.light,
40
+ logoDark,
41
+ logoLight,
42
+ preset: thumbnails === null || thumbnails === void 0 ? void 0 : thumbnails.preset,
43
+ primaryColor: docsConfig.colors.primary,
44
+ surface: thumbnails === null || thumbnails === void 0 ? void 0 : thumbnails.surface,
45
+ };
46
+ }
@@ -0,0 +1,7 @@
1
+ import { type DocsConfig, type ThumbnailOverride } from '@mintlify/validation';
2
+ import type { OgThemeParams } from './theme-params.js';
3
+ export type CardThemeOverride = Omit<ThumbnailOverride, 'eyebrow'>;
4
+ export declare function resolvePageThumbnailOverride(pageThumbnail: unknown, docsConfig: DocsConfig, path: string): ThumbnailOverride;
5
+ export declare function serializeCardThemeOverride({ eyebrow: _, ...override }: ThumbnailOverride): string | undefined;
6
+ export declare function applyCardThemeOverride(themeParams: OgThemeParams, serialized: string | undefined): OgThemeParams;
7
+ export declare function applyThumbnailOverride(themeParams: OgThemeParams, { preset, appearance, surface, background }: CardThemeOverride): OgThemeParams;
@@ -0,0 +1,54 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { thumbnailOverrideSchema, } from '@mintlify/validation';
13
+ import { findPageAncestors } from './navigation.js';
14
+ const cardThemeOverrideSchema = thumbnailOverrideSchema.omit({ eyebrow: true });
15
+ const parseOverride = (value) => {
16
+ const result = thumbnailOverrideSchema.safeParse(value);
17
+ return result.success ? result.data : {};
18
+ };
19
+ export function resolvePageThumbnailOverride(pageThumbnail, docsConfig, path) {
20
+ const layers = [
21
+ parseOverride(pageThumbnail),
22
+ ...findPageAncestors(path, docsConfig).map((node) => parseOverride(node.thumbnail)),
23
+ ];
24
+ const pick = (key) => { var _a; return (_a = layers.find((layer) => layer[key] !== undefined)) === null || _a === void 0 ? void 0 : _a[key]; };
25
+ return {
26
+ preset: pick('preset'),
27
+ appearance: pick('appearance'),
28
+ surface: pick('surface'),
29
+ background: pick('background'),
30
+ eyebrow: pick('eyebrow'),
31
+ };
32
+ }
33
+ export function serializeCardThemeOverride(_a) {
34
+ var { eyebrow: _ } = _a, override = __rest(_a, ["eyebrow"]);
35
+ const serialized = JSON.stringify(override);
36
+ return serialized === '{}' ? undefined : serialized;
37
+ }
38
+ const parseCardThemeOverride = (serialized) => {
39
+ try {
40
+ const result = cardThemeOverrideSchema.safeParse(JSON.parse(serialized));
41
+ return result.success ? result.data : {};
42
+ }
43
+ catch (_a) {
44
+ return {};
45
+ }
46
+ };
47
+ export function applyCardThemeOverride(themeParams, serialized) {
48
+ return serialized
49
+ ? applyThumbnailOverride(themeParams, parseCardThemeOverride(serialized))
50
+ : themeParams;
51
+ }
52
+ export function applyThumbnailOverride(themeParams, { preset, appearance, surface, background }) {
53
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, themeParams), (preset && { preset })), (appearance && { appearance })), (surface && { surface })), (background !== undefined && { background }));
54
+ }
@@ -15,6 +15,7 @@ export type OgPalette = {
15
15
  badgeText: string;
16
16
  };
17
17
  export declare const luminance: (hex: string) => number;
18
+ export declare const contrast: (a: string, b: string) => number;
18
19
  export declare const isBrandSurface: (theme: OgThemeInput) => boolean;
19
20
  export declare const resolveOgPalette: (theme: OgThemeInput) => OgPalette;
20
21
  export declare const resolveOgLogoAppearance: (theme: OgThemeInput, preset: OgPreset) => OgAppearance;
@@ -37,7 +37,7 @@ export const luminance = (hex) => {
37
37
  const { red, green, blue } = hexRgb(hex);
38
38
  return 0.2126 * toLinear(red) + 0.7152 * toLinear(green) + 0.0722 * toLinear(blue);
39
39
  };
40
- const contrast = (a, b) => {
40
+ export const contrast = (a, b) => {
41
41
  const [first, second] = [luminance(a), luminance(b)];
42
42
  return (Math.max(first, second) + 0.05) / (Math.min(first, second) + 0.05);
43
43
  };
@@ -1,4 +1,4 @@
1
- import type { OgEyebrowStyle } from '../types.js';
1
+ import type { OgEyebrowStyle, OgPreset } from '../types.js';
2
2
  import type { OgPresetName } from './types.js';
3
3
  export declare const OG_LAYOUTS: {
4
4
  legacy: ({ content, theme, logo, fontFamily }: import("./types.js").OgLayoutProps) => import("react/jsx-runtime").JSX.Element;
@@ -6,3 +6,7 @@ export declare const OG_LAYOUTS: {
6
6
  export declare const PRESET_EYEBROW_STYLES: Record<OgPresetName, OgEyebrowStyle>;
7
7
  export { LegacyLayout } from './legacy.js';
8
8
  export { PresetLayout } from './preset.js';
9
+ export declare const getTitleBox: (preset: OgPreset) => {
10
+ width: number;
11
+ size: number;
12
+ };
@@ -1,4 +1,5 @@
1
- import { LegacyLayout } from './legacy.js';
1
+ import { LEGACY_TITLE_BOX, LegacyLayout } from './legacy.js';
2
+ import { getPresetTitleBox } from './preset.js';
2
3
  export const OG_LAYOUTS = { legacy: LegacyLayout };
3
4
  export const PRESET_EYEBROW_STYLES = {
4
5
  classic: 'bar',
@@ -7,3 +8,4 @@ export const PRESET_EYEBROW_STYLES = {
7
8
  };
8
9
  export { LegacyLayout } from './legacy.js';
9
10
  export { PresetLayout } from './preset.js';
11
+ export const getTitleBox = (preset) => preset === 'legacy' ? LEGACY_TITLE_BOX : getPresetTitleBox(preset);
@@ -1,2 +1,6 @@
1
1
  import type { OgLayoutProps } from './types.js';
2
+ export declare const LEGACY_TITLE_BOX: {
3
+ width: number;
4
+ size: number;
5
+ };
2
6
  export declare const LegacyLayout: ({ content, theme, logo, fontFamily }: OgLayoutProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { LOGO_HEIGHT } from '../constants.js';
3
3
  import { getFontStack } from './font-stack.js';
4
- const TEXT_MAX_WIDTH = '1000px';
4
+ export const LEGACY_TITLE_BOX = { width: 1000, size: 72 };
5
+ const TEXT_MAX_WIDTH = `${LEGACY_TITLE_BOX.width}px`;
5
6
  const DESCRIPTION_WIDTH = '750px';
6
7
  const MAX_TEXT_LINES = 2;
7
8
  const TEXT_COLORS = {
@@ -45,7 +46,7 @@ const styles = {
45
46
  overflow: 'hidden',
46
47
  lineClamp: MAX_TEXT_LINES,
47
48
  marginTop: '12px',
48
- fontSize: '72px',
49
+ fontSize: `${LEGACY_TITLE_BOX.size}px`,
49
50
  fontWeight: 600,
50
51
  lineHeight: 1.15,
51
52
  letterSpacing: '-0.025em',
@@ -1,2 +1,6 @@
1
- import type { OgPresetLayoutProps } from './types.js';
1
+ import type { OgPresetLayoutProps, OgPresetName } from './types.js';
2
+ export declare const getPresetTitleBox: (preset: OgPresetName) => {
3
+ width: number;
4
+ size: number;
5
+ };
2
6
  export declare const PresetLayout: ({ preset, content, palette, backgroundImageUrl, eyebrowStyle, logo, fontFamily, fitText, }: OgPresetLayoutProps) => import("react/jsx-runtime").JSX.Element;
@@ -63,6 +63,10 @@ const PRESET_SETTINGS = {
63
63
  textLines: 3,
64
64
  },
65
65
  };
66
+ export const getPresetTitleBox = (preset) => ({
67
+ width: PRESET_SETTINGS[preset].titleWidth,
68
+ size: PRESET_SETTINGS[preset].titleSize,
69
+ });
66
70
  const BAR = { border: 2, padding: 25.229 };
67
71
  const PILL = { paddingBlock: 5.5, paddingInline: 15 };
68
72
  const TRACKING = -0.02;