@aranova/tracking-next 0.20.0 → 0.20.2

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/blog.js ADDED
@@ -0,0 +1,1045 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/blog.ts
31
+ var blog_exports = {};
32
+ __export(blog_exports, {
33
+ BASE_BLOG_THEME: () => BASE_BLOG_THEME,
34
+ BLOG_BREAKPOINTS: () => BLOG_BREAKPOINTS,
35
+ BLOG_BREAKPOINT_MIN_WIDTHS_PX: () => BLOG_BREAKPOINT_MIN_WIDTHS_PX,
36
+ BLOG_MDX_ALLOWED_COMPONENTS: () => BLOG_MDX_ALLOWED_COMPONENTS,
37
+ BLOG_MDX_COMPONENT_GRAMMAR: () => BLOG_MDX_COMPONENT_GRAMMAR,
38
+ BLOG_MDX_MAX_DEPTH: () => BLOG_MDX_MAX_DEPTH,
39
+ BLOG_MDX_MAX_NODES: () => BLOG_MDX_MAX_NODES,
40
+ BLOG_MDX_MAX_SIZE: () => BLOG_MDX_MAX_SIZE,
41
+ BLOG_THEME_PRESETS: () => BLOG_THEME_PRESETS,
42
+ blogHeroImageSchema: () => blogHeroImageSchema,
43
+ blogImageSchema: () => blogImageSchema,
44
+ blogManifestEntrySchema: () => blogManifestEntrySchema,
45
+ blogManifestSchema: () => blogManifestSchema,
46
+ blogManifestWireSchema: () => blogManifestWireSchema,
47
+ blogPostContentSchema: () => blogPostContentSchema,
48
+ blogPostContentWireSchema: () => blogPostContentWireSchema,
49
+ blogSeoSchema: () => blogSeoSchema,
50
+ blogThemeColorsSchema: () => blogThemeColorsSchema,
51
+ blogThemeOverrideSchema: () => blogThemeOverrideSchema,
52
+ blogThemeSchema: () => blogThemeSchema,
53
+ blogThemeSpacingSchema: () => blogThemeSpacingSchema,
54
+ blogThemeToCssVars: () => blogThemeToCssVars,
55
+ blogThemeTypographySchema: () => blogThemeTypographySchema,
56
+ buildBlogPostMetadata: () => buildBlogPostMetadata,
57
+ buildBlogPostingJsonLd: () => buildBlogPostingJsonLd,
58
+ buildBlogSitemapEntries: () => buildBlogSitemapEntries,
59
+ buildFaqJsonLd: () => buildFaqJsonLd,
60
+ createBlogClient: () => createBlogClient,
61
+ createBlogFontTheme: () => createBlogFontTheme,
62
+ estimateReadingTime: () => estimateReadingTime,
63
+ extractBlogHeadings: () => extractBlogHeadings,
64
+ extractFaqPairs: () => extractFaqPairs,
65
+ joinClassNames: () => joinClassNames,
66
+ nullishField: () => nullishField,
67
+ parseBlogManifest: () => parseBlogManifest,
68
+ parseBlogPostContent: () => parseBlogPostContent,
69
+ resolveBlogTheme: () => resolveBlogTheme,
70
+ responsive: () => responsive,
71
+ sanitizeBlogTheme: () => sanitizeBlogTheme,
72
+ serializeJsonLd: () => serializeJsonLd,
73
+ toBlogBreakpointMap: () => toBlogBreakpointMap,
74
+ validateBlogMdx: () => validateBlogMdx
75
+ });
76
+ module.exports = __toCommonJS(blog_exports);
77
+
78
+ // ../tracking-core/src/resources/blog/schema.ts
79
+ var import_zod2 = require("zod");
80
+
81
+ // ../tracking-core/src/resources/blog/theme.ts
82
+ var import_zod = require("zod");
83
+ var BLOG_BREAKPOINTS = ["sm", "md", "lg", "xl", "2xl", "3xl"];
84
+ var BLOG_BREAKPOINT_MIN_WIDTHS_PX = {
85
+ sm: 640,
86
+ md: 768,
87
+ lg: 1024,
88
+ xl: 1280,
89
+ "2xl": 1536,
90
+ "3xl": 1920
91
+ };
92
+ function toBlogBreakpointMap(value) {
93
+ return typeof value === "object" ? value : { base: value };
94
+ }
95
+ var HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
96
+ var FUNCTIONAL_COLOR = /^(rgb|rgba|hsl|hsla|oklch)\([0-9a-zA-Z ,.%/-]*\)$/;
97
+ var LENGTH = /^-?\d+(\.\d+)?(px|rem|em|%|ch|vw)$/;
98
+ var LINE_HEIGHT = /^-?\d+(\.\d+)?(px|rem|em|%|ch|vw)?$/;
99
+ var FONT_FAMILY_TOKEN = /^['"]?[a-zA-Z0-9 -]+['"]?$/;
100
+ var FONT_FAMILY_VAR_TOKEN = /^var\(--[a-zA-Z0-9-]+\)$/;
101
+ var colorValueSchema = import_zod.z.string().refine((v) => HEX_COLOR.test(v) || FUNCTIONAL_COLOR.test(v), {
102
+ message: "must be a #hex or rgb()/rgba()/hsl()/hsla()/oklch() color"
103
+ });
104
+ var lengthValueSchema = import_zod.z.string().regex(LENGTH, {
105
+ message: "must be a CSS length (px|rem|em|%|ch|vw)"
106
+ });
107
+ var lineHeightValueSchema = import_zod.z.string().regex(LINE_HEIGHT, {
108
+ message: "must be a CSS length or unitless line-height"
109
+ });
110
+ var fontWeightSchema = import_zod.z.number().int().min(100).max(1e3);
111
+ var alignValueSchema = import_zod.z.enum(["left", "center", "right"]);
112
+ var fontFamilySchema = import_zod.z.string().refine(
113
+ (v) => {
114
+ const tokens = v.split(",").map((t) => t.trim());
115
+ return tokens.length > 0 && tokens.every((t) => FONT_FAMILY_TOKEN.test(t) || FONT_FAMILY_VAR_TOKEN.test(t));
116
+ },
117
+ { message: "must be a comma-separated list of font-family or var(--custom-property) tokens" }
118
+ );
119
+ function nullishField(schema) {
120
+ return schema.nullish().transform((v) => v ?? void 0);
121
+ }
122
+ function breakpointMapSchema(valueSchema) {
123
+ return import_zod.z.object({
124
+ base: valueSchema,
125
+ sm: nullishField(valueSchema).catch(void 0),
126
+ md: nullishField(valueSchema).catch(void 0),
127
+ lg: nullishField(valueSchema).catch(void 0),
128
+ xl: nullishField(valueSchema).catch(void 0),
129
+ "2xl": nullishField(valueSchema).catch(void 0),
130
+ "3xl": nullishField(valueSchema).catch(void 0)
131
+ });
132
+ }
133
+ function responsive(valueSchema) {
134
+ return import_zod.z.union([valueSchema, breakpointMapSchema(valueSchema)]);
135
+ }
136
+ var blogThemeColorsShape = {
137
+ background: colorValueSchema,
138
+ surface: colorValueSchema,
139
+ text: colorValueSchema,
140
+ text_muted: colorValueSchema,
141
+ primary: colorValueSchema,
142
+ primary_foreground: colorValueSchema,
143
+ link: colorValueSchema,
144
+ border: colorValueSchema,
145
+ code_background: colorValueSchema,
146
+ heading: colorValueSchema
147
+ };
148
+ var blogThemeColorsSchema = import_zod.z.object({
149
+ ...blogThemeColorsShape,
150
+ // The ONE optional color in a resolved theme: when unset, headings fall back
151
+ // to `text` in the stylesheet (`--_heading-color`), so `blogThemeToCssVars`
152
+ // only emits `--ara-blog-color-heading` when a theme actually sets it.
153
+ heading: nullishField(colorValueSchema)
154
+ });
155
+ var blogThemeTypographySchema = import_zod.z.object({
156
+ heading_family: fontFamilySchema,
157
+ body_family: fontFamilySchema,
158
+ heading_weight: fontWeightSchema,
159
+ body_weight: fontWeightSchema,
160
+ h1_size: responsive(lengthValueSchema),
161
+ h2_size: responsive(lengthValueSchema),
162
+ h3_size: responsive(lengthValueSchema),
163
+ h4_size: responsive(lengthValueSchema),
164
+ body_size: responsive(lengthValueSchema),
165
+ body_line_height: responsive(lineHeightValueSchema),
166
+ small_size: responsive(lengthValueSchema),
167
+ // Scalar (non-responsive) length; the grammar allows negative em values.
168
+ // h1/h2 consume it directly, h3/h4 at half strength (calc in the base CSS).
169
+ heading_letter_spacing: lengthValueSchema,
170
+ // Figure caption text alignment (left/center/right). Site-owner override;
171
+ // default center in the base CSS if unset.
172
+ caption_align: alignValueSchema
173
+ });
174
+ var blogThemeSpacingSchema = import_zod.z.object({
175
+ content_max_width: responsive(lengthValueSchema),
176
+ block_gap: responsive(lengthValueSchema),
177
+ section_gap: responsive(lengthValueSchema)
178
+ });
179
+ var blogThemeColorsPartialSchema = import_zod.z.object(
180
+ Object.fromEntries(
181
+ Object.keys(blogThemeColorsShape).map((key) => [key, nullishField(colorValueSchema)])
182
+ )
183
+ );
184
+ var blogThemeSchema = import_zod.z.object({
185
+ colors: blogThemeColorsSchema,
186
+ colors_dark: nullishField(blogThemeColorsPartialSchema),
187
+ typography: blogThemeTypographySchema,
188
+ spacing: blogThemeSpacingSchema,
189
+ radius: responsive(lengthValueSchema)
190
+ });
191
+ function dropInvalidFields(shape) {
192
+ const out = {};
193
+ for (const [key, schema] of Object.entries(shape)) {
194
+ out[key] = nullTolerant(schema).catch(void 0);
195
+ }
196
+ return out;
197
+ }
198
+ function nullTolerant(schema) {
199
+ if (schema instanceof import_zod.z.ZodUnion) {
200
+ const options = schema.options;
201
+ return import_zod.z.union([...options, import_zod.z.null()]).optional().transform((v) => v ?? void 0);
202
+ }
203
+ return nullishField(schema);
204
+ }
205
+ var blogThemeColorsOverrideSchema = import_zod.z.object(dropInvalidFields(blogThemeColorsShape));
206
+ var blogThemeTypographyOverrideSchema = import_zod.z.object(
207
+ dropInvalidFields(blogThemeTypographySchema.shape)
208
+ );
209
+ var blogThemeSpacingOverrideSchema = import_zod.z.object(dropInvalidFields(blogThemeSpacingSchema.shape));
210
+ var blogThemeOverrideSchema = import_zod.z.object(
211
+ dropInvalidFields({
212
+ colors: blogThemeColorsOverrideSchema,
213
+ colors_dark: blogThemeColorsOverrideSchema,
214
+ typography: blogThemeTypographyOverrideSchema,
215
+ spacing: blogThemeSpacingOverrideSchema,
216
+ radius: responsive(lengthValueSchema)
217
+ })
218
+ );
219
+ var BASE_BLOG_THEME = {
220
+ colors: {
221
+ background: "#ffffff",
222
+ surface: "#f5f5f5",
223
+ text: "#171717",
224
+ text_muted: "#6b6b6b",
225
+ primary: "#171717",
226
+ primary_foreground: "#ffffff",
227
+ link: "#1d4ed8",
228
+ border: "#e5e5e5",
229
+ code_background: "#f5f5f5"
230
+ },
231
+ typography: {
232
+ heading_family: "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif",
233
+ body_family: "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif",
234
+ heading_weight: 700,
235
+ body_weight: 400,
236
+ // Responsive defaults: comfortable mobile sizes, larger from `md` up.
237
+ // This replaces the hardcoded mobile media query the base CSS used to need.
238
+ h1_size: { base: "1.9rem", md: "2.25rem" },
239
+ h2_size: { base: "1.5rem", md: "1.75rem" },
240
+ h3_size: "1.25rem",
241
+ h4_size: "1.125rem",
242
+ body_size: "1rem",
243
+ body_line_height: "1.7",
244
+ small_size: "0.875rem",
245
+ heading_letter_spacing: "-0.02em",
246
+ caption_align: "center"
247
+ },
248
+ spacing: {
249
+ content_max_width: "42rem",
250
+ block_gap: "1.25rem",
251
+ section_gap: "3rem"
252
+ },
253
+ radius: "8px"
254
+ };
255
+ function sanitizeBlogTheme(raw) {
256
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
257
+ const parsed = blogThemeOverrideSchema.safeParse(raw);
258
+ return parsed.success ? parsed.data : {};
259
+ }
260
+ function mergeGroup(base, over) {
261
+ if (!over) return base;
262
+ const out = { ...base };
263
+ for (const [key, value] of Object.entries(over)) {
264
+ if (value !== void 0) out[key] = value;
265
+ }
266
+ return out;
267
+ }
268
+ function resolveBlogTheme(...layers) {
269
+ let resolved = {
270
+ colors: { ...BASE_BLOG_THEME.colors },
271
+ colors_dark: BASE_BLOG_THEME.colors_dark ? { ...BASE_BLOG_THEME.colors_dark } : void 0,
272
+ typography: { ...BASE_BLOG_THEME.typography },
273
+ spacing: { ...BASE_BLOG_THEME.spacing },
274
+ radius: BASE_BLOG_THEME.radius
275
+ };
276
+ for (const layer of layers) {
277
+ if (!layer) continue;
278
+ resolved = {
279
+ colors: mergeGroup(resolved.colors, layer.colors),
280
+ colors_dark: layer.colors_dark || resolved.colors_dark ? mergeGroup(resolved.colors_dark ?? {}, layer.colors_dark) : void 0,
281
+ typography: mergeGroup(resolved.typography, layer.typography),
282
+ spacing: mergeGroup(resolved.spacing, layer.spacing),
283
+ radius: layer.radius ?? resolved.radius
284
+ };
285
+ }
286
+ return resolved;
287
+ }
288
+ function kebab(key) {
289
+ return key.split("_").join("-");
290
+ }
291
+ function emitResponsiveVar(vars, name, value) {
292
+ const map = toBlogBreakpointMap(value);
293
+ vars[name] = String(map.base);
294
+ for (const bp of BLOG_BREAKPOINTS) {
295
+ const bpValue = map[bp];
296
+ if (bpValue !== void 0) vars[`${name}-${bp}`] = String(bpValue);
297
+ }
298
+ }
299
+ function blogThemeToCssVars(theme) {
300
+ const vars = {};
301
+ const dark = theme.colors_dark;
302
+ const hasDark = !!dark && Object.values(dark).some((v) => v !== void 0);
303
+ const colorKeys = /* @__PURE__ */ new Set([...Object.keys(theme.colors), ...dark ? Object.keys(dark) : []]);
304
+ for (const key of colorKeys) {
305
+ const light = theme.colors[key];
306
+ const darkValue = hasDark ? dark?.[key] : void 0;
307
+ const lightValue = light ?? (darkValue !== void 0 ? theme.colors.text : void 0);
308
+ if (lightValue === void 0) continue;
309
+ vars[`--ara-blog-color-${kebab(key)}`] = darkValue ? `light-dark(${lightValue}, ${darkValue})` : lightValue;
310
+ }
311
+ if (hasDark) vars.colorScheme = "light dark";
312
+ for (const [key, value] of Object.entries(theme.typography)) {
313
+ emitResponsiveVar(vars, `--ara-blog-type-${kebab(key)}`, value);
314
+ }
315
+ for (const [key, value] of Object.entries(theme.spacing)) {
316
+ emitResponsiveVar(vars, `--ara-blog-space-${kebab(key)}`, value);
317
+ }
318
+ emitResponsiveVar(vars, "--ara-blog-radius", theme.radius);
319
+ return vars;
320
+ }
321
+
322
+ // ../tracking-core/src/resources/blog/schema.ts
323
+ var schemaVersionSchema = import_zod2.z.literal(1);
324
+ var blogHeroImageSchema = import_zod2.z.object({
325
+ url: import_zod2.z.string(),
326
+ alt: import_zod2.z.string().catch(""),
327
+ width: import_zod2.z.number().int(),
328
+ height: import_zod2.z.number().int()
329
+ });
330
+ var blogManifestEntrySchema = import_zod2.z.object({
331
+ slug: import_zod2.z.string(),
332
+ title: import_zod2.z.string(),
333
+ description: import_zod2.z.string().catch(""),
334
+ content_hash: import_zod2.z.string(),
335
+ content_key: import_zod2.z.string(),
336
+ published_at: import_zod2.z.string(),
337
+ updated_at: import_zod2.z.string(),
338
+ // Estimated read time in whole minutes (computed at publish from the post
339
+ // body). 0 when unknown; index cards render "N min read".
340
+ reading_minutes: import_zod2.z.number().int().catch(0),
341
+ // A malformed tags array / hero_image degrades gracefully instead of
342
+ // dropping the whole entry.
343
+ tags: import_zod2.z.array(import_zod2.z.string()).catch([]),
344
+ hero_image: blogHeroImageSchema.nullable().catch(null)
345
+ });
346
+ var blogManifestWireSchema = import_zod2.z.object({
347
+ schema_version: schemaVersionSchema,
348
+ namespace: import_zod2.z.string(),
349
+ generated_at: import_zod2.z.string(),
350
+ posts: import_zod2.z.array(blogManifestEntrySchema).catch([])
351
+ });
352
+ var blogManifestSchema = blogManifestWireSchema.extend({
353
+ // A single malformed entry is dropped; the rest of the manifest survives.
354
+ posts: import_zod2.z.array(blogManifestEntrySchema.nullable().catch(null)).transform((entries) => entries.filter((e) => e !== null)).catch([])
355
+ });
356
+ var blogImageSchema = import_zod2.z.object({
357
+ id: import_zod2.z.string(),
358
+ url: import_zod2.z.string(),
359
+ alt: import_zod2.z.string().catch(""),
360
+ caption: import_zod2.z.string().nullable().catch(null),
361
+ role: import_zod2.z.enum(["hero", "inline"]).catch("inline"),
362
+ width: import_zod2.z.number().int(),
363
+ height: import_zod2.z.number().int(),
364
+ mime: import_zod2.z.string().catch("")
365
+ });
366
+ var blogSeoSchema = import_zod2.z.object({
367
+ meta_title: import_zod2.z.string().catch(""),
368
+ meta_description: import_zod2.z.string().catch(""),
369
+ canonical_path: import_zod2.z.string().catch(""),
370
+ og_image: import_zod2.z.string().nullable().catch(null)
371
+ });
372
+ var blogPostContentWireSchema = import_zod2.z.object({
373
+ schema_version: schemaVersionSchema,
374
+ slug: import_zod2.z.string(),
375
+ title: import_zod2.z.string(),
376
+ description: import_zod2.z.string().catch(""),
377
+ mdx: import_zod2.z.string(),
378
+ images: import_zod2.z.array(blogImageSchema).catch([]),
379
+ // Already schema-validated at publish time, but re-sanitized on read (and
380
+ // again before rendering) — the override schema itself drops invalid values.
381
+ // Nullish: the backend serializes an unset override as an explicit `null`
382
+ // (Pydantic `model_dump()`); it folds into `undefined` for TS consumers.
383
+ theme_overrides: nullishField(blogThemeOverrideSchema).catch(void 0),
384
+ seo: blogSeoSchema.catch({
385
+ meta_title: "",
386
+ meta_description: "",
387
+ canonical_path: "",
388
+ og_image: null
389
+ }),
390
+ jsonld: import_zod2.z.record(import_zod2.z.unknown()).catch({}),
391
+ published_at: import_zod2.z.string(),
392
+ // The applied_at of the version this content was published from; `null`
393
+ // when the backend has no separate modified date. Nullish on the wire
394
+ // (Pydantic `model_dump()` emits explicit `null`s), folded to `null` for a
395
+ // single TS sentinel; a malformed value degrades to `null` instead of
396
+ // dropping the post.
397
+ updated_at: import_zod2.z.string().nullish().transform((v) => v ?? null).catch(null),
398
+ version_number: import_zod2.z.number().int().catch(0),
399
+ content_hash: import_zod2.z.string()
400
+ });
401
+ var blogPostContentSchema = blogPostContentWireSchema.extend({
402
+ // A single malformed image record is dropped; the post survives.
403
+ images: import_zod2.z.array(blogImageSchema.nullable().catch(null)).transform((images) => images.filter((i) => i !== null)).catch([])
404
+ });
405
+ function parseBlogManifest(raw) {
406
+ const parsed = blogManifestSchema.safeParse(raw);
407
+ return parsed.success ? parsed.data : null;
408
+ }
409
+ function parseBlogPostContent(raw) {
410
+ const parsed = blogPostContentSchema.safeParse(raw);
411
+ return parsed.success ? parsed.data : null;
412
+ }
413
+
414
+ // ../tracking-core/src/resources/blog/client.ts
415
+ function createBlogClient(config) {
416
+ const base = config.cdnBaseUrl.replace(/\/+$/, "");
417
+ const doFetch = (...args) => (config.fetchImpl ?? fetch)(...args);
418
+ async function fetchJson(url, init) {
419
+ try {
420
+ const response = await doFetch(url, init);
421
+ if (!response.ok) return null;
422
+ return await response.json();
423
+ } catch {
424
+ return null;
425
+ }
426
+ }
427
+ async function getManifest() {
428
+ const init = {
429
+ next: { revalidate: config.manifestRevalidateSeconds ?? 300 }
430
+ };
431
+ const raw = await fetchJson(`${base}/blogs/v1/${config.namespace}/manifest.json`, init);
432
+ return raw === null ? null : parseBlogManifest(raw);
433
+ }
434
+ async function listPosts() {
435
+ const manifest = await getManifest();
436
+ if (!manifest) return [];
437
+ return [...manifest.posts].sort((a, b) => b.published_at.localeCompare(a.published_at));
438
+ }
439
+ async function getPost(slug) {
440
+ const manifest = await getManifest();
441
+ const entry = manifest?.posts.find((post) => post.slug === slug);
442
+ if (!entry) return null;
443
+ const init = { cache: "force-cache" };
444
+ const raw = await fetchJson(`${base}/${entry.content_key.replace(/^\/+/, "")}`, init);
445
+ return raw === null ? null : parseBlogPostContent(raw);
446
+ }
447
+ async function getStaticParams() {
448
+ const posts = await listPosts();
449
+ return posts.map((post) => ({ slug: post.slug }));
450
+ }
451
+ return { getManifest, listPosts, getPost, getStaticParams };
452
+ }
453
+
454
+ // ../tracking-core/src/resources/blog/presets.ts
455
+ var BLOG_THEME_PRESETS = {
456
+ /** Serif display headings over a humanist sans body — long-form magazine feel. */
457
+ editorial: {
458
+ typography: {
459
+ heading_family: "'Source Serif 4', 'Iowan Old Style', 'Palatino Linotype', Georgia, serif",
460
+ body_family: "'Source Sans 3', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif",
461
+ heading_weight: 600,
462
+ body_weight: 400,
463
+ h1_size: { base: "2rem", md: "2.5rem" },
464
+ h2_size: { base: "1.5rem", md: "1.875rem" },
465
+ h3_size: "1.3rem",
466
+ h4_size: "1.125rem",
467
+ body_size: "1.0625rem",
468
+ body_line_height: "1.75",
469
+ heading_letter_spacing: "-0.015em"
470
+ },
471
+ spacing: {
472
+ block_gap: "1.35rem",
473
+ section_gap: "3.25rem"
474
+ },
475
+ radius: "6px"
476
+ },
477
+ /** Geometric sans pairing (Inter-first) — tight, contemporary product voice. */
478
+ modern: {
479
+ typography: {
480
+ heading_family: "'Inter', 'SF Pro Display', 'Segoe UI', Roboto, sans-serif",
481
+ body_family: "'Inter', 'SF Pro Text', 'Segoe UI', Roboto, sans-serif",
482
+ heading_weight: 700,
483
+ body_weight: 400,
484
+ h1_size: { base: "2.1rem", md: "2.75rem" },
485
+ h2_size: { base: "1.5rem", md: "1.75rem" },
486
+ h3_size: "1.25rem",
487
+ h4_size: "1.125rem",
488
+ body_size: "1rem",
489
+ body_line_height: "1.7",
490
+ heading_letter_spacing: "-0.03em"
491
+ },
492
+ spacing: {
493
+ block_gap: "1.25rem",
494
+ section_gap: "3rem"
495
+ },
496
+ radius: "12px"
497
+ },
498
+ /** Rounded-friendly pairing with a softer scale — approachable, human tone. */
499
+ warm: {
500
+ typography: {
501
+ heading_family: "'Nunito', 'Avenir Next', 'Trebuchet MS', Verdana, sans-serif",
502
+ body_family: "'Nunito Sans', 'Avenir Next', 'Trebuchet MS', Verdana, sans-serif",
503
+ heading_weight: 700,
504
+ body_weight: 400,
505
+ h1_size: { base: "1.9rem", md: "2.35rem" },
506
+ h2_size: { base: "1.45rem", md: "1.7rem" },
507
+ h3_size: "1.25rem",
508
+ h4_size: "1.125rem",
509
+ body_size: "1.0625rem",
510
+ body_line_height: "1.75",
511
+ heading_letter_spacing: "-0.01em"
512
+ },
513
+ spacing: {
514
+ block_gap: "1.35rem",
515
+ section_gap: "3.25rem"
516
+ },
517
+ radius: "16px"
518
+ },
519
+ /** Workmanlike sans with a prominent code surface — docs/engineering feel. */
520
+ technical: {
521
+ colors: {
522
+ // The one preset that ships a color: a cool code surface is part of the
523
+ // "technical" identity. Everything else stays on the site palette.
524
+ code_background: "#eef1f5"
525
+ },
526
+ colors_dark: {
527
+ code_background: "#1b1f27"
528
+ },
529
+ typography: {
530
+ heading_family: "'IBM Plex Sans', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif",
531
+ body_family: "'IBM Plex Sans', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif",
532
+ heading_weight: 600,
533
+ body_weight: 400,
534
+ h1_size: { base: "1.85rem", md: "2.25rem" },
535
+ h2_size: { base: "1.45rem", md: "1.65rem" },
536
+ h3_size: "1.2rem",
537
+ h4_size: "1.0625rem",
538
+ body_size: "1rem",
539
+ body_line_height: "1.65",
540
+ heading_letter_spacing: "-0.01em"
541
+ },
542
+ spacing: {
543
+ block_gap: "1.15rem",
544
+ section_gap: "2.75rem"
545
+ },
546
+ radius: "6px"
547
+ }
548
+ };
549
+ function prependFamily(front, stack) {
550
+ if (!front) return stack;
551
+ return stack ? `${front}, ${stack}` : front;
552
+ }
553
+ function createBlogFontTheme(preset, fonts) {
554
+ const heading_family = prependFamily(fonts.heading, preset.typography?.heading_family);
555
+ const body_family = prependFamily(fonts.body, preset.typography?.body_family);
556
+ return {
557
+ ...preset,
558
+ typography: {
559
+ ...preset.typography,
560
+ ...heading_family !== void 0 ? { heading_family } : {},
561
+ ...body_family !== void 0 ? { body_family } : {}
562
+ }
563
+ };
564
+ }
565
+
566
+ // ../tracking-core/src/resources/blog/view/class-names.ts
567
+ function joinClassNames(base, extra) {
568
+ return extra ? `${base} ${extra}` : base;
569
+ }
570
+
571
+ // ../tracking-core/src/resources/blog/seo.ts
572
+ function joinUrl(base, ...paths) {
573
+ let out = base.replace(/\/+$/, "");
574
+ for (const path of paths) {
575
+ const segment = path?.replace(/^\/+/, "").replace(/\/+$/, "");
576
+ if (segment) out += `/${segment}`;
577
+ }
578
+ return out;
579
+ }
580
+ function canonicalUrl(post, opts) {
581
+ return joinUrl(opts.siteUrl, opts.basePath ?? "/blog", post.slug);
582
+ }
583
+ function ogImages(post) {
584
+ if (post.seo.og_image) return [{ url: post.seo.og_image }];
585
+ const hero = post.images.find((image) => image.role === "hero");
586
+ if (!hero) return void 0;
587
+ return [{ url: hero.url, width: hero.width, height: hero.height, alt: hero.alt }];
588
+ }
589
+ function buildBlogPostMetadata(post, opts) {
590
+ const url = canonicalUrl(post, opts);
591
+ const title = post.seo.meta_title || post.title;
592
+ const description = post.seo.meta_description || post.description;
593
+ const images = ogImages(post);
594
+ return {
595
+ title,
596
+ description,
597
+ alternates: { canonical: url },
598
+ openGraph: {
599
+ title,
600
+ description,
601
+ type: "article",
602
+ url,
603
+ ...images ? { images } : {},
604
+ ...post.published_at ? { publishedTime: post.published_at } : {}
605
+ }
606
+ };
607
+ }
608
+ function buildBlogPostingJsonLd(post, opts) {
609
+ const url = canonicalUrl(post, opts);
610
+ const images = post.images.map((image) => image.url);
611
+ const doc = {
612
+ headline: post.title,
613
+ description: post.seo.meta_description || post.description,
614
+ datePublished: post.published_at,
615
+ dateModified: post.updated_at ?? post.published_at,
616
+ ...images.length > 0 ? { image: images } : {},
617
+ ...opts.publisherName ? { publisher: { "@type": "Organization", name: opts.publisherName } } : {}
618
+ };
619
+ const backend = post.jsonld;
620
+ const merged = backend && typeof backend === "object" && Object.keys(backend).length > 0 ? { ...doc, ...backend } : doc;
621
+ return {
622
+ ...merged,
623
+ "@context": "https://schema.org",
624
+ "@type": "BlogPosting",
625
+ url,
626
+ mainEntityOfPage: { "@type": "WebPage", "@id": url }
627
+ };
628
+ }
629
+ var FAQ_ITEM_RE = /<FaqItem\s+question=(?:"([^"]*)"|'([^']*)')\s*>([\s\S]*?)<\/FaqItem>/g;
630
+ var CODE_FENCE_RE = /^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^ {0,3}\1`* *$|(?![\s\S]))/gm;
631
+ var ANSWER_TAG_RE = /<[^>]*>/g;
632
+ function extractFaqPairs(mdx) {
633
+ const withoutCode = mdx.replaceAll(CODE_FENCE_RE, "");
634
+ const pairs = [];
635
+ for (const match of withoutCode.matchAll(FAQ_ITEM_RE)) {
636
+ const question = (match[1] ?? match[2] ?? "").trim();
637
+ const answer = match[3].replaceAll(ANSWER_TAG_RE, " ").replaceAll(/\s+/g, " ").trim();
638
+ if (question && answer) pairs.push({ question, answer });
639
+ }
640
+ return pairs;
641
+ }
642
+ function buildFaqJsonLd(pairs) {
643
+ if (pairs.length === 0) return null;
644
+ return {
645
+ "@context": "https://schema.org",
646
+ "@type": "FAQPage",
647
+ mainEntity: pairs.map((pair) => ({
648
+ "@type": "Question",
649
+ name: pair.question,
650
+ acceptedAnswer: { "@type": "Answer", text: pair.answer }
651
+ }))
652
+ };
653
+ }
654
+ var JSONLD_ESCAPES = {
655
+ "<": "\\u003c",
656
+ ">": "\\u003e",
657
+ "&": "\\u0026",
658
+ "\u2028": "\\u2028",
659
+ "\u2029": "\\u2029"
660
+ };
661
+ function serializeJsonLd(data) {
662
+ return JSON.stringify(data).replaceAll(/[<>&\u2028\u2029]/g, (ch) => JSONLD_ESCAPES[ch] ?? ch);
663
+ }
664
+ function buildBlogSitemapEntries(posts, opts) {
665
+ return posts.map((post) => ({
666
+ url: joinUrl(opts.siteUrl, opts.basePath ?? "/blog", post.slug),
667
+ lastModified: post.updated_at
668
+ }));
669
+ }
670
+
671
+ // ../tracking-core/src/resources/blog/toc.ts
672
+ var import_github_slugger = __toESM(require("github-slugger"));
673
+ var import_remark_gfm = __toESM(require("remark-gfm"));
674
+ var import_remark_mdx = __toESM(require("remark-mdx"));
675
+ var import_remark_parse = __toESM(require("remark-parse"));
676
+ var import_unified = require("unified");
677
+ var TEXT_LEAF_TYPES = /* @__PURE__ */ new Set(["text", "inlineCode"]);
678
+ function headingText(node) {
679
+ let text = "";
680
+ const stack = [];
681
+ const roots = node.children ?? [];
682
+ for (let i = roots.length - 1; i >= 0; i--) {
683
+ stack.push(roots[i]);
684
+ }
685
+ while (stack.length > 0) {
686
+ const current = stack.pop();
687
+ if (!current) break;
688
+ if (TEXT_LEAF_TYPES.has(current.type) && typeof current.value === "string") {
689
+ text += current.value;
690
+ }
691
+ if (Array.isArray(current.children)) {
692
+ for (let i = current.children.length - 1; i >= 0; i--) {
693
+ stack.push(current.children[i]);
694
+ }
695
+ }
696
+ }
697
+ return text;
698
+ }
699
+ function extractBlogHeadings(mdx) {
700
+ let tree;
701
+ try {
702
+ tree = (0, import_unified.unified)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_mdx.default).parse(mdx);
703
+ } catch {
704
+ return [];
705
+ }
706
+ const slugger = new import_github_slugger.default();
707
+ const headings = [];
708
+ const stack = [tree];
709
+ while (stack.length > 0) {
710
+ const node = stack.pop();
711
+ if (!node) break;
712
+ if (node.type === "heading" && typeof node.depth === "number") {
713
+ const text = headingText(node);
714
+ const id = slugger.slug(text);
715
+ if (node.depth === 2 || node.depth === 3) {
716
+ headings.push({ depth: node.depth, text, id });
717
+ }
718
+ }
719
+ if (Array.isArray(node.children)) {
720
+ for (let i = node.children.length - 1; i >= 0; i--) {
721
+ stack.push(node.children[i]);
722
+ }
723
+ }
724
+ }
725
+ return headings;
726
+ }
727
+ var WORDS_PER_MINUTE = 220;
728
+ var MDX_SYNTAX_RE = /<[^>]*>|```[^`]*```|`|^[#>\-*+\s]+|\]\([^)]*\)|[[\]!]/gm;
729
+ function estimateReadingTime(mdx) {
730
+ const words = mdx.replaceAll(MDX_SYNTAX_RE, " ").split(/\s+/).filter((w) => w.length > 0).length;
731
+ return Math.max(1, Math.round(words / WORDS_PER_MINUTE));
732
+ }
733
+
734
+ // ../tracking-core/src/resources/blog/validate-mdx.ts
735
+ var import_remark_gfm2 = __toESM(require("remark-gfm"));
736
+ var import_remark_mdx2 = __toESM(require("remark-mdx"));
737
+ var import_remark_parse2 = __toESM(require("remark-parse"));
738
+ var import_unified2 = require("unified");
739
+ var BLOG_MDX_MAX_SIZE = 5e5;
740
+ var BLOG_MDX_MAX_DEPTH = 64;
741
+ var BLOG_MDX_MAX_NODES = 1e5;
742
+ var MAX_VIOLATIONS = 50;
743
+ var BLOG_MDX_COMPONENT_GRAMMAR = [
744
+ {
745
+ name: "Callout",
746
+ attrs: [],
747
+ urlAttrs: [],
748
+ selfClosing: false,
749
+ cssClass: "aranova-blog-callout",
750
+ promptUsage: "<Callout>...</Callout>",
751
+ promptDescription: "a highlighted tip/note block; no attributes."
752
+ },
753
+ {
754
+ name: "Cta",
755
+ attrs: ["href", "label"],
756
+ urlAttrs: ["href"],
757
+ selfClosing: true,
758
+ cssClass: "aranova-blog-cta",
759
+ promptUsage: '<Cta href="..." label="..." />',
760
+ promptDescription: "a call-to-action button (self-closing)."
761
+ },
762
+ {
763
+ name: "BlogImage",
764
+ attrs: ["id"],
765
+ urlAttrs: [],
766
+ selfClosing: true,
767
+ promptUsage: '<BlogImage id="..." />',
768
+ promptDescription: "a generated image placeholder (self-closing). The `id` references an image brief id from the plan; the renderer resolves it."
769
+ },
770
+ {
771
+ name: "FaqItem",
772
+ attrs: ["question"],
773
+ urlAttrs: [],
774
+ selfClosing: false,
775
+ cssClass: "aranova-blog-faq-item",
776
+ promptUsage: '<FaqItem question="...">...</FaqItem>',
777
+ promptDescription: "one FAQ entry: the required `question` attribute carries the question, the children are the prose answer. Group consecutive `<FaqItem>`s under an FAQ heading near the end of the post when the topic suits a Q&A section."
778
+ },
779
+ {
780
+ name: "KeyTakeaways",
781
+ attrs: [],
782
+ urlAttrs: [],
783
+ selfClosing: false,
784
+ cssClass: "aranova-blog-key-takeaways",
785
+ promptUsage: "<KeyTakeaways>...</KeyTakeaways>",
786
+ promptDescription: "a TL;DR box containing a standard markdown bullet list of 3-5 takeaways; place it right after the H1 (and hero image)."
787
+ }
788
+ ];
789
+ var BLOG_MDX_ALLOWED_COMPONENTS = BLOG_MDX_COMPONENT_GRAMMAR.map((entry) => entry.name);
790
+ var ALLOWED_NODE_TYPES = /* @__PURE__ */ new Set([
791
+ "root",
792
+ "heading",
793
+ "paragraph",
794
+ "text",
795
+ "emphasis",
796
+ "strong",
797
+ "delete",
798
+ "link",
799
+ "image",
800
+ "linkReference",
801
+ "imageReference",
802
+ "definition",
803
+ "list",
804
+ "listItem",
805
+ "blockquote",
806
+ "code",
807
+ "inlineCode",
808
+ "table",
809
+ "tableRow",
810
+ "tableCell",
811
+ "thematicBreak",
812
+ "break"
813
+ ]);
814
+ var FORBIDDEN_NODE_TYPES = {
815
+ html: { rule: "raw-html", message: "Raw HTML is not allowed" },
816
+ mdxjsEsm: { rule: "esm", message: "import/export statements are not allowed" },
817
+ mdxFlowExpression: { rule: "expression", message: "JSX expressions ({\u2026}) are not allowed" },
818
+ mdxTextExpression: { rule: "expression", message: "JSX expressions ({\u2026}) are not allowed" }
819
+ };
820
+ var COMPONENT_SPECS = Object.fromEntries(
821
+ BLOG_MDX_COMPONENT_GRAMMAR.map((entry) => [
822
+ entry.name,
823
+ { attrs: entry.attrs, urlAttrs: entry.urlAttrs, allowChildren: !entry.selfClosing }
824
+ ])
825
+ );
826
+ var URL_SCHEME_RE = /^([A-Za-z][A-Za-z0-9+.-]*):/;
827
+ var ALLOWED_URL_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto"]);
828
+ function forbiddenUrlScheme(rawUrl) {
829
+ const cleaned = rawUrl.replaceAll(/[\u0000-\u0020\u007F]/g, "");
830
+ const match = URL_SCHEME_RE.exec(cleaned);
831
+ if (!match) return null;
832
+ const scheme = match[1].toLowerCase();
833
+ return ALLOWED_URL_SCHEMES.has(scheme) ? null : scheme;
834
+ }
835
+ function nodeLine(node) {
836
+ return node.position?.start?.line;
837
+ }
838
+ function checkComponent(node, violations) {
839
+ const line = nodeLine(node);
840
+ const name = node.name;
841
+ if (!name) {
842
+ violations.push({
843
+ rule: "unknown-component",
844
+ message: "JSX fragments (<>\u2026</>) are not allowed",
845
+ line
846
+ });
847
+ return;
848
+ }
849
+ const spec = COMPONENT_SPECS[name];
850
+ if (!spec) {
851
+ violations.push({
852
+ rule: "unknown-component",
853
+ message: `<${name}> is not an allowed element (allowed: ${BLOG_MDX_ALLOWED_COMPONENTS.join(", ")})`,
854
+ line
855
+ });
856
+ return;
857
+ }
858
+ const seen = /* @__PURE__ */ new Set();
859
+ for (const attr of node.attributes ?? []) {
860
+ if (attr.type !== "mdxJsxAttribute" || typeof attr.name !== "string") {
861
+ violations.push({
862
+ rule: "component-attribute",
863
+ message: `<${name}> has a non-literal attribute`,
864
+ line
865
+ });
866
+ continue;
867
+ }
868
+ if (!spec.attrs.includes(attr.name)) {
869
+ violations.push({
870
+ rule: "component-attribute",
871
+ message: `<${name}> does not accept attribute "${attr.name}"`,
872
+ line
873
+ });
874
+ continue;
875
+ }
876
+ if (typeof attr.value !== "string") {
877
+ violations.push({
878
+ rule: "component-attribute",
879
+ message: `<${name} ${attr.name}> must be a quoted string literal`,
880
+ line
881
+ });
882
+ continue;
883
+ }
884
+ seen.add(attr.name);
885
+ if (spec.urlAttrs.includes(attr.name)) {
886
+ const scheme = forbiddenUrlScheme(attr.value);
887
+ if (scheme !== null) {
888
+ violations.push({
889
+ rule: "url-scheme",
890
+ message: `<${name} ${attr.name}> uses forbidden URL scheme "${scheme}:"`,
891
+ line
892
+ });
893
+ }
894
+ }
895
+ }
896
+ for (const required of spec.attrs) {
897
+ if (!seen.has(required)) {
898
+ violations.push({
899
+ rule: "component-attribute",
900
+ message: `<${name}> requires attribute "${required}"`,
901
+ line
902
+ });
903
+ }
904
+ }
905
+ if (!spec.allowChildren && (node.children?.length ?? 0) > 0) {
906
+ violations.push({
907
+ rule: "component-attribute",
908
+ message: `<${name}> must be self-closing (no children)`,
909
+ line
910
+ });
911
+ }
912
+ }
913
+ function validateBlogMdx(mdx, options) {
914
+ const violations = [];
915
+ if (typeof mdx !== "string" || mdx.length > BLOG_MDX_MAX_SIZE) {
916
+ return {
917
+ ok: false,
918
+ violations: [
919
+ {
920
+ rule: "size-limit",
921
+ message: `Document exceeds the ${BLOG_MDX_MAX_SIZE}-character limit`
922
+ }
923
+ ]
924
+ };
925
+ }
926
+ let tree;
927
+ try {
928
+ tree = (0, import_unified2.unified)().use(import_remark_parse2.default).use(import_remark_gfm2.default).use(import_remark_mdx2.default).parse(mdx);
929
+ } catch (error) {
930
+ const line = error.line;
931
+ return {
932
+ ok: false,
933
+ violations: [
934
+ {
935
+ rule: "parse-error",
936
+ message: "Document is not valid MDX",
937
+ ...typeof line === "number" ? { line } : {}
938
+ }
939
+ ]
940
+ };
941
+ }
942
+ if (options?.requireLeadingH1) {
943
+ const firstChild = tree.children?.[0];
944
+ if (!firstChild || firstChild.type !== "heading" || firstChild.depth !== 1) {
945
+ violations.push({
946
+ rule: "leading-h1",
947
+ message: "Document must start with the H1 title (`# ...`) \u2014 no frontmatter or metadata block before it",
948
+ line: nodeLine(firstChild ?? tree)
949
+ });
950
+ }
951
+ }
952
+ let nodeCount = 0;
953
+ const stack = [{ node: tree, depth: 0 }];
954
+ while (stack.length > 0 && violations.length < MAX_VIOLATIONS) {
955
+ const item = stack.pop();
956
+ if (!item) break;
957
+ const { node, depth } = item;
958
+ nodeCount += 1;
959
+ if (nodeCount > BLOG_MDX_MAX_NODES) {
960
+ violations.push({ rule: "size-limit", message: "Document has too many nodes" });
961
+ break;
962
+ }
963
+ if (depth > BLOG_MDX_MAX_DEPTH) {
964
+ violations.push({ rule: "depth-limit", message: "Document nests too deeply" });
965
+ break;
966
+ }
967
+ const forbidden = FORBIDDEN_NODE_TYPES[node.type];
968
+ if (forbidden) {
969
+ violations.push({ ...forbidden, line: nodeLine(node) });
970
+ continue;
971
+ }
972
+ if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
973
+ checkComponent(node, violations);
974
+ } else if (!ALLOWED_NODE_TYPES.has(node.type)) {
975
+ violations.push({
976
+ rule: "disallowed-node",
977
+ message: `Markdown construct "${node.type}" is not allowed`,
978
+ line: nodeLine(node)
979
+ });
980
+ continue;
981
+ }
982
+ if ((node.type === "link" || node.type === "image" || node.type === "definition") && typeof node.url === "string") {
983
+ const scheme = forbiddenUrlScheme(node.url);
984
+ if (scheme !== null) {
985
+ violations.push({
986
+ rule: "url-scheme",
987
+ message: `${node.type} uses forbidden URL scheme "${scheme}:"`,
988
+ line: nodeLine(node)
989
+ });
990
+ }
991
+ }
992
+ if (Array.isArray(node.children)) {
993
+ for (const child of node.children) {
994
+ stack.push({ node: child, depth: depth + 1 });
995
+ }
996
+ }
997
+ }
998
+ return violations.length === 0 ? { ok: true } : { ok: false, violations };
999
+ }
1000
+ // Annotate the CommonJS export names for ESM import in node:
1001
+ 0 && (module.exports = {
1002
+ BASE_BLOG_THEME,
1003
+ BLOG_BREAKPOINTS,
1004
+ BLOG_BREAKPOINT_MIN_WIDTHS_PX,
1005
+ BLOG_MDX_ALLOWED_COMPONENTS,
1006
+ BLOG_MDX_COMPONENT_GRAMMAR,
1007
+ BLOG_MDX_MAX_DEPTH,
1008
+ BLOG_MDX_MAX_NODES,
1009
+ BLOG_MDX_MAX_SIZE,
1010
+ BLOG_THEME_PRESETS,
1011
+ blogHeroImageSchema,
1012
+ blogImageSchema,
1013
+ blogManifestEntrySchema,
1014
+ blogManifestSchema,
1015
+ blogManifestWireSchema,
1016
+ blogPostContentSchema,
1017
+ blogPostContentWireSchema,
1018
+ blogSeoSchema,
1019
+ blogThemeColorsSchema,
1020
+ blogThemeOverrideSchema,
1021
+ blogThemeSchema,
1022
+ blogThemeSpacingSchema,
1023
+ blogThemeToCssVars,
1024
+ blogThemeTypographySchema,
1025
+ buildBlogPostMetadata,
1026
+ buildBlogPostingJsonLd,
1027
+ buildBlogSitemapEntries,
1028
+ buildFaqJsonLd,
1029
+ createBlogClient,
1030
+ createBlogFontTheme,
1031
+ estimateReadingTime,
1032
+ extractBlogHeadings,
1033
+ extractFaqPairs,
1034
+ joinClassNames,
1035
+ nullishField,
1036
+ parseBlogManifest,
1037
+ parseBlogPostContent,
1038
+ resolveBlogTheme,
1039
+ responsive,
1040
+ sanitizeBlogTheme,
1041
+ serializeJsonLd,
1042
+ toBlogBreakpointMap,
1043
+ validateBlogMdx
1044
+ });
1045
+ //# sourceMappingURL=blog.js.map