@aranova/tracking-next 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -0
- package/dist/blog-client.d.mts +69 -0
- package/dist/blog-client.d.ts +69 -0
- package/dist/blog-client.js +193 -0
- package/dist/blog-client.js.map +1 -0
- package/dist/blog-client.mjs +172 -0
- package/dist/blog-client.mjs.map +1 -0
- package/dist/blog-server.d.mts +248 -0
- package/dist/blog-server.d.ts +248 -0
- package/dist/blog-server.js +2303 -0
- package/dist/blog-server.js.map +1 -0
- package/dist/blog-server.mjs +2219 -0
- package/dist/blog-server.mjs.map +1 -0
- package/dist/blog.d.mts +2842 -0
- package/dist/blog.d.ts +2842 -0
- package/dist/blog.js +1043 -0
- package/dist/blog.js.map +1 -0
- package/dist/blog.mjs +965 -0
- package/dist/blog.mjs.map +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/toc-C_OvH3ES.d.mts +23 -0
- package/dist/toc-C_OvH3ES.d.ts +23 -0
- package/package.json +37 -1
|
@@ -0,0 +1,2219 @@
|
|
|
1
|
+
// src/blog-server.ts
|
|
2
|
+
import "server-only";
|
|
3
|
+
|
|
4
|
+
// ../tracking-core/src/resources/blog/schema.ts
|
|
5
|
+
import { z as z2 } from "zod";
|
|
6
|
+
|
|
7
|
+
// ../tracking-core/src/resources/blog/theme.ts
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
var BLOG_BREAKPOINTS = ["sm", "md", "lg", "xl", "2xl", "3xl"];
|
|
10
|
+
var BLOG_BREAKPOINT_MIN_WIDTHS_PX = {
|
|
11
|
+
sm: 640,
|
|
12
|
+
md: 768,
|
|
13
|
+
lg: 1024,
|
|
14
|
+
xl: 1280,
|
|
15
|
+
"2xl": 1536,
|
|
16
|
+
"3xl": 1920
|
|
17
|
+
};
|
|
18
|
+
function toBlogBreakpointMap(value) {
|
|
19
|
+
return typeof value === "object" ? value : { base: value };
|
|
20
|
+
}
|
|
21
|
+
var HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
|
|
22
|
+
var FUNCTIONAL_COLOR = /^(rgb|rgba|hsl|hsla|oklch)\([0-9a-zA-Z ,.%/-]*\)$/;
|
|
23
|
+
var LENGTH = /^-?\d+(\.\d+)?(px|rem|em|%|ch|vw)$/;
|
|
24
|
+
var LINE_HEIGHT = /^-?\d+(\.\d+)?(px|rem|em|%|ch|vw)?$/;
|
|
25
|
+
var FONT_FAMILY_TOKEN = /^['"]?[a-zA-Z0-9 -]+['"]?$/;
|
|
26
|
+
var FONT_FAMILY_VAR_TOKEN = /^var\(--[a-zA-Z0-9-]+\)$/;
|
|
27
|
+
var colorValueSchema = z.string().refine((v) => HEX_COLOR.test(v) || FUNCTIONAL_COLOR.test(v), {
|
|
28
|
+
message: "must be a #hex or rgb()/rgba()/hsl()/hsla()/oklch() color"
|
|
29
|
+
});
|
|
30
|
+
var lengthValueSchema = z.string().regex(LENGTH, {
|
|
31
|
+
message: "must be a CSS length (px|rem|em|%|ch|vw)"
|
|
32
|
+
});
|
|
33
|
+
var lineHeightValueSchema = z.string().regex(LINE_HEIGHT, {
|
|
34
|
+
message: "must be a CSS length or unitless line-height"
|
|
35
|
+
});
|
|
36
|
+
var fontWeightSchema = z.number().int().min(100).max(1e3);
|
|
37
|
+
var alignValueSchema = z.enum(["left", "center", "right"]);
|
|
38
|
+
var fontFamilySchema = z.string().refine(
|
|
39
|
+
(v) => {
|
|
40
|
+
const tokens = v.split(",").map((t) => t.trim());
|
|
41
|
+
return tokens.length > 0 && tokens.every((t) => FONT_FAMILY_TOKEN.test(t) || FONT_FAMILY_VAR_TOKEN.test(t));
|
|
42
|
+
},
|
|
43
|
+
{ message: "must be a comma-separated list of font-family or var(--custom-property) tokens" }
|
|
44
|
+
);
|
|
45
|
+
function nullishField(schema) {
|
|
46
|
+
return schema.nullish().transform((v) => v ?? void 0);
|
|
47
|
+
}
|
|
48
|
+
function breakpointMapSchema(valueSchema) {
|
|
49
|
+
return z.object({
|
|
50
|
+
base: valueSchema,
|
|
51
|
+
sm: nullishField(valueSchema).catch(void 0),
|
|
52
|
+
md: nullishField(valueSchema).catch(void 0),
|
|
53
|
+
lg: nullishField(valueSchema).catch(void 0),
|
|
54
|
+
xl: nullishField(valueSchema).catch(void 0),
|
|
55
|
+
"2xl": nullishField(valueSchema).catch(void 0),
|
|
56
|
+
"3xl": nullishField(valueSchema).catch(void 0)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function responsive(valueSchema) {
|
|
60
|
+
return z.union([valueSchema, breakpointMapSchema(valueSchema)]);
|
|
61
|
+
}
|
|
62
|
+
var blogThemeColorsShape = {
|
|
63
|
+
background: colorValueSchema,
|
|
64
|
+
surface: colorValueSchema,
|
|
65
|
+
text: colorValueSchema,
|
|
66
|
+
text_muted: colorValueSchema,
|
|
67
|
+
primary: colorValueSchema,
|
|
68
|
+
primary_foreground: colorValueSchema,
|
|
69
|
+
link: colorValueSchema,
|
|
70
|
+
border: colorValueSchema,
|
|
71
|
+
code_background: colorValueSchema,
|
|
72
|
+
heading: colorValueSchema
|
|
73
|
+
};
|
|
74
|
+
var blogThemeColorsSchema = z.object({
|
|
75
|
+
...blogThemeColorsShape,
|
|
76
|
+
// The ONE optional color in a resolved theme: when unset, headings fall back
|
|
77
|
+
// to `text` in the stylesheet (`--_heading-color`), so `blogThemeToCssVars`
|
|
78
|
+
// only emits `--ara-blog-color-heading` when a theme actually sets it.
|
|
79
|
+
heading: nullishField(colorValueSchema)
|
|
80
|
+
});
|
|
81
|
+
var blogThemeTypographySchema = z.object({
|
|
82
|
+
heading_family: fontFamilySchema,
|
|
83
|
+
body_family: fontFamilySchema,
|
|
84
|
+
heading_weight: fontWeightSchema,
|
|
85
|
+
body_weight: fontWeightSchema,
|
|
86
|
+
h1_size: responsive(lengthValueSchema),
|
|
87
|
+
h2_size: responsive(lengthValueSchema),
|
|
88
|
+
h3_size: responsive(lengthValueSchema),
|
|
89
|
+
h4_size: responsive(lengthValueSchema),
|
|
90
|
+
body_size: responsive(lengthValueSchema),
|
|
91
|
+
body_line_height: responsive(lineHeightValueSchema),
|
|
92
|
+
small_size: responsive(lengthValueSchema),
|
|
93
|
+
// Scalar (non-responsive) length; the grammar allows negative em values.
|
|
94
|
+
// h1/h2 consume it directly, h3/h4 at half strength (calc in the base CSS).
|
|
95
|
+
heading_letter_spacing: lengthValueSchema,
|
|
96
|
+
// Figure caption text alignment (left/center/right). Site-owner override;
|
|
97
|
+
// default center in the base CSS if unset.
|
|
98
|
+
caption_align: alignValueSchema
|
|
99
|
+
});
|
|
100
|
+
var blogThemeSpacingSchema = z.object({
|
|
101
|
+
content_max_width: responsive(lengthValueSchema),
|
|
102
|
+
block_gap: responsive(lengthValueSchema),
|
|
103
|
+
section_gap: responsive(lengthValueSchema)
|
|
104
|
+
});
|
|
105
|
+
var blogThemeColorsPartialSchema = z.object(
|
|
106
|
+
Object.fromEntries(
|
|
107
|
+
Object.keys(blogThemeColorsShape).map((key) => [key, nullishField(colorValueSchema)])
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
var blogThemeSchema = z.object({
|
|
111
|
+
colors: blogThemeColorsSchema,
|
|
112
|
+
colors_dark: nullishField(blogThemeColorsPartialSchema),
|
|
113
|
+
typography: blogThemeTypographySchema,
|
|
114
|
+
spacing: blogThemeSpacingSchema,
|
|
115
|
+
radius: responsive(lengthValueSchema)
|
|
116
|
+
});
|
|
117
|
+
function dropInvalidFields(shape) {
|
|
118
|
+
const out = {};
|
|
119
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
120
|
+
out[key] = nullTolerant(schema).catch(void 0);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
function nullTolerant(schema) {
|
|
125
|
+
if (schema instanceof z.ZodUnion) {
|
|
126
|
+
const options = schema.options;
|
|
127
|
+
return z.union([...options, z.null()]).optional().transform((v) => v ?? void 0);
|
|
128
|
+
}
|
|
129
|
+
return nullishField(schema);
|
|
130
|
+
}
|
|
131
|
+
var blogThemeColorsOverrideSchema = z.object(dropInvalidFields(blogThemeColorsShape));
|
|
132
|
+
var blogThemeTypographyOverrideSchema = z.object(
|
|
133
|
+
dropInvalidFields(blogThemeTypographySchema.shape)
|
|
134
|
+
);
|
|
135
|
+
var blogThemeSpacingOverrideSchema = z.object(dropInvalidFields(blogThemeSpacingSchema.shape));
|
|
136
|
+
var blogThemeOverrideSchema = z.object(
|
|
137
|
+
dropInvalidFields({
|
|
138
|
+
colors: blogThemeColorsOverrideSchema,
|
|
139
|
+
colors_dark: blogThemeColorsOverrideSchema,
|
|
140
|
+
typography: blogThemeTypographyOverrideSchema,
|
|
141
|
+
spacing: blogThemeSpacingOverrideSchema,
|
|
142
|
+
radius: responsive(lengthValueSchema)
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
var BASE_BLOG_THEME = {
|
|
146
|
+
colors: {
|
|
147
|
+
background: "#ffffff",
|
|
148
|
+
surface: "#f5f5f5",
|
|
149
|
+
text: "#171717",
|
|
150
|
+
text_muted: "#6b6b6b",
|
|
151
|
+
primary: "#171717",
|
|
152
|
+
primary_foreground: "#ffffff",
|
|
153
|
+
link: "#1d4ed8",
|
|
154
|
+
border: "#e5e5e5",
|
|
155
|
+
code_background: "#f5f5f5"
|
|
156
|
+
},
|
|
157
|
+
typography: {
|
|
158
|
+
heading_family: "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif",
|
|
159
|
+
body_family: "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif",
|
|
160
|
+
heading_weight: 700,
|
|
161
|
+
body_weight: 400,
|
|
162
|
+
// Responsive defaults: comfortable mobile sizes, larger from `md` up.
|
|
163
|
+
// This replaces the hardcoded mobile media query the base CSS used to need.
|
|
164
|
+
h1_size: { base: "1.9rem", md: "2.25rem" },
|
|
165
|
+
h2_size: { base: "1.5rem", md: "1.75rem" },
|
|
166
|
+
h3_size: "1.25rem",
|
|
167
|
+
h4_size: "1.125rem",
|
|
168
|
+
body_size: "1rem",
|
|
169
|
+
body_line_height: "1.7",
|
|
170
|
+
small_size: "0.875rem",
|
|
171
|
+
heading_letter_spacing: "-0.02em",
|
|
172
|
+
caption_align: "center"
|
|
173
|
+
},
|
|
174
|
+
spacing: {
|
|
175
|
+
content_max_width: "42rem",
|
|
176
|
+
block_gap: "1.25rem",
|
|
177
|
+
section_gap: "3rem"
|
|
178
|
+
},
|
|
179
|
+
radius: "8px"
|
|
180
|
+
};
|
|
181
|
+
function sanitizeBlogTheme(raw) {
|
|
182
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
183
|
+
const parsed = blogThemeOverrideSchema.safeParse(raw);
|
|
184
|
+
return parsed.success ? parsed.data : {};
|
|
185
|
+
}
|
|
186
|
+
function mergeGroup(base, over) {
|
|
187
|
+
if (!over) return base;
|
|
188
|
+
const out = { ...base };
|
|
189
|
+
for (const [key, value] of Object.entries(over)) {
|
|
190
|
+
if (value !== void 0) out[key] = value;
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
function resolveBlogTheme(...layers) {
|
|
195
|
+
let resolved = {
|
|
196
|
+
colors: { ...BASE_BLOG_THEME.colors },
|
|
197
|
+
colors_dark: BASE_BLOG_THEME.colors_dark ? { ...BASE_BLOG_THEME.colors_dark } : void 0,
|
|
198
|
+
typography: { ...BASE_BLOG_THEME.typography },
|
|
199
|
+
spacing: { ...BASE_BLOG_THEME.spacing },
|
|
200
|
+
radius: BASE_BLOG_THEME.radius
|
|
201
|
+
};
|
|
202
|
+
for (const layer of layers) {
|
|
203
|
+
if (!layer) continue;
|
|
204
|
+
resolved = {
|
|
205
|
+
colors: mergeGroup(resolved.colors, layer.colors),
|
|
206
|
+
colors_dark: layer.colors_dark || resolved.colors_dark ? mergeGroup(resolved.colors_dark ?? {}, layer.colors_dark) : void 0,
|
|
207
|
+
typography: mergeGroup(resolved.typography, layer.typography),
|
|
208
|
+
spacing: mergeGroup(resolved.spacing, layer.spacing),
|
|
209
|
+
radius: layer.radius ?? resolved.radius
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return resolved;
|
|
213
|
+
}
|
|
214
|
+
function kebab(key) {
|
|
215
|
+
return key.split("_").join("-");
|
|
216
|
+
}
|
|
217
|
+
function emitResponsiveVar(vars, name, value) {
|
|
218
|
+
const map = toBlogBreakpointMap(value);
|
|
219
|
+
vars[name] = String(map.base);
|
|
220
|
+
for (const bp of BLOG_BREAKPOINTS) {
|
|
221
|
+
const bpValue = map[bp];
|
|
222
|
+
if (bpValue !== void 0) vars[`${name}-${bp}`] = String(bpValue);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function blogThemeToCssVars(theme) {
|
|
226
|
+
const vars = {};
|
|
227
|
+
const dark = theme.colors_dark;
|
|
228
|
+
const hasDark = !!dark && Object.values(dark).some((v) => v !== void 0);
|
|
229
|
+
const colorKeys = /* @__PURE__ */ new Set([...Object.keys(theme.colors), ...dark ? Object.keys(dark) : []]);
|
|
230
|
+
for (const key of colorKeys) {
|
|
231
|
+
const light = theme.colors[key];
|
|
232
|
+
const darkValue = hasDark ? dark?.[key] : void 0;
|
|
233
|
+
const lightValue = light ?? (darkValue !== void 0 ? theme.colors.text : void 0);
|
|
234
|
+
if (lightValue === void 0) continue;
|
|
235
|
+
vars[`--ara-blog-color-${kebab(key)}`] = darkValue ? `light-dark(${lightValue}, ${darkValue})` : lightValue;
|
|
236
|
+
}
|
|
237
|
+
if (hasDark) vars.colorScheme = "light dark";
|
|
238
|
+
for (const [key, value] of Object.entries(theme.typography)) {
|
|
239
|
+
emitResponsiveVar(vars, `--ara-blog-type-${kebab(key)}`, value);
|
|
240
|
+
}
|
|
241
|
+
for (const [key, value] of Object.entries(theme.spacing)) {
|
|
242
|
+
emitResponsiveVar(vars, `--ara-blog-space-${kebab(key)}`, value);
|
|
243
|
+
}
|
|
244
|
+
emitResponsiveVar(vars, "--ara-blog-radius", theme.radius);
|
|
245
|
+
return vars;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ../tracking-core/src/resources/blog/schema.ts
|
|
249
|
+
var schemaVersionSchema = z2.literal(1);
|
|
250
|
+
var blogHeroImageSchema = z2.object({
|
|
251
|
+
url: z2.string(),
|
|
252
|
+
alt: z2.string().catch(""),
|
|
253
|
+
width: z2.number().int(),
|
|
254
|
+
height: z2.number().int()
|
|
255
|
+
});
|
|
256
|
+
var blogManifestEntrySchema = z2.object({
|
|
257
|
+
slug: z2.string(),
|
|
258
|
+
title: z2.string(),
|
|
259
|
+
description: z2.string().catch(""),
|
|
260
|
+
content_hash: z2.string(),
|
|
261
|
+
content_key: z2.string(),
|
|
262
|
+
published_at: z2.string(),
|
|
263
|
+
updated_at: z2.string(),
|
|
264
|
+
// Estimated read time in whole minutes (computed at publish from the post
|
|
265
|
+
// body). 0 when unknown; index cards render "N min read".
|
|
266
|
+
reading_minutes: z2.number().int().catch(0),
|
|
267
|
+
// A malformed tags array / hero_image degrades gracefully instead of
|
|
268
|
+
// dropping the whole entry.
|
|
269
|
+
tags: z2.array(z2.string()).catch([]),
|
|
270
|
+
hero_image: blogHeroImageSchema.nullable().catch(null)
|
|
271
|
+
});
|
|
272
|
+
var blogManifestWireSchema = z2.object({
|
|
273
|
+
schema_version: schemaVersionSchema,
|
|
274
|
+
namespace: z2.string(),
|
|
275
|
+
generated_at: z2.string(),
|
|
276
|
+
posts: z2.array(blogManifestEntrySchema).catch([])
|
|
277
|
+
});
|
|
278
|
+
var blogManifestSchema = blogManifestWireSchema.extend({
|
|
279
|
+
// A single malformed entry is dropped; the rest of the manifest survives.
|
|
280
|
+
posts: z2.array(blogManifestEntrySchema.nullable().catch(null)).transform((entries) => entries.filter((e) => e !== null)).catch([])
|
|
281
|
+
});
|
|
282
|
+
var blogImageSchema = z2.object({
|
|
283
|
+
id: z2.string(),
|
|
284
|
+
url: z2.string(),
|
|
285
|
+
alt: z2.string().catch(""),
|
|
286
|
+
caption: z2.string().nullable().catch(null),
|
|
287
|
+
role: z2.enum(["hero", "inline"]).catch("inline"),
|
|
288
|
+
width: z2.number().int(),
|
|
289
|
+
height: z2.number().int(),
|
|
290
|
+
mime: z2.string().catch("")
|
|
291
|
+
});
|
|
292
|
+
var blogSeoSchema = z2.object({
|
|
293
|
+
meta_title: z2.string().catch(""),
|
|
294
|
+
meta_description: z2.string().catch(""),
|
|
295
|
+
canonical_path: z2.string().catch(""),
|
|
296
|
+
og_image: z2.string().nullable().catch(null)
|
|
297
|
+
});
|
|
298
|
+
var blogPostContentWireSchema = z2.object({
|
|
299
|
+
schema_version: schemaVersionSchema,
|
|
300
|
+
slug: z2.string(),
|
|
301
|
+
title: z2.string(),
|
|
302
|
+
description: z2.string().catch(""),
|
|
303
|
+
mdx: z2.string(),
|
|
304
|
+
images: z2.array(blogImageSchema).catch([]),
|
|
305
|
+
// Already schema-validated at publish time, but re-sanitized on read (and
|
|
306
|
+
// again before rendering) — the override schema itself drops invalid values.
|
|
307
|
+
// Nullish: the backend serializes an unset override as an explicit `null`
|
|
308
|
+
// (Pydantic `model_dump()`); it folds into `undefined` for TS consumers.
|
|
309
|
+
theme_overrides: nullishField(blogThemeOverrideSchema).catch(void 0),
|
|
310
|
+
seo: blogSeoSchema.catch({
|
|
311
|
+
meta_title: "",
|
|
312
|
+
meta_description: "",
|
|
313
|
+
canonical_path: "",
|
|
314
|
+
og_image: null
|
|
315
|
+
}),
|
|
316
|
+
jsonld: z2.record(z2.unknown()).catch({}),
|
|
317
|
+
published_at: z2.string(),
|
|
318
|
+
// The applied_at of the version this content was published from; `null`
|
|
319
|
+
// when the backend has no separate modified date. Nullish on the wire
|
|
320
|
+
// (Pydantic `model_dump()` emits explicit `null`s), folded to `null` for a
|
|
321
|
+
// single TS sentinel; a malformed value degrades to `null` instead of
|
|
322
|
+
// dropping the post.
|
|
323
|
+
updated_at: z2.string().nullish().transform((v) => v ?? null).catch(null),
|
|
324
|
+
version_number: z2.number().int().catch(0),
|
|
325
|
+
content_hash: z2.string()
|
|
326
|
+
});
|
|
327
|
+
var blogPostContentSchema = blogPostContentWireSchema.extend({
|
|
328
|
+
// A single malformed image record is dropped; the post survives.
|
|
329
|
+
images: z2.array(blogImageSchema.nullable().catch(null)).transform((images) => images.filter((i) => i !== null)).catch([])
|
|
330
|
+
});
|
|
331
|
+
function parseBlogManifest(raw) {
|
|
332
|
+
const parsed = blogManifestSchema.safeParse(raw);
|
|
333
|
+
return parsed.success ? parsed.data : null;
|
|
334
|
+
}
|
|
335
|
+
function parseBlogPostContent(raw) {
|
|
336
|
+
const parsed = blogPostContentSchema.safeParse(raw);
|
|
337
|
+
return parsed.success ? parsed.data : null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ../tracking-core/src/resources/blog/client.ts
|
|
341
|
+
function createBlogClient(config) {
|
|
342
|
+
const base = config.cdnBaseUrl.replace(/\/+$/, "");
|
|
343
|
+
const doFetch = (...args) => (config.fetchImpl ?? fetch)(...args);
|
|
344
|
+
async function fetchJson(url, init) {
|
|
345
|
+
try {
|
|
346
|
+
const response = await doFetch(url, init);
|
|
347
|
+
if (!response.ok) return null;
|
|
348
|
+
return await response.json();
|
|
349
|
+
} catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function getManifest() {
|
|
354
|
+
const init = {
|
|
355
|
+
next: { revalidate: config.manifestRevalidateSeconds ?? 300 }
|
|
356
|
+
};
|
|
357
|
+
const raw = await fetchJson(`${base}/blogs/v1/${config.namespace}/manifest.json`, init);
|
|
358
|
+
return raw === null ? null : parseBlogManifest(raw);
|
|
359
|
+
}
|
|
360
|
+
async function listPosts() {
|
|
361
|
+
const manifest = await getManifest();
|
|
362
|
+
if (!manifest) return [];
|
|
363
|
+
return [...manifest.posts].sort((a, b) => b.published_at.localeCompare(a.published_at));
|
|
364
|
+
}
|
|
365
|
+
async function getPost(slug) {
|
|
366
|
+
const manifest = await getManifest();
|
|
367
|
+
const entry = manifest?.posts.find((post) => post.slug === slug);
|
|
368
|
+
if (!entry) return null;
|
|
369
|
+
const init = { cache: "force-cache" };
|
|
370
|
+
const raw = await fetchJson(`${base}/${entry.content_key.replace(/^\/+/, "")}`, init);
|
|
371
|
+
return raw === null ? null : parseBlogPostContent(raw);
|
|
372
|
+
}
|
|
373
|
+
async function getStaticParams() {
|
|
374
|
+
const posts = await listPosts();
|
|
375
|
+
return posts.map((post) => ({ slug: post.slug }));
|
|
376
|
+
}
|
|
377
|
+
return { getManifest, listPosts, getPost, getStaticParams };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ../tracking-core/src/resources/blog/presets.ts
|
|
381
|
+
var BLOG_THEME_PRESETS = {
|
|
382
|
+
/** Serif display headings over a humanist sans body — long-form magazine feel. */
|
|
383
|
+
editorial: {
|
|
384
|
+
typography: {
|
|
385
|
+
heading_family: "'Source Serif 4', 'Iowan Old Style', 'Palatino Linotype', Georgia, serif",
|
|
386
|
+
body_family: "'Source Sans 3', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif",
|
|
387
|
+
heading_weight: 600,
|
|
388
|
+
body_weight: 400,
|
|
389
|
+
h1_size: { base: "2rem", md: "2.5rem" },
|
|
390
|
+
h2_size: { base: "1.5rem", md: "1.875rem" },
|
|
391
|
+
h3_size: "1.3rem",
|
|
392
|
+
h4_size: "1.125rem",
|
|
393
|
+
body_size: "1.0625rem",
|
|
394
|
+
body_line_height: "1.75",
|
|
395
|
+
heading_letter_spacing: "-0.015em"
|
|
396
|
+
},
|
|
397
|
+
spacing: {
|
|
398
|
+
block_gap: "1.35rem",
|
|
399
|
+
section_gap: "3.25rem"
|
|
400
|
+
},
|
|
401
|
+
radius: "6px"
|
|
402
|
+
},
|
|
403
|
+
/** Geometric sans pairing (Inter-first) — tight, contemporary product voice. */
|
|
404
|
+
modern: {
|
|
405
|
+
typography: {
|
|
406
|
+
heading_family: "'Inter', 'SF Pro Display', 'Segoe UI', Roboto, sans-serif",
|
|
407
|
+
body_family: "'Inter', 'SF Pro Text', 'Segoe UI', Roboto, sans-serif",
|
|
408
|
+
heading_weight: 700,
|
|
409
|
+
body_weight: 400,
|
|
410
|
+
h1_size: { base: "2.1rem", md: "2.75rem" },
|
|
411
|
+
h2_size: { base: "1.5rem", md: "1.75rem" },
|
|
412
|
+
h3_size: "1.25rem",
|
|
413
|
+
h4_size: "1.125rem",
|
|
414
|
+
body_size: "1rem",
|
|
415
|
+
body_line_height: "1.7",
|
|
416
|
+
heading_letter_spacing: "-0.03em"
|
|
417
|
+
},
|
|
418
|
+
spacing: {
|
|
419
|
+
block_gap: "1.25rem",
|
|
420
|
+
section_gap: "3rem"
|
|
421
|
+
},
|
|
422
|
+
radius: "12px"
|
|
423
|
+
},
|
|
424
|
+
/** Rounded-friendly pairing with a softer scale — approachable, human tone. */
|
|
425
|
+
warm: {
|
|
426
|
+
typography: {
|
|
427
|
+
heading_family: "'Nunito', 'Avenir Next', 'Trebuchet MS', Verdana, sans-serif",
|
|
428
|
+
body_family: "'Nunito Sans', 'Avenir Next', 'Trebuchet MS', Verdana, sans-serif",
|
|
429
|
+
heading_weight: 700,
|
|
430
|
+
body_weight: 400,
|
|
431
|
+
h1_size: { base: "1.9rem", md: "2.35rem" },
|
|
432
|
+
h2_size: { base: "1.45rem", md: "1.7rem" },
|
|
433
|
+
h3_size: "1.25rem",
|
|
434
|
+
h4_size: "1.125rem",
|
|
435
|
+
body_size: "1.0625rem",
|
|
436
|
+
body_line_height: "1.75",
|
|
437
|
+
heading_letter_spacing: "-0.01em"
|
|
438
|
+
},
|
|
439
|
+
spacing: {
|
|
440
|
+
block_gap: "1.35rem",
|
|
441
|
+
section_gap: "3.25rem"
|
|
442
|
+
},
|
|
443
|
+
radius: "16px"
|
|
444
|
+
},
|
|
445
|
+
/** Workmanlike sans with a prominent code surface — docs/engineering feel. */
|
|
446
|
+
technical: {
|
|
447
|
+
colors: {
|
|
448
|
+
// The one preset that ships a color: a cool code surface is part of the
|
|
449
|
+
// "technical" identity. Everything else stays on the site palette.
|
|
450
|
+
code_background: "#eef1f5"
|
|
451
|
+
},
|
|
452
|
+
colors_dark: {
|
|
453
|
+
code_background: "#1b1f27"
|
|
454
|
+
},
|
|
455
|
+
typography: {
|
|
456
|
+
heading_family: "'IBM Plex Sans', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif",
|
|
457
|
+
body_family: "'IBM Plex Sans', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif",
|
|
458
|
+
heading_weight: 600,
|
|
459
|
+
body_weight: 400,
|
|
460
|
+
h1_size: { base: "1.85rem", md: "2.25rem" },
|
|
461
|
+
h2_size: { base: "1.45rem", md: "1.65rem" },
|
|
462
|
+
h3_size: "1.2rem",
|
|
463
|
+
h4_size: "1.0625rem",
|
|
464
|
+
body_size: "1rem",
|
|
465
|
+
body_line_height: "1.65",
|
|
466
|
+
heading_letter_spacing: "-0.01em"
|
|
467
|
+
},
|
|
468
|
+
spacing: {
|
|
469
|
+
block_gap: "1.15rem",
|
|
470
|
+
section_gap: "2.75rem"
|
|
471
|
+
},
|
|
472
|
+
radius: "6px"
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
function prependFamily(front, stack) {
|
|
476
|
+
if (!front) return stack;
|
|
477
|
+
return stack ? `${front}, ${stack}` : front;
|
|
478
|
+
}
|
|
479
|
+
function createBlogFontTheme(preset, fonts) {
|
|
480
|
+
const heading_family = prependFamily(fonts.heading, preset.typography?.heading_family);
|
|
481
|
+
const body_family = prependFamily(fonts.body, preset.typography?.body_family);
|
|
482
|
+
return {
|
|
483
|
+
...preset,
|
|
484
|
+
typography: {
|
|
485
|
+
...preset.typography,
|
|
486
|
+
...heading_family !== void 0 ? { heading_family } : {},
|
|
487
|
+
...body_family !== void 0 ? { body_family } : {}
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ../tracking-core/src/resources/blog/view/class-names.ts
|
|
493
|
+
function joinClassNames(base, extra) {
|
|
494
|
+
return extra ? `${base} ${extra}` : base;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ../tracking-core/src/resources/blog/seo.ts
|
|
498
|
+
function joinUrl(base, ...paths) {
|
|
499
|
+
let out = base.replace(/\/+$/, "");
|
|
500
|
+
for (const path of paths) {
|
|
501
|
+
const segment = path?.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
502
|
+
if (segment) out += `/${segment}`;
|
|
503
|
+
}
|
|
504
|
+
return out;
|
|
505
|
+
}
|
|
506
|
+
function canonicalUrl(post, opts) {
|
|
507
|
+
return joinUrl(opts.siteUrl, opts.basePath ?? "/blog", post.slug);
|
|
508
|
+
}
|
|
509
|
+
function ogImages(post) {
|
|
510
|
+
if (post.seo.og_image) return [{ url: post.seo.og_image }];
|
|
511
|
+
const hero = post.images.find((image) => image.role === "hero");
|
|
512
|
+
if (!hero) return void 0;
|
|
513
|
+
return [{ url: hero.url, width: hero.width, height: hero.height, alt: hero.alt }];
|
|
514
|
+
}
|
|
515
|
+
function buildBlogPostMetadata(post, opts) {
|
|
516
|
+
const url = canonicalUrl(post, opts);
|
|
517
|
+
const title = post.seo.meta_title || post.title;
|
|
518
|
+
const description = post.seo.meta_description || post.description;
|
|
519
|
+
const images = ogImages(post);
|
|
520
|
+
return {
|
|
521
|
+
title,
|
|
522
|
+
description,
|
|
523
|
+
alternates: { canonical: url },
|
|
524
|
+
openGraph: {
|
|
525
|
+
title,
|
|
526
|
+
description,
|
|
527
|
+
type: "article",
|
|
528
|
+
url,
|
|
529
|
+
...images ? { images } : {},
|
|
530
|
+
...post.published_at ? { publishedTime: post.published_at } : {}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
function buildBlogPostingJsonLd(post, opts) {
|
|
535
|
+
const url = canonicalUrl(post, opts);
|
|
536
|
+
const images = post.images.map((image) => image.url);
|
|
537
|
+
const doc = {
|
|
538
|
+
headline: post.title,
|
|
539
|
+
description: post.seo.meta_description || post.description,
|
|
540
|
+
datePublished: post.published_at,
|
|
541
|
+
dateModified: post.updated_at ?? post.published_at,
|
|
542
|
+
...images.length > 0 ? { image: images } : {},
|
|
543
|
+
...opts.publisherName ? { publisher: { "@type": "Organization", name: opts.publisherName } } : {}
|
|
544
|
+
};
|
|
545
|
+
const backend = post.jsonld;
|
|
546
|
+
const merged = backend && typeof backend === "object" && Object.keys(backend).length > 0 ? { ...doc, ...backend } : doc;
|
|
547
|
+
return {
|
|
548
|
+
...merged,
|
|
549
|
+
"@context": "https://schema.org",
|
|
550
|
+
"@type": "BlogPosting",
|
|
551
|
+
url,
|
|
552
|
+
mainEntityOfPage: { "@type": "WebPage", "@id": url }
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
var FAQ_ITEM_RE = /<FaqItem\s+question=(?:"([^"]*)"|'([^']*)')\s*>([\s\S]*?)<\/FaqItem>/g;
|
|
556
|
+
var CODE_FENCE_RE = /^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^ {0,3}\1`* *$|(?![\s\S]))/gm;
|
|
557
|
+
var ANSWER_TAG_RE = /<[^>]*>/g;
|
|
558
|
+
function extractFaqPairs(mdx) {
|
|
559
|
+
const withoutCode = mdx.replaceAll(CODE_FENCE_RE, "");
|
|
560
|
+
const pairs = [];
|
|
561
|
+
for (const match of withoutCode.matchAll(FAQ_ITEM_RE)) {
|
|
562
|
+
const question = (match[1] ?? match[2] ?? "").trim();
|
|
563
|
+
const answer = match[3].replaceAll(ANSWER_TAG_RE, " ").replaceAll(/\s+/g, " ").trim();
|
|
564
|
+
if (question && answer) pairs.push({ question, answer });
|
|
565
|
+
}
|
|
566
|
+
return pairs;
|
|
567
|
+
}
|
|
568
|
+
function buildFaqJsonLd(pairs) {
|
|
569
|
+
if (pairs.length === 0) return null;
|
|
570
|
+
return {
|
|
571
|
+
"@context": "https://schema.org",
|
|
572
|
+
"@type": "FAQPage",
|
|
573
|
+
mainEntity: pairs.map((pair) => ({
|
|
574
|
+
"@type": "Question",
|
|
575
|
+
name: pair.question,
|
|
576
|
+
acceptedAnswer: { "@type": "Answer", text: pair.answer }
|
|
577
|
+
}))
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
var JSONLD_ESCAPES = {
|
|
581
|
+
"<": "\\u003c",
|
|
582
|
+
">": "\\u003e",
|
|
583
|
+
"&": "\\u0026",
|
|
584
|
+
"\u2028": "\\u2028",
|
|
585
|
+
"\u2029": "\\u2029"
|
|
586
|
+
};
|
|
587
|
+
function serializeJsonLd(data) {
|
|
588
|
+
return JSON.stringify(data).replaceAll(/[<>&\u2028\u2029]/g, (ch) => JSONLD_ESCAPES[ch] ?? ch);
|
|
589
|
+
}
|
|
590
|
+
function buildBlogSitemapEntries(posts, opts) {
|
|
591
|
+
return posts.map((post) => ({
|
|
592
|
+
url: joinUrl(opts.siteUrl, opts.basePath ?? "/blog", post.slug),
|
|
593
|
+
lastModified: post.updated_at
|
|
594
|
+
}));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// ../tracking-core/src/resources/blog/toc.ts
|
|
598
|
+
import GithubSlugger from "github-slugger";
|
|
599
|
+
import remarkMdx from "remark-mdx";
|
|
600
|
+
import remarkParse from "remark-parse";
|
|
601
|
+
import { unified } from "unified";
|
|
602
|
+
var TEXT_LEAF_TYPES = /* @__PURE__ */ new Set(["text", "inlineCode"]);
|
|
603
|
+
function headingText(node) {
|
|
604
|
+
let text = "";
|
|
605
|
+
const stack = [];
|
|
606
|
+
const roots = node.children ?? [];
|
|
607
|
+
for (let i = roots.length - 1; i >= 0; i--) {
|
|
608
|
+
stack.push(roots[i]);
|
|
609
|
+
}
|
|
610
|
+
while (stack.length > 0) {
|
|
611
|
+
const current = stack.pop();
|
|
612
|
+
if (!current) break;
|
|
613
|
+
if (TEXT_LEAF_TYPES.has(current.type) && typeof current.value === "string") {
|
|
614
|
+
text += current.value;
|
|
615
|
+
}
|
|
616
|
+
if (Array.isArray(current.children)) {
|
|
617
|
+
for (let i = current.children.length - 1; i >= 0; i--) {
|
|
618
|
+
stack.push(current.children[i]);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return text;
|
|
623
|
+
}
|
|
624
|
+
function extractBlogHeadings(mdx) {
|
|
625
|
+
let tree;
|
|
626
|
+
try {
|
|
627
|
+
tree = unified().use(remarkParse).use(remarkMdx).parse(mdx);
|
|
628
|
+
} catch {
|
|
629
|
+
return [];
|
|
630
|
+
}
|
|
631
|
+
const slugger = new GithubSlugger();
|
|
632
|
+
const headings = [];
|
|
633
|
+
const stack = [tree];
|
|
634
|
+
while (stack.length > 0) {
|
|
635
|
+
const node = stack.pop();
|
|
636
|
+
if (!node) break;
|
|
637
|
+
if (node.type === "heading" && typeof node.depth === "number") {
|
|
638
|
+
const text = headingText(node);
|
|
639
|
+
const id = slugger.slug(text);
|
|
640
|
+
if (node.depth === 2 || node.depth === 3) {
|
|
641
|
+
headings.push({ depth: node.depth, text, id });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
if (Array.isArray(node.children)) {
|
|
645
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
646
|
+
stack.push(node.children[i]);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return headings;
|
|
651
|
+
}
|
|
652
|
+
var WORDS_PER_MINUTE = 220;
|
|
653
|
+
var MDX_SYNTAX_RE = /<[^>]*>|```[^`]*```|`|^[#>\-*+\s]+|\]\([^)]*\)|[[\]!]/gm;
|
|
654
|
+
function estimateReadingTime(mdx) {
|
|
655
|
+
const words = mdx.replaceAll(MDX_SYNTAX_RE, " ").split(/\s+/).filter((w) => w.length > 0).length;
|
|
656
|
+
return Math.max(1, Math.round(words / WORDS_PER_MINUTE));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ../tracking-core/src/resources/blog/validate-mdx.ts
|
|
660
|
+
import remarkMdx2 from "remark-mdx";
|
|
661
|
+
import remarkParse2 from "remark-parse";
|
|
662
|
+
import { unified as unified2 } from "unified";
|
|
663
|
+
var BLOG_MDX_MAX_SIZE = 5e5;
|
|
664
|
+
var BLOG_MDX_MAX_DEPTH = 64;
|
|
665
|
+
var BLOG_MDX_MAX_NODES = 1e5;
|
|
666
|
+
var MAX_VIOLATIONS = 50;
|
|
667
|
+
var BLOG_MDX_COMPONENT_GRAMMAR = [
|
|
668
|
+
{
|
|
669
|
+
name: "Callout",
|
|
670
|
+
attrs: [],
|
|
671
|
+
urlAttrs: [],
|
|
672
|
+
selfClosing: false,
|
|
673
|
+
cssClass: "aranova-blog-callout",
|
|
674
|
+
promptUsage: "<Callout>...</Callout>",
|
|
675
|
+
promptDescription: "a highlighted tip/note block; no attributes."
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: "Cta",
|
|
679
|
+
attrs: ["href", "label"],
|
|
680
|
+
urlAttrs: ["href"],
|
|
681
|
+
selfClosing: true,
|
|
682
|
+
cssClass: "aranova-blog-cta",
|
|
683
|
+
promptUsage: '<Cta href="..." label="..." />',
|
|
684
|
+
promptDescription: "a call-to-action button (self-closing)."
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: "BlogImage",
|
|
688
|
+
attrs: ["id"],
|
|
689
|
+
urlAttrs: [],
|
|
690
|
+
selfClosing: true,
|
|
691
|
+
promptUsage: '<BlogImage id="..." />',
|
|
692
|
+
promptDescription: "a generated image placeholder (self-closing). The `id` references an image brief id from the plan; the renderer resolves it."
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
name: "FaqItem",
|
|
696
|
+
attrs: ["question"],
|
|
697
|
+
urlAttrs: [],
|
|
698
|
+
selfClosing: false,
|
|
699
|
+
cssClass: "aranova-blog-faq-item",
|
|
700
|
+
promptUsage: '<FaqItem question="...">...</FaqItem>',
|
|
701
|
+
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."
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
name: "KeyTakeaways",
|
|
705
|
+
attrs: [],
|
|
706
|
+
urlAttrs: [],
|
|
707
|
+
selfClosing: false,
|
|
708
|
+
cssClass: "aranova-blog-key-takeaways",
|
|
709
|
+
promptUsage: "<KeyTakeaways>...</KeyTakeaways>",
|
|
710
|
+
promptDescription: "a TL;DR box containing a standard markdown bullet list of 3-5 takeaways; place it right after the H1 (and hero image)."
|
|
711
|
+
}
|
|
712
|
+
];
|
|
713
|
+
var BLOG_MDX_ALLOWED_COMPONENTS = BLOG_MDX_COMPONENT_GRAMMAR.map((entry) => entry.name);
|
|
714
|
+
var ALLOWED_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
715
|
+
"root",
|
|
716
|
+
"heading",
|
|
717
|
+
"paragraph",
|
|
718
|
+
"text",
|
|
719
|
+
"emphasis",
|
|
720
|
+
"strong",
|
|
721
|
+
"delete",
|
|
722
|
+
"link",
|
|
723
|
+
"image",
|
|
724
|
+
"linkReference",
|
|
725
|
+
"imageReference",
|
|
726
|
+
"definition",
|
|
727
|
+
"list",
|
|
728
|
+
"listItem",
|
|
729
|
+
"blockquote",
|
|
730
|
+
"code",
|
|
731
|
+
"inlineCode",
|
|
732
|
+
"table",
|
|
733
|
+
"tableRow",
|
|
734
|
+
"tableCell",
|
|
735
|
+
"thematicBreak",
|
|
736
|
+
"break"
|
|
737
|
+
]);
|
|
738
|
+
var FORBIDDEN_NODE_TYPES = {
|
|
739
|
+
html: { rule: "raw-html", message: "Raw HTML is not allowed" },
|
|
740
|
+
mdxjsEsm: { rule: "esm", message: "import/export statements are not allowed" },
|
|
741
|
+
mdxFlowExpression: { rule: "expression", message: "JSX expressions ({\u2026}) are not allowed" },
|
|
742
|
+
mdxTextExpression: { rule: "expression", message: "JSX expressions ({\u2026}) are not allowed" }
|
|
743
|
+
};
|
|
744
|
+
var COMPONENT_SPECS = Object.fromEntries(
|
|
745
|
+
BLOG_MDX_COMPONENT_GRAMMAR.map((entry) => [
|
|
746
|
+
entry.name,
|
|
747
|
+
{ attrs: entry.attrs, urlAttrs: entry.urlAttrs, allowChildren: !entry.selfClosing }
|
|
748
|
+
])
|
|
749
|
+
);
|
|
750
|
+
var URL_SCHEME_RE = /^([A-Za-z][A-Za-z0-9+.-]*):/;
|
|
751
|
+
var ALLOWED_URL_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto"]);
|
|
752
|
+
function forbiddenUrlScheme(rawUrl) {
|
|
753
|
+
const cleaned = rawUrl.replaceAll(/[\u0000-\u0020\u007F]/g, "");
|
|
754
|
+
const match = URL_SCHEME_RE.exec(cleaned);
|
|
755
|
+
if (!match) return null;
|
|
756
|
+
const scheme = match[1].toLowerCase();
|
|
757
|
+
return ALLOWED_URL_SCHEMES.has(scheme) ? null : scheme;
|
|
758
|
+
}
|
|
759
|
+
function nodeLine(node) {
|
|
760
|
+
return node.position?.start?.line;
|
|
761
|
+
}
|
|
762
|
+
function checkComponent(node, violations) {
|
|
763
|
+
const line = nodeLine(node);
|
|
764
|
+
const name = node.name;
|
|
765
|
+
if (!name) {
|
|
766
|
+
violations.push({
|
|
767
|
+
rule: "unknown-component",
|
|
768
|
+
message: "JSX fragments (<>\u2026</>) are not allowed",
|
|
769
|
+
line
|
|
770
|
+
});
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const spec = COMPONENT_SPECS[name];
|
|
774
|
+
if (!spec) {
|
|
775
|
+
violations.push({
|
|
776
|
+
rule: "unknown-component",
|
|
777
|
+
message: `<${name}> is not an allowed element (allowed: ${BLOG_MDX_ALLOWED_COMPONENTS.join(", ")})`,
|
|
778
|
+
line
|
|
779
|
+
});
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const seen = /* @__PURE__ */ new Set();
|
|
783
|
+
for (const attr of node.attributes ?? []) {
|
|
784
|
+
if (attr.type !== "mdxJsxAttribute" || typeof attr.name !== "string") {
|
|
785
|
+
violations.push({
|
|
786
|
+
rule: "component-attribute",
|
|
787
|
+
message: `<${name}> has a non-literal attribute`,
|
|
788
|
+
line
|
|
789
|
+
});
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
if (!spec.attrs.includes(attr.name)) {
|
|
793
|
+
violations.push({
|
|
794
|
+
rule: "component-attribute",
|
|
795
|
+
message: `<${name}> does not accept attribute "${attr.name}"`,
|
|
796
|
+
line
|
|
797
|
+
});
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (typeof attr.value !== "string") {
|
|
801
|
+
violations.push({
|
|
802
|
+
rule: "component-attribute",
|
|
803
|
+
message: `<${name} ${attr.name}> must be a quoted string literal`,
|
|
804
|
+
line
|
|
805
|
+
});
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
seen.add(attr.name);
|
|
809
|
+
if (spec.urlAttrs.includes(attr.name)) {
|
|
810
|
+
const scheme = forbiddenUrlScheme(attr.value);
|
|
811
|
+
if (scheme !== null) {
|
|
812
|
+
violations.push({
|
|
813
|
+
rule: "url-scheme",
|
|
814
|
+
message: `<${name} ${attr.name}> uses forbidden URL scheme "${scheme}:"`,
|
|
815
|
+
line
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
for (const required of spec.attrs) {
|
|
821
|
+
if (!seen.has(required)) {
|
|
822
|
+
violations.push({
|
|
823
|
+
rule: "component-attribute",
|
|
824
|
+
message: `<${name}> requires attribute "${required}"`,
|
|
825
|
+
line
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (!spec.allowChildren && (node.children?.length ?? 0) > 0) {
|
|
830
|
+
violations.push({
|
|
831
|
+
rule: "component-attribute",
|
|
832
|
+
message: `<${name}> must be self-closing (no children)`,
|
|
833
|
+
line
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function validateBlogMdx(mdx, options) {
|
|
838
|
+
const violations = [];
|
|
839
|
+
if (typeof mdx !== "string" || mdx.length > BLOG_MDX_MAX_SIZE) {
|
|
840
|
+
return {
|
|
841
|
+
ok: false,
|
|
842
|
+
violations: [
|
|
843
|
+
{
|
|
844
|
+
rule: "size-limit",
|
|
845
|
+
message: `Document exceeds the ${BLOG_MDX_MAX_SIZE}-character limit`
|
|
846
|
+
}
|
|
847
|
+
]
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
let tree;
|
|
851
|
+
try {
|
|
852
|
+
tree = unified2().use(remarkParse2).use(remarkMdx2).parse(mdx);
|
|
853
|
+
} catch (error) {
|
|
854
|
+
const line = error.line;
|
|
855
|
+
return {
|
|
856
|
+
ok: false,
|
|
857
|
+
violations: [
|
|
858
|
+
{
|
|
859
|
+
rule: "parse-error",
|
|
860
|
+
message: "Document is not valid MDX",
|
|
861
|
+
...typeof line === "number" ? { line } : {}
|
|
862
|
+
}
|
|
863
|
+
]
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
if (options?.requireLeadingH1) {
|
|
867
|
+
const firstChild = tree.children?.[0];
|
|
868
|
+
if (!firstChild || firstChild.type !== "heading" || firstChild.depth !== 1) {
|
|
869
|
+
violations.push({
|
|
870
|
+
rule: "leading-h1",
|
|
871
|
+
message: "Document must start with the H1 title (`# ...`) \u2014 no frontmatter or metadata block before it",
|
|
872
|
+
line: nodeLine(firstChild ?? tree)
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
let nodeCount = 0;
|
|
877
|
+
const stack = [{ node: tree, depth: 0 }];
|
|
878
|
+
while (stack.length > 0 && violations.length < MAX_VIOLATIONS) {
|
|
879
|
+
const item = stack.pop();
|
|
880
|
+
if (!item) break;
|
|
881
|
+
const { node, depth } = item;
|
|
882
|
+
nodeCount += 1;
|
|
883
|
+
if (nodeCount > BLOG_MDX_MAX_NODES) {
|
|
884
|
+
violations.push({ rule: "size-limit", message: "Document has too many nodes" });
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
if (depth > BLOG_MDX_MAX_DEPTH) {
|
|
888
|
+
violations.push({ rule: "depth-limit", message: "Document nests too deeply" });
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
const forbidden = FORBIDDEN_NODE_TYPES[node.type];
|
|
892
|
+
if (forbidden) {
|
|
893
|
+
violations.push({ ...forbidden, line: nodeLine(node) });
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
|
|
897
|
+
checkComponent(node, violations);
|
|
898
|
+
} else if (!ALLOWED_NODE_TYPES.has(node.type)) {
|
|
899
|
+
violations.push({
|
|
900
|
+
rule: "disallowed-node",
|
|
901
|
+
message: `Markdown construct "${node.type}" is not allowed`,
|
|
902
|
+
line: nodeLine(node)
|
|
903
|
+
});
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if ((node.type === "link" || node.type === "image" || node.type === "definition") && typeof node.url === "string") {
|
|
907
|
+
const scheme = forbiddenUrlScheme(node.url);
|
|
908
|
+
if (scheme !== null) {
|
|
909
|
+
violations.push({
|
|
910
|
+
rule: "url-scheme",
|
|
911
|
+
message: `${node.type} uses forbidden URL scheme "${scheme}:"`,
|
|
912
|
+
line: nodeLine(node)
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (Array.isArray(node.children)) {
|
|
917
|
+
for (const child of node.children) {
|
|
918
|
+
stack.push({ node: child, depth: depth + 1 });
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
return violations.length === 0 ? { ok: true } : { ok: false, violations };
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// ../tracking-core/src/resources/blog/view/blog-post-view.tsx
|
|
926
|
+
import { compileMDX } from "next-mdx-remote/rsc";
|
|
927
|
+
import rehypeSlug from "rehype-slug";
|
|
928
|
+
|
|
929
|
+
// ../tracking-core/src/resources/blog/view/components.tsx
|
|
930
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
931
|
+
function createDefaultComponents(images, classNames) {
|
|
932
|
+
const imagesById = new Map(images.map((image) => [image.id, image]));
|
|
933
|
+
return {
|
|
934
|
+
// `children` is destructured explicitly so static a11y analysis
|
|
935
|
+
// (heading-has-content, anchor-has-content) can see the content flow.
|
|
936
|
+
h1: ({ children, ...props }) => /* @__PURE__ */ jsx("h1", { ...props, children }),
|
|
937
|
+
h2: ({ children, ...props }) => /* @__PURE__ */ jsx("h2", { ...props, children }),
|
|
938
|
+
h3: ({ children, ...props }) => /* @__PURE__ */ jsx("h3", { ...props, children }),
|
|
939
|
+
h4: ({ children, ...props }) => /* @__PURE__ */ jsx("h4", { ...props, children }),
|
|
940
|
+
h5: ({ children, ...props }) => /* @__PURE__ */ jsx("h5", { ...props, children }),
|
|
941
|
+
h6: ({ children, ...props }) => /* @__PURE__ */ jsx("h6", { ...props, children }),
|
|
942
|
+
p: ({ children, ...props }) => /* @__PURE__ */ jsx("p", { ...props, children }),
|
|
943
|
+
a: ({ children, ...props }) => /* @__PURE__ */ jsx("a", { ...props, children }),
|
|
944
|
+
img: ({ alt = "", ...props }) => /* @__PURE__ */ jsx("img", { loading: "lazy", alt, ...props }),
|
|
945
|
+
pre: ({ children, ...props }) => /* @__PURE__ */ jsx("pre", { ...props, children }),
|
|
946
|
+
code: ({ children, ...props }) => /* @__PURE__ */ jsx("code", { ...props, children }),
|
|
947
|
+
blockquote: ({ children, ...props }) => /* @__PURE__ */ jsx("blockquote", { ...props, children }),
|
|
948
|
+
ul: ({ children, ...props }) => /* @__PURE__ */ jsx("ul", { ...props, children }),
|
|
949
|
+
ol: ({ children, ...props }) => /* @__PURE__ */ jsx("ol", { ...props, children }),
|
|
950
|
+
li: ({ children, ...props }) => /* @__PURE__ */ jsx("li", { ...props, children }),
|
|
951
|
+
// Wrapped so wide tables scroll horizontally inside the article instead of
|
|
952
|
+
// overflowing the host page (the wrapper also carries border + radius).
|
|
953
|
+
table: ({ children, ...props }) => /* @__PURE__ */ jsx("div", { className: joinClassNames("aranova-blog-table-wrap", classNames?.tableWrap), children: /* @__PURE__ */ jsx("table", { ...props, children }) }),
|
|
954
|
+
hr: (props) => /* @__PURE__ */ jsx("hr", { ...props }),
|
|
955
|
+
Callout: ({ children }) => /* @__PURE__ */ jsx("aside", { className: joinClassNames("aranova-blog-callout", classNames?.callout), children }),
|
|
956
|
+
Cta: ({ href, label }) => (
|
|
957
|
+
// `data-aranova-cta` opts this into the tracking SDK's cta_click
|
|
958
|
+
// auto-capture (default selector `[data-aranova-cta]`) with the label as
|
|
959
|
+
// the cta_name — zero site config. Inert if auto-capture is off.
|
|
960
|
+
/* @__PURE__ */ jsx(
|
|
961
|
+
"a",
|
|
962
|
+
{
|
|
963
|
+
className: joinClassNames("aranova-blog-cta", classNames?.cta),
|
|
964
|
+
href,
|
|
965
|
+
"data-aranova-cta": label,
|
|
966
|
+
"data-aranova-section": "blog",
|
|
967
|
+
children: label
|
|
968
|
+
}
|
|
969
|
+
)
|
|
970
|
+
),
|
|
971
|
+
BlogImage: ({ id }) => {
|
|
972
|
+
const image = imagesById.get(id);
|
|
973
|
+
if (!image) return null;
|
|
974
|
+
return /* @__PURE__ */ jsxs("figure", { className: classNames?.figure, children: [
|
|
975
|
+
/* @__PURE__ */ jsx(
|
|
976
|
+
"img",
|
|
977
|
+
{
|
|
978
|
+
src: image.url,
|
|
979
|
+
alt: image.alt,
|
|
980
|
+
width: image.width,
|
|
981
|
+
height: image.height,
|
|
982
|
+
loading: "lazy"
|
|
983
|
+
}
|
|
984
|
+
),
|
|
985
|
+
image.caption ? /* @__PURE__ */ jsx("figcaption", { children: image.caption }) : null
|
|
986
|
+
] });
|
|
987
|
+
},
|
|
988
|
+
// Native disclosure accordion — no JS, server-renderable. The h3 inside
|
|
989
|
+
// <summary> is valid HTML and keeps the document outline / a11y heading.
|
|
990
|
+
FaqItem: ({ question, children }) => /* @__PURE__ */ jsxs("details", { className: joinClassNames("aranova-blog-faq-item", classNames?.faqItem), children: [
|
|
991
|
+
/* @__PURE__ */ jsx("summary", { className: joinClassNames("aranova-blog-faq-question", classNames?.faqQuestion), children: /* @__PURE__ */ jsx("h3", { children: question }) }),
|
|
992
|
+
/* @__PURE__ */ jsx("div", { className: joinClassNames("aranova-blog-faq-answer", classNames?.faqAnswer), children })
|
|
993
|
+
] }),
|
|
994
|
+
KeyTakeaways: ({ children }) => /* @__PURE__ */ jsx("aside", { className: joinClassNames("aranova-blog-key-takeaways", classNames?.keyTakeaways), children })
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// ../tracking-core/src/resources/blog/view/styles.ts
|
|
999
|
+
var BLOG_BASE_CSS = `/* Aranova blog post base stylesheet \u2014 authored source of truth.
|
|
1000
|
+
*
|
|
1001
|
+
* Scoped under \`.aranova-blog-post\` and driven entirely by the sanitized
|
|
1002
|
+
* \`--ara-blog-*\` CSS custom properties emitted by \`blogThemeToCssVars\` on the
|
|
1003
|
+
* article element. Host sites usually run Tailwind preflight, so:
|
|
1004
|
+
*
|
|
1005
|
+
* - element rules use \`.aranova-blog-post :where(el)\` \u2014 one class of
|
|
1006
|
+
* specificity (0,1,0), enough to beat preflight's bare element resets
|
|
1007
|
+
* while every rule in this sheet stays flat and order-resolvable;
|
|
1008
|
+
* - component classes (\`.aranova-blog-callout\`, \u2026) keep their natural
|
|
1009
|
+
* class-on-class specificity.
|
|
1010
|
+
*
|
|
1011
|
+
* Responsive-capable tokens (h1/h2/h3/h4/body/small sizes, body line-height,
|
|
1012
|
+
* content max-width, block gap, section gap, radius) are NEVER consumed via their raw
|
|
1013
|
+
* \`--ara-blog-*\` vars here \u2014 the generated block below re-points internal
|
|
1014
|
+
* \`--_*\` vars per breakpoint, and all rules consume those.
|
|
1015
|
+
*
|
|
1016
|
+
* Rhythm rules near the end target BOTH \`.aranova-blog-post\` (content is a
|
|
1017
|
+
* direct child of the article today) and \`.aranova-blog-content\` (a later
|
|
1018
|
+
* phase wraps content in that div) so that phase needs no CSS changes.
|
|
1019
|
+
*
|
|
1020
|
+
* No raw colors anywhere: every color flows through the theme vars, which
|
|
1021
|
+
* already carry \`light-dark(...)\` values when a dark palette is present.
|
|
1022
|
+
*/
|
|
1023
|
+
|
|
1024
|
+
/* Responsive token plumbing \u2014 GENERATED by build-styles.ts from theme.ts
|
|
1025
|
+
(BLOG_BREAKPOINT_MIN_WIDTHS_PX + the responsive() token set). Mobile-first:
|
|
1026
|
+
each breakpoint falls back through every narrower key down to the base var.
|
|
1027
|
+
Authored rules consume ONLY the --_* internal vars for these tokens. */
|
|
1028
|
+
.aranova-blog-post {
|
|
1029
|
+
--_h1-size: var(--ara-blog-type-h1-size);
|
|
1030
|
+
--_h2-size: var(--ara-blog-type-h2-size);
|
|
1031
|
+
--_h3-size: var(--ara-blog-type-h3-size);
|
|
1032
|
+
--_h4-size: var(--ara-blog-type-h4-size);
|
|
1033
|
+
--_body-size: var(--ara-blog-type-body-size);
|
|
1034
|
+
--_body-line-height: var(--ara-blog-type-body-line-height);
|
|
1035
|
+
--_small-size: var(--ara-blog-type-small-size);
|
|
1036
|
+
--_content-max-width: var(--ara-blog-space-content-max-width);
|
|
1037
|
+
--_block-gap: var(--ara-blog-space-block-gap);
|
|
1038
|
+
--_section-gap: var(--ara-blog-space-section-gap);
|
|
1039
|
+
--_radius: var(--ara-blog-radius);
|
|
1040
|
+
}
|
|
1041
|
+
@media (min-width: 640px) {
|
|
1042
|
+
.aranova-blog-post {
|
|
1043
|
+
--_h1-size: var(--ara-blog-type-h1-size-sm, var(--ara-blog-type-h1-size));
|
|
1044
|
+
--_h2-size: var(--ara-blog-type-h2-size-sm, var(--ara-blog-type-h2-size));
|
|
1045
|
+
--_h3-size: var(--ara-blog-type-h3-size-sm, var(--ara-blog-type-h3-size));
|
|
1046
|
+
--_h4-size: var(--ara-blog-type-h4-size-sm, var(--ara-blog-type-h4-size));
|
|
1047
|
+
--_body-size: var(--ara-blog-type-body-size-sm, var(--ara-blog-type-body-size));
|
|
1048
|
+
--_body-line-height: var(--ara-blog-type-body-line-height-sm, var(--ara-blog-type-body-line-height));
|
|
1049
|
+
--_small-size: var(--ara-blog-type-small-size-sm, var(--ara-blog-type-small-size));
|
|
1050
|
+
--_content-max-width: var(--ara-blog-space-content-max-width-sm, var(--ara-blog-space-content-max-width));
|
|
1051
|
+
--_block-gap: var(--ara-blog-space-block-gap-sm, var(--ara-blog-space-block-gap));
|
|
1052
|
+
--_section-gap: var(--ara-blog-space-section-gap-sm, var(--ara-blog-space-section-gap));
|
|
1053
|
+
--_radius: var(--ara-blog-radius-sm, var(--ara-blog-radius));
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
@media (min-width: 768px) {
|
|
1057
|
+
.aranova-blog-post {
|
|
1058
|
+
--_h1-size: var(--ara-blog-type-h1-size-md, var(--ara-blog-type-h1-size-sm, var(--ara-blog-type-h1-size)));
|
|
1059
|
+
--_h2-size: var(--ara-blog-type-h2-size-md, var(--ara-blog-type-h2-size-sm, var(--ara-blog-type-h2-size)));
|
|
1060
|
+
--_h3-size: var(--ara-blog-type-h3-size-md, var(--ara-blog-type-h3-size-sm, var(--ara-blog-type-h3-size)));
|
|
1061
|
+
--_h4-size: var(--ara-blog-type-h4-size-md, var(--ara-blog-type-h4-size-sm, var(--ara-blog-type-h4-size)));
|
|
1062
|
+
--_body-size: var(--ara-blog-type-body-size-md, var(--ara-blog-type-body-size-sm, var(--ara-blog-type-body-size)));
|
|
1063
|
+
--_body-line-height: var(--ara-blog-type-body-line-height-md, var(--ara-blog-type-body-line-height-sm, var(--ara-blog-type-body-line-height)));
|
|
1064
|
+
--_small-size: var(--ara-blog-type-small-size-md, var(--ara-blog-type-small-size-sm, var(--ara-blog-type-small-size)));
|
|
1065
|
+
--_content-max-width: var(--ara-blog-space-content-max-width-md, var(--ara-blog-space-content-max-width-sm, var(--ara-blog-space-content-max-width)));
|
|
1066
|
+
--_block-gap: var(--ara-blog-space-block-gap-md, var(--ara-blog-space-block-gap-sm, var(--ara-blog-space-block-gap)));
|
|
1067
|
+
--_section-gap: var(--ara-blog-space-section-gap-md, var(--ara-blog-space-section-gap-sm, var(--ara-blog-space-section-gap)));
|
|
1068
|
+
--_radius: var(--ara-blog-radius-md, var(--ara-blog-radius-sm, var(--ara-blog-radius)));
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
@media (min-width: 1024px) {
|
|
1072
|
+
.aranova-blog-post {
|
|
1073
|
+
--_h1-size: var(--ara-blog-type-h1-size-lg, var(--ara-blog-type-h1-size-md, var(--ara-blog-type-h1-size-sm, var(--ara-blog-type-h1-size))));
|
|
1074
|
+
--_h2-size: var(--ara-blog-type-h2-size-lg, var(--ara-blog-type-h2-size-md, var(--ara-blog-type-h2-size-sm, var(--ara-blog-type-h2-size))));
|
|
1075
|
+
--_h3-size: var(--ara-blog-type-h3-size-lg, var(--ara-blog-type-h3-size-md, var(--ara-blog-type-h3-size-sm, var(--ara-blog-type-h3-size))));
|
|
1076
|
+
--_h4-size: var(--ara-blog-type-h4-size-lg, var(--ara-blog-type-h4-size-md, var(--ara-blog-type-h4-size-sm, var(--ara-blog-type-h4-size))));
|
|
1077
|
+
--_body-size: var(--ara-blog-type-body-size-lg, var(--ara-blog-type-body-size-md, var(--ara-blog-type-body-size-sm, var(--ara-blog-type-body-size))));
|
|
1078
|
+
--_body-line-height: var(--ara-blog-type-body-line-height-lg, var(--ara-blog-type-body-line-height-md, var(--ara-blog-type-body-line-height-sm, var(--ara-blog-type-body-line-height))));
|
|
1079
|
+
--_small-size: var(--ara-blog-type-small-size-lg, var(--ara-blog-type-small-size-md, var(--ara-blog-type-small-size-sm, var(--ara-blog-type-small-size))));
|
|
1080
|
+
--_content-max-width: var(--ara-blog-space-content-max-width-lg, var(--ara-blog-space-content-max-width-md, var(--ara-blog-space-content-max-width-sm, var(--ara-blog-space-content-max-width))));
|
|
1081
|
+
--_block-gap: var(--ara-blog-space-block-gap-lg, var(--ara-blog-space-block-gap-md, var(--ara-blog-space-block-gap-sm, var(--ara-blog-space-block-gap))));
|
|
1082
|
+
--_section-gap: var(--ara-blog-space-section-gap-lg, var(--ara-blog-space-section-gap-md, var(--ara-blog-space-section-gap-sm, var(--ara-blog-space-section-gap))));
|
|
1083
|
+
--_radius: var(--ara-blog-radius-lg, var(--ara-blog-radius-md, var(--ara-blog-radius-sm, var(--ara-blog-radius))));
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
@media (min-width: 1280px) {
|
|
1087
|
+
.aranova-blog-post {
|
|
1088
|
+
--_h1-size: var(--ara-blog-type-h1-size-xl, var(--ara-blog-type-h1-size-lg, var(--ara-blog-type-h1-size-md, var(--ara-blog-type-h1-size-sm, var(--ara-blog-type-h1-size)))));
|
|
1089
|
+
--_h2-size: var(--ara-blog-type-h2-size-xl, var(--ara-blog-type-h2-size-lg, var(--ara-blog-type-h2-size-md, var(--ara-blog-type-h2-size-sm, var(--ara-blog-type-h2-size)))));
|
|
1090
|
+
--_h3-size: var(--ara-blog-type-h3-size-xl, var(--ara-blog-type-h3-size-lg, var(--ara-blog-type-h3-size-md, var(--ara-blog-type-h3-size-sm, var(--ara-blog-type-h3-size)))));
|
|
1091
|
+
--_h4-size: var(--ara-blog-type-h4-size-xl, var(--ara-blog-type-h4-size-lg, var(--ara-blog-type-h4-size-md, var(--ara-blog-type-h4-size-sm, var(--ara-blog-type-h4-size)))));
|
|
1092
|
+
--_body-size: var(--ara-blog-type-body-size-xl, var(--ara-blog-type-body-size-lg, var(--ara-blog-type-body-size-md, var(--ara-blog-type-body-size-sm, var(--ara-blog-type-body-size)))));
|
|
1093
|
+
--_body-line-height: var(--ara-blog-type-body-line-height-xl, var(--ara-blog-type-body-line-height-lg, var(--ara-blog-type-body-line-height-md, var(--ara-blog-type-body-line-height-sm, var(--ara-blog-type-body-line-height)))));
|
|
1094
|
+
--_small-size: var(--ara-blog-type-small-size-xl, var(--ara-blog-type-small-size-lg, var(--ara-blog-type-small-size-md, var(--ara-blog-type-small-size-sm, var(--ara-blog-type-small-size)))));
|
|
1095
|
+
--_content-max-width: var(--ara-blog-space-content-max-width-xl, var(--ara-blog-space-content-max-width-lg, var(--ara-blog-space-content-max-width-md, var(--ara-blog-space-content-max-width-sm, var(--ara-blog-space-content-max-width)))));
|
|
1096
|
+
--_block-gap: var(--ara-blog-space-block-gap-xl, var(--ara-blog-space-block-gap-lg, var(--ara-blog-space-block-gap-md, var(--ara-blog-space-block-gap-sm, var(--ara-blog-space-block-gap)))));
|
|
1097
|
+
--_section-gap: var(--ara-blog-space-section-gap-xl, var(--ara-blog-space-section-gap-lg, var(--ara-blog-space-section-gap-md, var(--ara-blog-space-section-gap-sm, var(--ara-blog-space-section-gap)))));
|
|
1098
|
+
--_radius: var(--ara-blog-radius-xl, var(--ara-blog-radius-lg, var(--ara-blog-radius-md, var(--ara-blog-radius-sm, var(--ara-blog-radius)))));
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
@media (min-width: 1536px) {
|
|
1102
|
+
.aranova-blog-post {
|
|
1103
|
+
--_h1-size: var(--ara-blog-type-h1-size-2xl, var(--ara-blog-type-h1-size-xl, var(--ara-blog-type-h1-size-lg, var(--ara-blog-type-h1-size-md, var(--ara-blog-type-h1-size-sm, var(--ara-blog-type-h1-size))))));
|
|
1104
|
+
--_h2-size: var(--ara-blog-type-h2-size-2xl, var(--ara-blog-type-h2-size-xl, var(--ara-blog-type-h2-size-lg, var(--ara-blog-type-h2-size-md, var(--ara-blog-type-h2-size-sm, var(--ara-blog-type-h2-size))))));
|
|
1105
|
+
--_h3-size: var(--ara-blog-type-h3-size-2xl, var(--ara-blog-type-h3-size-xl, var(--ara-blog-type-h3-size-lg, var(--ara-blog-type-h3-size-md, var(--ara-blog-type-h3-size-sm, var(--ara-blog-type-h3-size))))));
|
|
1106
|
+
--_h4-size: var(--ara-blog-type-h4-size-2xl, var(--ara-blog-type-h4-size-xl, var(--ara-blog-type-h4-size-lg, var(--ara-blog-type-h4-size-md, var(--ara-blog-type-h4-size-sm, var(--ara-blog-type-h4-size))))));
|
|
1107
|
+
--_body-size: var(--ara-blog-type-body-size-2xl, var(--ara-blog-type-body-size-xl, var(--ara-blog-type-body-size-lg, var(--ara-blog-type-body-size-md, var(--ara-blog-type-body-size-sm, var(--ara-blog-type-body-size))))));
|
|
1108
|
+
--_body-line-height: var(--ara-blog-type-body-line-height-2xl, var(--ara-blog-type-body-line-height-xl, var(--ara-blog-type-body-line-height-lg, var(--ara-blog-type-body-line-height-md, var(--ara-blog-type-body-line-height-sm, var(--ara-blog-type-body-line-height))))));
|
|
1109
|
+
--_small-size: var(--ara-blog-type-small-size-2xl, var(--ara-blog-type-small-size-xl, var(--ara-blog-type-small-size-lg, var(--ara-blog-type-small-size-md, var(--ara-blog-type-small-size-sm, var(--ara-blog-type-small-size))))));
|
|
1110
|
+
--_content-max-width: var(--ara-blog-space-content-max-width-2xl, var(--ara-blog-space-content-max-width-xl, var(--ara-blog-space-content-max-width-lg, var(--ara-blog-space-content-max-width-md, var(--ara-blog-space-content-max-width-sm, var(--ara-blog-space-content-max-width))))));
|
|
1111
|
+
--_block-gap: var(--ara-blog-space-block-gap-2xl, var(--ara-blog-space-block-gap-xl, var(--ara-blog-space-block-gap-lg, var(--ara-blog-space-block-gap-md, var(--ara-blog-space-block-gap-sm, var(--ara-blog-space-block-gap))))));
|
|
1112
|
+
--_section-gap: var(--ara-blog-space-section-gap-2xl, var(--ara-blog-space-section-gap-xl, var(--ara-blog-space-section-gap-lg, var(--ara-blog-space-section-gap-md, var(--ara-blog-space-section-gap-sm, var(--ara-blog-space-section-gap))))));
|
|
1113
|
+
--_radius: var(--ara-blog-radius-2xl, var(--ara-blog-radius-xl, var(--ara-blog-radius-lg, var(--ara-blog-radius-md, var(--ara-blog-radius-sm, var(--ara-blog-radius))))));
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
@media (min-width: 1920px) {
|
|
1117
|
+
.aranova-blog-post {
|
|
1118
|
+
--_h1-size: var(--ara-blog-type-h1-size-3xl, var(--ara-blog-type-h1-size-2xl, var(--ara-blog-type-h1-size-xl, var(--ara-blog-type-h1-size-lg, var(--ara-blog-type-h1-size-md, var(--ara-blog-type-h1-size-sm, var(--ara-blog-type-h1-size)))))));
|
|
1119
|
+
--_h2-size: var(--ara-blog-type-h2-size-3xl, var(--ara-blog-type-h2-size-2xl, var(--ara-blog-type-h2-size-xl, var(--ara-blog-type-h2-size-lg, var(--ara-blog-type-h2-size-md, var(--ara-blog-type-h2-size-sm, var(--ara-blog-type-h2-size)))))));
|
|
1120
|
+
--_h3-size: var(--ara-blog-type-h3-size-3xl, var(--ara-blog-type-h3-size-2xl, var(--ara-blog-type-h3-size-xl, var(--ara-blog-type-h3-size-lg, var(--ara-blog-type-h3-size-md, var(--ara-blog-type-h3-size-sm, var(--ara-blog-type-h3-size)))))));
|
|
1121
|
+
--_h4-size: var(--ara-blog-type-h4-size-3xl, var(--ara-blog-type-h4-size-2xl, var(--ara-blog-type-h4-size-xl, var(--ara-blog-type-h4-size-lg, var(--ara-blog-type-h4-size-md, var(--ara-blog-type-h4-size-sm, var(--ara-blog-type-h4-size)))))));
|
|
1122
|
+
--_body-size: var(--ara-blog-type-body-size-3xl, var(--ara-blog-type-body-size-2xl, var(--ara-blog-type-body-size-xl, var(--ara-blog-type-body-size-lg, var(--ara-blog-type-body-size-md, var(--ara-blog-type-body-size-sm, var(--ara-blog-type-body-size)))))));
|
|
1123
|
+
--_body-line-height: var(--ara-blog-type-body-line-height-3xl, var(--ara-blog-type-body-line-height-2xl, var(--ara-blog-type-body-line-height-xl, var(--ara-blog-type-body-line-height-lg, var(--ara-blog-type-body-line-height-md, var(--ara-blog-type-body-line-height-sm, var(--ara-blog-type-body-line-height)))))));
|
|
1124
|
+
--_small-size: var(--ara-blog-type-small-size-3xl, var(--ara-blog-type-small-size-2xl, var(--ara-blog-type-small-size-xl, var(--ara-blog-type-small-size-lg, var(--ara-blog-type-small-size-md, var(--ara-blog-type-small-size-sm, var(--ara-blog-type-small-size)))))));
|
|
1125
|
+
--_content-max-width: var(--ara-blog-space-content-max-width-3xl, var(--ara-blog-space-content-max-width-2xl, var(--ara-blog-space-content-max-width-xl, var(--ara-blog-space-content-max-width-lg, var(--ara-blog-space-content-max-width-md, var(--ara-blog-space-content-max-width-sm, var(--ara-blog-space-content-max-width)))))));
|
|
1126
|
+
--_block-gap: var(--ara-blog-space-block-gap-3xl, var(--ara-blog-space-block-gap-2xl, var(--ara-blog-space-block-gap-xl, var(--ara-blog-space-block-gap-lg, var(--ara-blog-space-block-gap-md, var(--ara-blog-space-block-gap-sm, var(--ara-blog-space-block-gap)))))));
|
|
1127
|
+
--_section-gap: var(--ara-blog-space-section-gap-3xl, var(--ara-blog-space-section-gap-2xl, var(--ara-blog-space-section-gap-xl, var(--ara-blog-space-section-gap-lg, var(--ara-blog-space-section-gap-md, var(--ara-blog-space-section-gap-sm, var(--ara-blog-space-section-gap)))))));
|
|
1128
|
+
--_radius: var(--ara-blog-radius-3xl, var(--ara-blog-radius-2xl, var(--ara-blog-radius-xl, var(--ara-blog-radius-lg, var(--ara-blog-radius-md, var(--ara-blog-radius-sm, var(--ara-blog-radius)))))));
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.aranova-blog-post {
|
|
1133
|
+
/* Internal spacing scale, derived from the responsive block gap. */
|
|
1134
|
+
--_flow: var(--_block-gap);
|
|
1135
|
+
--_flow-sm: calc(var(--_block-gap) * 0.5);
|
|
1136
|
+
--_flow-lg: calc(var(--_block-gap) * 1.75);
|
|
1137
|
+
--_flow-section: var(--_section-gap);
|
|
1138
|
+
/* Optional heading color token \u2014 themes that don't set it fall back to text. */
|
|
1139
|
+
--_heading-color: var(--ara-blog-color-heading, var(--ara-blog-color-text));
|
|
1140
|
+
|
|
1141
|
+
max-width: var(--_content-max-width);
|
|
1142
|
+
margin-inline: auto;
|
|
1143
|
+
background: var(--ara-blog-color-background);
|
|
1144
|
+
color: var(--ara-blog-color-text);
|
|
1145
|
+
font-family: var(--ara-blog-type-body-family);
|
|
1146
|
+
font-weight: var(--ara-blog-type-body-weight);
|
|
1147
|
+
font-size: var(--_body-size);
|
|
1148
|
+
line-height: var(--_body-line-height);
|
|
1149
|
+
overflow-wrap: break-word;
|
|
1150
|
+
}
|
|
1151
|
+
@media (max-width: 639.99px) {
|
|
1152
|
+
.aranova-blog-post {
|
|
1153
|
+
padding-inline: 1.25rem;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/* --- Headings ------------------------------------------------------------ */
|
|
1158
|
+
.aranova-blog-post :where(h1, h2, h3, h4, h5, h6) {
|
|
1159
|
+
font-family: var(--ara-blog-type-heading-family);
|
|
1160
|
+
font-weight: var(--ara-blog-type-heading-weight);
|
|
1161
|
+
line-height: 1.25;
|
|
1162
|
+
margin-block: 0;
|
|
1163
|
+
color: var(--_heading-color);
|
|
1164
|
+
scroll-margin-top: 5rem;
|
|
1165
|
+
}
|
|
1166
|
+
.aranova-blog-post :where(h1, h2, h3) {
|
|
1167
|
+
/* \`pretty\` fills each line greedily (only the LAST line is massaged to avoid
|
|
1168
|
+
* an orphan word) \u2014 so a heading uses the full measure. \`balance\` was leaving
|
|
1169
|
+
* short first lines. */
|
|
1170
|
+
text-wrap: pretty;
|
|
1171
|
+
}
|
|
1172
|
+
.aranova-blog-post :where(h1) {
|
|
1173
|
+
font-size: var(--_h1-size);
|
|
1174
|
+
letter-spacing: var(--ara-blog-type-heading-letter-spacing);
|
|
1175
|
+
}
|
|
1176
|
+
.aranova-blog-post :where(h2) {
|
|
1177
|
+
font-size: var(--_h2-size);
|
|
1178
|
+
letter-spacing: var(--ara-blog-type-heading-letter-spacing);
|
|
1179
|
+
}
|
|
1180
|
+
.aranova-blog-post :where(h3) {
|
|
1181
|
+
font-size: var(--_h3-size);
|
|
1182
|
+
letter-spacing: calc(var(--ara-blog-type-heading-letter-spacing) * 0.5);
|
|
1183
|
+
}
|
|
1184
|
+
.aranova-blog-post :where(h4) {
|
|
1185
|
+
font-size: var(--_h4-size);
|
|
1186
|
+
font-weight: 600;
|
|
1187
|
+
letter-spacing: calc(var(--ara-blog-type-heading-letter-spacing) * 0.5);
|
|
1188
|
+
}
|
|
1189
|
+
.aranova-blog-post :where(h5) {
|
|
1190
|
+
font-size: var(--_body-size);
|
|
1191
|
+
font-weight: 600;
|
|
1192
|
+
}
|
|
1193
|
+
.aranova-blog-post :where(h6) {
|
|
1194
|
+
font-size: var(--_small-size);
|
|
1195
|
+
font-weight: 600;
|
|
1196
|
+
text-transform: uppercase;
|
|
1197
|
+
letter-spacing: 0.05em;
|
|
1198
|
+
color: var(--ara-blog-color-text-muted);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/* --- Text ---------------------------------------------------------------- */
|
|
1202
|
+
.aranova-blog-post :where(p) {
|
|
1203
|
+
margin-block: 0;
|
|
1204
|
+
}
|
|
1205
|
+
/* Lead paragraph: the one directly after the post title. */
|
|
1206
|
+
.aranova-blog-post :where(h1) + :where(p) {
|
|
1207
|
+
font-size: 1.15em;
|
|
1208
|
+
color: color-mix(in srgb, var(--ara-blog-color-text) 72%, var(--ara-blog-color-text-muted));
|
|
1209
|
+
}
|
|
1210
|
+
.aranova-blog-post :where(a) {
|
|
1211
|
+
color: var(--ara-blog-color-link);
|
|
1212
|
+
text-decoration: underline;
|
|
1213
|
+
text-underline-offset: 0.15em;
|
|
1214
|
+
}
|
|
1215
|
+
.aranova-blog-post :where(a):focus-visible {
|
|
1216
|
+
outline: 2px solid var(--ara-blog-color-link);
|
|
1217
|
+
outline-offset: 2px;
|
|
1218
|
+
border-radius: 2px;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/* --- Lists ---------------------------------------------------------------- */
|
|
1222
|
+
.aranova-blog-post :where(ul, ol) {
|
|
1223
|
+
margin-block: 0;
|
|
1224
|
+
padding-inline-start: 1.5em;
|
|
1225
|
+
}
|
|
1226
|
+
.aranova-blog-post :where(ul) {
|
|
1227
|
+
list-style-type: disc;
|
|
1228
|
+
}
|
|
1229
|
+
.aranova-blog-post :where(ul ul) {
|
|
1230
|
+
list-style-type: circle;
|
|
1231
|
+
}
|
|
1232
|
+
.aranova-blog-post :where(ul ul ul) {
|
|
1233
|
+
list-style-type: square;
|
|
1234
|
+
}
|
|
1235
|
+
.aranova-blog-post :where(ol) {
|
|
1236
|
+
list-style-type: decimal;
|
|
1237
|
+
}
|
|
1238
|
+
.aranova-blog-post :where(ol ol) {
|
|
1239
|
+
list-style-type: lower-alpha;
|
|
1240
|
+
}
|
|
1241
|
+
.aranova-blog-post :where(ul) > li::marker {
|
|
1242
|
+
color: var(--ara-blog-color-text-muted);
|
|
1243
|
+
}
|
|
1244
|
+
.aranova-blog-post :where(ol) > li::marker {
|
|
1245
|
+
font-weight: 600;
|
|
1246
|
+
color: var(--_heading-color);
|
|
1247
|
+
}
|
|
1248
|
+
.aranova-blog-post :where(li + li) {
|
|
1249
|
+
margin-block-start: 0.35em;
|
|
1250
|
+
}
|
|
1251
|
+
.aranova-blog-post :where(li > ul, li > ol) {
|
|
1252
|
+
margin-block-start: 0.35em;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/* --- Blockquote ------------------------------------------------------------ */
|
|
1256
|
+
.aranova-blog-post :where(blockquote) {
|
|
1257
|
+
margin: 0;
|
|
1258
|
+
padding-block: 0.25em;
|
|
1259
|
+
padding-inline: 1.25em 0.5em;
|
|
1260
|
+
border-inline-start: 3px solid var(--ara-blog-color-primary);
|
|
1261
|
+
color: var(--ara-blog-color-text-muted);
|
|
1262
|
+
font-style: italic;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/* --- Code ------------------------------------------------------------------ */
|
|
1266
|
+
.aranova-blog-post :where(code) {
|
|
1267
|
+
background: var(--ara-blog-color-code-background);
|
|
1268
|
+
border-radius: calc(var(--_radius) / 2);
|
|
1269
|
+
padding: 0.15em 0.35em;
|
|
1270
|
+
font-size: 0.9em;
|
|
1271
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
1272
|
+
}
|
|
1273
|
+
.aranova-blog-post :where(pre) {
|
|
1274
|
+
margin: 0;
|
|
1275
|
+
background: var(--ara-blog-color-code-background);
|
|
1276
|
+
border: 1px solid var(--ara-blog-color-border);
|
|
1277
|
+
border-radius: var(--_radius);
|
|
1278
|
+
padding: 1em;
|
|
1279
|
+
overflow-x: auto;
|
|
1280
|
+
line-height: 1.5;
|
|
1281
|
+
}
|
|
1282
|
+
.aranova-blog-post :where(pre code) {
|
|
1283
|
+
background: none;
|
|
1284
|
+
border-radius: 0;
|
|
1285
|
+
padding: 0;
|
|
1286
|
+
font-size: var(--_small-size);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/* --- Media ------------------------------------------------------------------ */
|
|
1290
|
+
.aranova-blog-post :where(img) {
|
|
1291
|
+
max-width: 100%;
|
|
1292
|
+
height: auto;
|
|
1293
|
+
display: block;
|
|
1294
|
+
border: 1px solid var(--ara-blog-color-border);
|
|
1295
|
+
border-radius: var(--_radius);
|
|
1296
|
+
}
|
|
1297
|
+
.aranova-blog-post :where(figure) {
|
|
1298
|
+
margin: 0;
|
|
1299
|
+
}
|
|
1300
|
+
.aranova-blog-post :where(figcaption) {
|
|
1301
|
+
margin-block-start: 0.5em;
|
|
1302
|
+
font-size: var(--_small-size);
|
|
1303
|
+
color: var(--ara-blog-color-text-muted);
|
|
1304
|
+
text-align: var(--ara-blog-type-caption-align, center);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/* --- Tables ------------------------------------------------------------------ */
|
|
1308
|
+
.aranova-blog-post .aranova-blog-table-wrap {
|
|
1309
|
+
overflow-x: auto;
|
|
1310
|
+
border: 1px solid var(--ara-blog-color-border);
|
|
1311
|
+
border-radius: var(--_radius);
|
|
1312
|
+
}
|
|
1313
|
+
.aranova-blog-post :where(table) {
|
|
1314
|
+
border-collapse: collapse;
|
|
1315
|
+
width: 100%;
|
|
1316
|
+
font-size: var(--_small-size);
|
|
1317
|
+
}
|
|
1318
|
+
.aranova-blog-post :where(th, td) {
|
|
1319
|
+
border: 0;
|
|
1320
|
+
border-block-end: 1px solid var(--ara-blog-color-border);
|
|
1321
|
+
padding: 0.6em 0.85em;
|
|
1322
|
+
text-align: left;
|
|
1323
|
+
}
|
|
1324
|
+
.aranova-blog-post :where(th) {
|
|
1325
|
+
background: var(--ara-blog-color-surface);
|
|
1326
|
+
border-block-end: 2px solid var(--ara-blog-color-border);
|
|
1327
|
+
font-weight: 600;
|
|
1328
|
+
}
|
|
1329
|
+
.aranova-blog-post :where(tbody tr:nth-child(even) td) {
|
|
1330
|
+
background: color-mix(
|
|
1331
|
+
in srgb,
|
|
1332
|
+
var(--ara-blog-color-surface) 55%,
|
|
1333
|
+
var(--ara-blog-color-background)
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
.aranova-blog-post :where(tbody tr:last-child) :where(th, td) {
|
|
1337
|
+
border-block-end: 0;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/* --- Section break ------------------------------------------------------------ */
|
|
1341
|
+
.aranova-blog-post :where(hr) {
|
|
1342
|
+
border: none;
|
|
1343
|
+
border-top: 2px solid var(--ara-blog-color-border);
|
|
1344
|
+
width: 4rem;
|
|
1345
|
+
margin-inline: auto;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/* --- Callout ------------------------------------------------------------------ */
|
|
1349
|
+
.aranova-blog-post .aranova-blog-callout {
|
|
1350
|
+
background: var(--ara-blog-color-surface);
|
|
1351
|
+
border: 1px solid var(--ara-blog-color-border);
|
|
1352
|
+
border-inline-start: 4px solid var(--ara-blog-color-primary);
|
|
1353
|
+
border-radius: var(--_radius);
|
|
1354
|
+
padding: 0.85em 1.1em;
|
|
1355
|
+
}
|
|
1356
|
+
.aranova-blog-post .aranova-blog-callout > * + * {
|
|
1357
|
+
margin-block-start: var(--_flow-sm);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/* --- CTA ------------------------------------------------------------------ */
|
|
1361
|
+
.aranova-blog-post .aranova-blog-cta {
|
|
1362
|
+
display: inline-block;
|
|
1363
|
+
background: var(--ara-blog-color-primary);
|
|
1364
|
+
color: var(--ara-blog-color-primary-foreground);
|
|
1365
|
+
border-radius: var(--_radius);
|
|
1366
|
+
padding: 0.6em 1.2em;
|
|
1367
|
+
font-weight: 600;
|
|
1368
|
+
text-decoration: none;
|
|
1369
|
+
transition:
|
|
1370
|
+
filter 150ms ease,
|
|
1371
|
+
transform 150ms ease;
|
|
1372
|
+
}
|
|
1373
|
+
.aranova-blog-post .aranova-blog-cta:hover {
|
|
1374
|
+
filter: brightness(1.08);
|
|
1375
|
+
transform: translateY(-1px);
|
|
1376
|
+
}
|
|
1377
|
+
.aranova-blog-post .aranova-blog-cta:active {
|
|
1378
|
+
filter: none;
|
|
1379
|
+
transform: none;
|
|
1380
|
+
}
|
|
1381
|
+
.aranova-blog-post .aranova-blog-cta:focus-visible {
|
|
1382
|
+
outline: 2px solid var(--ara-blog-color-primary);
|
|
1383
|
+
outline-offset: 2px;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/* --- FAQ (details/summary accordion) ------------------------------------------ */
|
|
1387
|
+
.aranova-blog-post .aranova-blog-faq-item {
|
|
1388
|
+
border-block-start: 1px solid var(--ara-blog-color-border);
|
|
1389
|
+
padding-block: calc(var(--_flow) * 0.75);
|
|
1390
|
+
}
|
|
1391
|
+
.aranova-blog-post .aranova-blog-faq-question {
|
|
1392
|
+
cursor: pointer;
|
|
1393
|
+
list-style: none;
|
|
1394
|
+
position: relative;
|
|
1395
|
+
padding-inline-end: 1.75em;
|
|
1396
|
+
}
|
|
1397
|
+
.aranova-blog-post .aranova-blog-faq-question::-webkit-details-marker {
|
|
1398
|
+
display: none;
|
|
1399
|
+
}
|
|
1400
|
+
/* Rotating chevron (border trick \u2014 colors via theme vars only). */
|
|
1401
|
+
.aranova-blog-post .aranova-blog-faq-question::after {
|
|
1402
|
+
content: "";
|
|
1403
|
+
position: absolute;
|
|
1404
|
+
inset-block-start: 50%;
|
|
1405
|
+
inset-inline-end: 0.35em;
|
|
1406
|
+
width: 0.45em;
|
|
1407
|
+
height: 0.45em;
|
|
1408
|
+
border-inline-end: 2px solid var(--ara-blog-color-text-muted);
|
|
1409
|
+
border-block-end: 2px solid var(--ara-blog-color-text-muted);
|
|
1410
|
+
transform: translateY(-70%) rotate(45deg);
|
|
1411
|
+
transition: transform 150ms ease;
|
|
1412
|
+
}
|
|
1413
|
+
.aranova-blog-post .aranova-blog-faq-item[open] > .aranova-blog-faq-question::after {
|
|
1414
|
+
transform: translateY(-30%) rotate(225deg);
|
|
1415
|
+
}
|
|
1416
|
+
.aranova-blog-post .aranova-blog-faq-question:focus-visible {
|
|
1417
|
+
outline: 2px solid var(--ara-blog-color-link);
|
|
1418
|
+
outline-offset: 2px;
|
|
1419
|
+
border-radius: 2px;
|
|
1420
|
+
}
|
|
1421
|
+
.aranova-blog-post .aranova-blog-faq-answer {
|
|
1422
|
+
padding-block-start: var(--_flow-sm);
|
|
1423
|
+
}
|
|
1424
|
+
.aranova-blog-post .aranova-blog-faq-answer > * + * {
|
|
1425
|
+
margin-block-start: var(--_flow-sm);
|
|
1426
|
+
}
|
|
1427
|
+
/* Progressive open/close animation where the platform supports it. */
|
|
1428
|
+
@supports (interpolate-size: allow-keywords) {
|
|
1429
|
+
.aranova-blog-post .aranova-blog-faq-item {
|
|
1430
|
+
interpolate-size: allow-keywords;
|
|
1431
|
+
}
|
|
1432
|
+
.aranova-blog-post .aranova-blog-faq-item::details-content {
|
|
1433
|
+
block-size: 0;
|
|
1434
|
+
overflow-y: clip;
|
|
1435
|
+
transition:
|
|
1436
|
+
block-size 200ms ease,
|
|
1437
|
+
content-visibility 200ms allow-discrete;
|
|
1438
|
+
}
|
|
1439
|
+
.aranova-blog-post .aranova-blog-faq-item[open]::details-content {
|
|
1440
|
+
block-size: auto;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/* --- Key takeaways ------------------------------------------------------------ */
|
|
1445
|
+
.aranova-blog-post .aranova-blog-key-takeaways {
|
|
1446
|
+
background: var(--ara-blog-color-surface);
|
|
1447
|
+
border: 1px solid var(--ara-blog-color-border);
|
|
1448
|
+
border-radius: var(--_radius);
|
|
1449
|
+
padding: 1em 1.25em;
|
|
1450
|
+
}
|
|
1451
|
+
.aranova-blog-post .aranova-blog-key-takeaways :where(ul) {
|
|
1452
|
+
margin-block: 0;
|
|
1453
|
+
}
|
|
1454
|
+
.aranova-blog-post .aranova-blog-key-takeaways :where(ul) > li::marker {
|
|
1455
|
+
color: var(--ara-blog-color-primary);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/* --- Article layout (content column + optional aside rail) -----------------------
|
|
1459
|
+
* \`.aranova-blog-layout\` is a plain block on narrow viewports (the aside, when
|
|
1460
|
+
* present, stacks after the content). On wide viewports a grid opens ONLY when
|
|
1461
|
+
* an aside exists, and the article widens by exactly the rail + gap so the
|
|
1462
|
+
* content column keeps its \`content_max_width\`. */
|
|
1463
|
+
.aranova-blog-post .aranova-blog-layout {
|
|
1464
|
+
/* Flex column (not block) so the aside can be VISUALLY hoisted above the
|
|
1465
|
+
* content on narrow viewports while the content stays first in DOM
|
|
1466
|
+
* (reading order / SEO). */
|
|
1467
|
+
display: flex;
|
|
1468
|
+
flex-direction: column;
|
|
1469
|
+
}
|
|
1470
|
+
.aranova-blog-post .aranova-blog-layout > .aranova-blog-aside {
|
|
1471
|
+
order: -1;
|
|
1472
|
+
margin-block-end: var(--_flow-lg);
|
|
1473
|
+
}
|
|
1474
|
+
@media (min-width: 1100px) {
|
|
1475
|
+
.aranova-blog-post:has(.aranova-blog-aside) {
|
|
1476
|
+
max-width: calc(var(--_content-max-width) + 240px + 3rem);
|
|
1477
|
+
}
|
|
1478
|
+
.aranova-blog-post .aranova-blog-layout:has(.aranova-blog-aside) {
|
|
1479
|
+
display: grid;
|
|
1480
|
+
grid-template-columns: minmax(0, 1fr) 240px;
|
|
1481
|
+
column-gap: 3rem;
|
|
1482
|
+
align-items: start;
|
|
1483
|
+
}
|
|
1484
|
+
.aranova-blog-post .aranova-blog-layout:has(.aranova-blog-aside) > .aranova-blog-aside {
|
|
1485
|
+
/* Reset the mobile hoist: grid auto-placement honors \`order\`, so -1 would
|
|
1486
|
+
* drop the aside into the FIRST column. */
|
|
1487
|
+
order: 0;
|
|
1488
|
+
margin-block-end: 0;
|
|
1489
|
+
position: sticky;
|
|
1490
|
+
top: 5rem;
|
|
1491
|
+
align-self: start;
|
|
1492
|
+
max-height: calc(100vh - 6rem);
|
|
1493
|
+
overflow: auto;
|
|
1494
|
+
}
|
|
1495
|
+
/* Rail on the LEFT: flip the column order (content DOM stays first for
|
|
1496
|
+
* reading order / SEO; only the visual columns swap). */
|
|
1497
|
+
.aranova-blog-post .aranova-blog-layout[data-toc-side="left"]:has(.aranova-blog-aside) {
|
|
1498
|
+
grid-template-columns: 240px minmax(0, 1fr);
|
|
1499
|
+
}
|
|
1500
|
+
/* Horizontal collapse, CLOSED: the rail column shrinks to the compact toggle
|
|
1501
|
+
* (auto) and the article reflows into the reclaimed space. */
|
|
1502
|
+
.aranova-blog-post
|
|
1503
|
+
.aranova-blog-layout:has(
|
|
1504
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"]:not([data-open="true"])
|
|
1505
|
+
) {
|
|
1506
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
1507
|
+
}
|
|
1508
|
+
.aranova-blog-post
|
|
1509
|
+
.aranova-blog-layout[data-toc-side="left"]:has(
|
|
1510
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"]:not([data-open="true"])
|
|
1511
|
+
) {
|
|
1512
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1513
|
+
}
|
|
1514
|
+
.aranova-blog-post
|
|
1515
|
+
.aranova-blog-layout[data-toc-side="left"]:has(.aranova-blog-aside)
|
|
1516
|
+
> .aranova-blog-content {
|
|
1517
|
+
order: 2;
|
|
1518
|
+
}
|
|
1519
|
+
.aranova-blog-post
|
|
1520
|
+
.aranova-blog-layout[data-toc-side="left"]:has(.aranova-blog-aside)
|
|
1521
|
+
> .aranova-blog-aside {
|
|
1522
|
+
order: 1;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/* --- Byline / meta row (chrome) --------------------------------------------------- */
|
|
1527
|
+
.aranova-blog-post .aranova-blog-meta {
|
|
1528
|
+
display: flex;
|
|
1529
|
+
flex-wrap: wrap;
|
|
1530
|
+
align-items: center;
|
|
1531
|
+
gap: 0.35em 0.5em;
|
|
1532
|
+
font-size: var(--_small-size);
|
|
1533
|
+
color: var(--ara-blog-color-text-muted);
|
|
1534
|
+
padding-block-end: var(--_flow);
|
|
1535
|
+
border-block-end: 1px solid var(--ara-blog-color-border);
|
|
1536
|
+
}
|
|
1537
|
+
.aranova-blog-post .aranova-blog-meta-item {
|
|
1538
|
+
display: inline-flex;
|
|
1539
|
+
align-items: baseline;
|
|
1540
|
+
gap: 0.5em;
|
|
1541
|
+
white-space: nowrap;
|
|
1542
|
+
}
|
|
1543
|
+
.aranova-blog-post .aranova-blog-meta-tags {
|
|
1544
|
+
display: inline-flex;
|
|
1545
|
+
flex-wrap: wrap;
|
|
1546
|
+
gap: 0.35em;
|
|
1547
|
+
margin-inline-start: auto;
|
|
1548
|
+
}
|
|
1549
|
+
.aranova-blog-post .aranova-blog-meta-tag {
|
|
1550
|
+
background: var(--ara-blog-color-surface);
|
|
1551
|
+
border: 1px solid var(--ara-blog-color-border);
|
|
1552
|
+
border-radius: 999px;
|
|
1553
|
+
padding: 0.1em 0.7em;
|
|
1554
|
+
white-space: nowrap;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/* --- Breadcrumbs (chrome) ---------------------------------------------------------- */
|
|
1558
|
+
.aranova-blog-post .aranova-blog-breadcrumbs {
|
|
1559
|
+
font-size: var(--_small-size);
|
|
1560
|
+
color: var(--ara-blog-color-text-muted);
|
|
1561
|
+
}
|
|
1562
|
+
.aranova-blog-post .aranova-blog-breadcrumbs ol {
|
|
1563
|
+
list-style: none;
|
|
1564
|
+
margin: 0;
|
|
1565
|
+
padding: 0;
|
|
1566
|
+
display: flex;
|
|
1567
|
+
flex-wrap: wrap;
|
|
1568
|
+
align-items: baseline;
|
|
1569
|
+
gap: 0.2em 0.5em;
|
|
1570
|
+
}
|
|
1571
|
+
.aranova-blog-post .aranova-blog-breadcrumbs li {
|
|
1572
|
+
display: inline-flex;
|
|
1573
|
+
align-items: baseline;
|
|
1574
|
+
gap: 0.5em;
|
|
1575
|
+
}
|
|
1576
|
+
.aranova-blog-post .aranova-blog-breadcrumbs a {
|
|
1577
|
+
color: var(--ara-blog-color-text-muted);
|
|
1578
|
+
text-decoration: none;
|
|
1579
|
+
}
|
|
1580
|
+
.aranova-blog-post .aranova-blog-breadcrumbs a:hover {
|
|
1581
|
+
color: var(--ara-blog-color-link);
|
|
1582
|
+
text-decoration: underline;
|
|
1583
|
+
text-underline-offset: 0.15em;
|
|
1584
|
+
}
|
|
1585
|
+
.aranova-blog-post .aranova-blog-breadcrumbs [aria-current="page"] {
|
|
1586
|
+
color: var(--ara-blog-color-text);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
/* --- Table of contents (chrome) -----------------------------------------------------
|
|
1590
|
+
* Zero-JS shell: a native details/summary disclosure. Chevron follows the FAQ
|
|
1591
|
+
* accordion's border-trick pattern (theme vars only, no raw colors). */
|
|
1592
|
+
.aranova-blog-post .aranova-blog-toc {
|
|
1593
|
+
font-size: var(--_small-size);
|
|
1594
|
+
background: var(--ara-blog-color-surface);
|
|
1595
|
+
border: 1px solid var(--ara-blog-color-border);
|
|
1596
|
+
border-radius: var(--_radius);
|
|
1597
|
+
padding: 0.85em 1.1em;
|
|
1598
|
+
}
|
|
1599
|
+
.aranova-blog-post .aranova-blog-toc-title {
|
|
1600
|
+
cursor: pointer;
|
|
1601
|
+
list-style: none;
|
|
1602
|
+
position: relative;
|
|
1603
|
+
padding-inline-end: 1.75em;
|
|
1604
|
+
font-weight: 600;
|
|
1605
|
+
color: var(--_heading-color);
|
|
1606
|
+
}
|
|
1607
|
+
.aranova-blog-post .aranova-blog-toc-title::-webkit-details-marker {
|
|
1608
|
+
display: none;
|
|
1609
|
+
}
|
|
1610
|
+
.aranova-blog-post .aranova-blog-toc-title::after {
|
|
1611
|
+
content: "";
|
|
1612
|
+
position: absolute;
|
|
1613
|
+
inset-block-start: 50%;
|
|
1614
|
+
inset-inline-end: 0.35em;
|
|
1615
|
+
width: 0.45em;
|
|
1616
|
+
height: 0.45em;
|
|
1617
|
+
border-inline-end: 2px solid var(--ara-blog-color-text-muted);
|
|
1618
|
+
border-block-end: 2px solid var(--ara-blog-color-text-muted);
|
|
1619
|
+
transform: translateY(-70%) rotate(45deg);
|
|
1620
|
+
transition: transform 150ms ease;
|
|
1621
|
+
}
|
|
1622
|
+
.aranova-blog-post .aranova-blog-toc[open] > .aranova-blog-toc-title::after {
|
|
1623
|
+
transform: translateY(-30%) rotate(225deg);
|
|
1624
|
+
}
|
|
1625
|
+
.aranova-blog-post .aranova-blog-toc-title:focus-visible {
|
|
1626
|
+
outline: 2px solid var(--ara-blog-color-link);
|
|
1627
|
+
outline-offset: 2px;
|
|
1628
|
+
border-radius: 2px;
|
|
1629
|
+
}
|
|
1630
|
+
.aranova-blog-post .aranova-blog-toc ol {
|
|
1631
|
+
list-style: none;
|
|
1632
|
+
margin: 0.75em 0 0;
|
|
1633
|
+
padding: 0;
|
|
1634
|
+
}
|
|
1635
|
+
.aranova-blog-post .aranova-blog-toc li {
|
|
1636
|
+
margin: 0;
|
|
1637
|
+
}
|
|
1638
|
+
.aranova-blog-post .aranova-blog-toc li + li {
|
|
1639
|
+
margin-block-start: 0.3em;
|
|
1640
|
+
}
|
|
1641
|
+
.aranova-blog-post .aranova-blog-toc li[data-depth="3"] {
|
|
1642
|
+
padding-inline-start: 0.9em;
|
|
1643
|
+
font-size: 0.94em;
|
|
1644
|
+
}
|
|
1645
|
+
.aranova-blog-post .aranova-blog-toc a {
|
|
1646
|
+
display: -webkit-box;
|
|
1647
|
+
-webkit-box-orient: vertical;
|
|
1648
|
+
-webkit-line-clamp: 2;
|
|
1649
|
+
overflow: hidden;
|
|
1650
|
+
line-height: 1.35;
|
|
1651
|
+
color: var(--ara-blog-color-text-muted);
|
|
1652
|
+
text-decoration: none;
|
|
1653
|
+
}
|
|
1654
|
+
.aranova-blog-post .aranova-blog-toc a:hover {
|
|
1655
|
+
color: var(--ara-blog-color-link);
|
|
1656
|
+
text-decoration: underline;
|
|
1657
|
+
text-underline-offset: 0.15em;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/* --- Table of contents (client island) ------------------------------------------
|
|
1661
|
+
* Animated companion to the zero-JS shell above: same box, but a real <button>
|
|
1662
|
+
* header (aria-expanded) and a grid-template-rows 0fr\u21941fr collapse. The global
|
|
1663
|
+
* reduced-motion block at the end of this sheet disables the transition. */
|
|
1664
|
+
.aranova-blog-post .aranova-blog-toc-panel {
|
|
1665
|
+
font-size: var(--_small-size);
|
|
1666
|
+
background: var(--ara-blog-color-surface);
|
|
1667
|
+
border: 1px solid var(--ara-blog-color-border);
|
|
1668
|
+
border-radius: var(--_radius);
|
|
1669
|
+
padding: 0.85em 1.1em;
|
|
1670
|
+
}
|
|
1671
|
+
.aranova-blog-post .aranova-blog-toc-panel-title {
|
|
1672
|
+
/* Button reset \u2014 host sites may or may not run Tailwind preflight. */
|
|
1673
|
+
appearance: none;
|
|
1674
|
+
background: none;
|
|
1675
|
+
border: 0;
|
|
1676
|
+
margin: 0;
|
|
1677
|
+
padding: 0;
|
|
1678
|
+
padding-inline-end: 1.75em;
|
|
1679
|
+
width: 100%;
|
|
1680
|
+
text-align: start;
|
|
1681
|
+
font: inherit;
|
|
1682
|
+
cursor: pointer;
|
|
1683
|
+
position: relative;
|
|
1684
|
+
font-weight: 600;
|
|
1685
|
+
color: var(--_heading-color);
|
|
1686
|
+
}
|
|
1687
|
+
/* Rotating chevron (border trick \u2014 same pattern as the FAQ / zero-JS TOC). */
|
|
1688
|
+
.aranova-blog-post .aranova-blog-toc-panel-title::after {
|
|
1689
|
+
content: "";
|
|
1690
|
+
position: absolute;
|
|
1691
|
+
inset-block-start: 50%;
|
|
1692
|
+
inset-inline-end: 0.35em;
|
|
1693
|
+
width: 0.45em;
|
|
1694
|
+
height: 0.45em;
|
|
1695
|
+
border-inline-end: 2px solid var(--ara-blog-color-text-muted);
|
|
1696
|
+
border-block-end: 2px solid var(--ara-blog-color-text-muted);
|
|
1697
|
+
transform: translateY(-70%) rotate(45deg);
|
|
1698
|
+
transition: transform 250ms ease;
|
|
1699
|
+
}
|
|
1700
|
+
.aranova-blog-post
|
|
1701
|
+
.aranova-blog-toc-panel[data-open="true"]
|
|
1702
|
+
> .aranova-blog-toc-panel-title::after {
|
|
1703
|
+
transform: translateY(-30%) rotate(225deg);
|
|
1704
|
+
}
|
|
1705
|
+
.aranova-blog-post .aranova-blog-toc-panel-title:focus-visible {
|
|
1706
|
+
outline: 2px solid var(--ara-blog-color-link);
|
|
1707
|
+
outline-offset: 2px;
|
|
1708
|
+
border-radius: 2px;
|
|
1709
|
+
}
|
|
1710
|
+
/* Collapsible body: the grid row animates 0fr\u21941fr; the clip wrapper hides overflow. */
|
|
1711
|
+
.aranova-blog-post .aranova-blog-toc-panel-body {
|
|
1712
|
+
display: grid;
|
|
1713
|
+
grid-template-rows: 0fr;
|
|
1714
|
+
transition: grid-template-rows 250ms ease;
|
|
1715
|
+
}
|
|
1716
|
+
.aranova-blog-post .aranova-blog-toc-panel[data-open="true"] > .aranova-blog-toc-panel-body {
|
|
1717
|
+
grid-template-rows: 1fr;
|
|
1718
|
+
}
|
|
1719
|
+
/* Horizontal collapse: a clean binary toggle along the inline axis. Open = the
|
|
1720
|
+
* normal rail; closed = the whole panel is a compact chevron chip and the rail
|
|
1721
|
+
* grid column reclaims the space (via \`:has()\` on the layout, below). No width
|
|
1722
|
+
* animation: grid \`auto\` tracks can't interpolate, and a sliding clip either
|
|
1723
|
+
* overflows the sticky aside (horizontal scrollbar) or reflows the entries \u2014
|
|
1724
|
+
* instant + correct beats janky. */
|
|
1725
|
+
.aranova-blog-post
|
|
1726
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"]
|
|
1727
|
+
> .aranova-blog-toc-panel-body {
|
|
1728
|
+
display: block;
|
|
1729
|
+
grid-template-rows: none;
|
|
1730
|
+
}
|
|
1731
|
+
.aranova-blog-post
|
|
1732
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"]:not([data-open="true"])
|
|
1733
|
+
> .aranova-blog-toc-panel-body {
|
|
1734
|
+
display: none;
|
|
1735
|
+
}
|
|
1736
|
+
/* Closed panel compacts to a small rounded chevron chip: the label is clipped
|
|
1737
|
+
* out of a square toggle box and the panel hugs it (the rail's \`auto\` grid
|
|
1738
|
+
* column then shrinks to the chip). */
|
|
1739
|
+
.aranova-blog-post .aranova-blog-toc-panel[data-collapse="horizontal"]:not([data-open="true"]) {
|
|
1740
|
+
width: fit-content;
|
|
1741
|
+
padding: 0.6em;
|
|
1742
|
+
}
|
|
1743
|
+
.aranova-blog-post
|
|
1744
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"]:not([data-open="true"])
|
|
1745
|
+
> .aranova-blog-toc-panel-title {
|
|
1746
|
+
display: block;
|
|
1747
|
+
width: 1.4em;
|
|
1748
|
+
height: 1.4em;
|
|
1749
|
+
overflow: hidden;
|
|
1750
|
+
text-indent: 150%;
|
|
1751
|
+
white-space: nowrap;
|
|
1752
|
+
padding-inline-end: 0;
|
|
1753
|
+
}
|
|
1754
|
+
/* Horizontal chevron points along the fold axis (\u2039/\u203A), flipping on open/close
|
|
1755
|
+
* per side, instead of the vertical \u2304/\u2303. */
|
|
1756
|
+
.aranova-blog-post
|
|
1757
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"]
|
|
1758
|
+
> .aranova-blog-toc-panel-title::after {
|
|
1759
|
+
inset-inline: auto 0.5em;
|
|
1760
|
+
}
|
|
1761
|
+
.aranova-blog-post
|
|
1762
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"]:not([data-open="true"])
|
|
1763
|
+
> .aranova-blog-toc-panel-title::after {
|
|
1764
|
+
inset-inline: 0;
|
|
1765
|
+
margin-inline: auto;
|
|
1766
|
+
}
|
|
1767
|
+
/* Left rail: open folds left (\u2039 = rotate 135deg), closed expands right (\u203A = -45deg). */
|
|
1768
|
+
.aranova-blog-post
|
|
1769
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"][data-side="left"][data-open="true"]
|
|
1770
|
+
> .aranova-blog-toc-panel-title::after {
|
|
1771
|
+
transform: translateY(-30%) rotate(135deg);
|
|
1772
|
+
}
|
|
1773
|
+
.aranova-blog-post
|
|
1774
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"][data-side="left"]:not([data-open="true"])
|
|
1775
|
+
> .aranova-blog-toc-panel-title::after {
|
|
1776
|
+
transform: translateY(-70%) rotate(-45deg);
|
|
1777
|
+
}
|
|
1778
|
+
/* Right rail: mirror \u2014 open folds right (\u203A), closed expands left (\u2039). */
|
|
1779
|
+
.aranova-blog-post
|
|
1780
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"][data-side="right"][data-open="true"]
|
|
1781
|
+
> .aranova-blog-toc-panel-title::after {
|
|
1782
|
+
transform: translateY(-70%) rotate(-45deg);
|
|
1783
|
+
}
|
|
1784
|
+
.aranova-blog-post
|
|
1785
|
+
.aranova-blog-toc-panel[data-collapse="horizontal"][data-side="right"]:not([data-open="true"])
|
|
1786
|
+
> .aranova-blog-toc-panel-title::after {
|
|
1787
|
+
transform: translateY(-30%) rotate(135deg);
|
|
1788
|
+
}
|
|
1789
|
+
.aranova-blog-post .aranova-blog-toc-panel-clip {
|
|
1790
|
+
overflow: hidden;
|
|
1791
|
+
min-height: 0;
|
|
1792
|
+
}
|
|
1793
|
+
.aranova-blog-post .aranova-blog-toc-panel ol {
|
|
1794
|
+
list-style: none;
|
|
1795
|
+
margin: 0.75em 0 0;
|
|
1796
|
+
padding: 0;
|
|
1797
|
+
}
|
|
1798
|
+
.aranova-blog-post .aranova-blog-toc-panel li {
|
|
1799
|
+
margin: 0;
|
|
1800
|
+
}
|
|
1801
|
+
.aranova-blog-post .aranova-blog-toc-panel li + li {
|
|
1802
|
+
margin-block-start: 0.3em;
|
|
1803
|
+
}
|
|
1804
|
+
.aranova-blog-post .aranova-blog-toc-panel li[data-depth="3"] {
|
|
1805
|
+
padding-inline-start: 0.9em;
|
|
1806
|
+
font-size: 0.94em;
|
|
1807
|
+
}
|
|
1808
|
+
.aranova-blog-post .aranova-blog-toc-panel-link {
|
|
1809
|
+
display: -webkit-box;
|
|
1810
|
+
-webkit-box-orient: vertical;
|
|
1811
|
+
-webkit-line-clamp: 2;
|
|
1812
|
+
overflow: hidden;
|
|
1813
|
+
line-height: 1.35;
|
|
1814
|
+
color: var(--ara-blog-color-text-muted);
|
|
1815
|
+
text-decoration: none;
|
|
1816
|
+
border-inline-start: 2px solid transparent;
|
|
1817
|
+
padding-inline-start: 0.6em;
|
|
1818
|
+
margin-inline-start: -0.75em;
|
|
1819
|
+
transition: color 150ms ease;
|
|
1820
|
+
}
|
|
1821
|
+
.aranova-blog-post .aranova-blog-toc-panel-link:hover {
|
|
1822
|
+
color: var(--ara-blog-color-link);
|
|
1823
|
+
text-decoration: underline;
|
|
1824
|
+
text-underline-offset: 0.15em;
|
|
1825
|
+
}
|
|
1826
|
+
/* Scroll-spy active entry: primary color + inline-start accent. */
|
|
1827
|
+
.aranova-blog-post .aranova-blog-toc-panel-link[data-active="true"] {
|
|
1828
|
+
color: var(--ara-blog-color-primary);
|
|
1829
|
+
border-inline-start-color: var(--ara-blog-color-primary);
|
|
1830
|
+
font-weight: 600;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
/* --- Reading progress (client island) --------------------------------------------
|
|
1834
|
+
* Fixed strip along the top of the viewport; fill = scaleX(scrolled fraction),
|
|
1835
|
+
* driven directly from rAF (no transition \u2014 it tracks position, so the
|
|
1836
|
+
* reduced-motion block has nothing to disable and the bar stays useful). */
|
|
1837
|
+
.aranova-blog-post .aranova-blog-progress {
|
|
1838
|
+
position: fixed;
|
|
1839
|
+
inset-block-start: 0;
|
|
1840
|
+
inset-inline: 0;
|
|
1841
|
+
z-index: 60;
|
|
1842
|
+
background: var(--ara-blog-color-primary);
|
|
1843
|
+
transform: scaleX(0);
|
|
1844
|
+
transform-origin: 0 50%;
|
|
1845
|
+
pointer-events: none;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
/* --- Vertical rhythm ------------------------------------------------------------
|
|
1849
|
+
* Kept AFTER the element rules: the owl selector and the :where()-wrapped
|
|
1850
|
+
* element rules all sit at (0,1,0), so source order lets rhythm win over the
|
|
1851
|
+
* elements' margin resets, and the per-heading overrides below win over the
|
|
1852
|
+
* owl. \`:is(...)\` targets both today's direct-child layout and the later
|
|
1853
|
+
* \`.aranova-blog-content\` wrapper at class specificity. */
|
|
1854
|
+
:is(.aranova-blog-post, .aranova-blog-content) > * + * {
|
|
1855
|
+
margin-block-start: var(--_flow);
|
|
1856
|
+
}
|
|
1857
|
+
:is(.aranova-blog-post, .aranova-blog-content) > :where(h2) {
|
|
1858
|
+
margin-block-start: var(--_flow-section);
|
|
1859
|
+
}
|
|
1860
|
+
:is(.aranova-blog-post, .aranova-blog-content) > :where(h3, h4) {
|
|
1861
|
+
margin-block-start: var(--_flow-lg);
|
|
1862
|
+
}
|
|
1863
|
+
:is(.aranova-blog-post, .aranova-blog-content) > :where(figure) {
|
|
1864
|
+
margin-block: var(--_flow-lg);
|
|
1865
|
+
}
|
|
1866
|
+
:is(.aranova-blog-post, .aranova-blog-content) > :where(hr) {
|
|
1867
|
+
margin-block: var(--_flow-section);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/* --- Motion ------------------------------------------------------------------
|
|
1871
|
+
* Opt-in: a later phase stamps data-motion="subtle" on the article; without
|
|
1872
|
+
* the attribute nothing animates. */
|
|
1873
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
1874
|
+
.aranova-blog-post[data-motion="subtle"] {
|
|
1875
|
+
animation: aranova-blog-fade-up 300ms ease-out both;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
@keyframes aranova-blog-fade-up {
|
|
1879
|
+
from {
|
|
1880
|
+
opacity: 0;
|
|
1881
|
+
transform: translateY(8px);
|
|
1882
|
+
}
|
|
1883
|
+
to {
|
|
1884
|
+
opacity: 1;
|
|
1885
|
+
transform: none;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1889
|
+
.aranova-blog-post,
|
|
1890
|
+
.aranova-blog-post *,
|
|
1891
|
+
.aranova-blog-post *::before,
|
|
1892
|
+
.aranova-blog-post *::after {
|
|
1893
|
+
animation: none !important;
|
|
1894
|
+
transition: none !important;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
`;
|
|
1898
|
+
|
|
1899
|
+
// ../tracking-core/src/resources/blog/view/blog-post-view.tsx
|
|
1900
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1901
|
+
async function BlogPostView({
|
|
1902
|
+
mdx,
|
|
1903
|
+
theme = BASE_BLOG_THEME,
|
|
1904
|
+
components,
|
|
1905
|
+
header,
|
|
1906
|
+
aside,
|
|
1907
|
+
motion = "none",
|
|
1908
|
+
classNames,
|
|
1909
|
+
requireLeadingH1 = true,
|
|
1910
|
+
tocSide = "right"
|
|
1911
|
+
}) {
|
|
1912
|
+
const style = blogThemeToCssVars(theme);
|
|
1913
|
+
const merged = { ...createDefaultComponents([], classNames), ...components };
|
|
1914
|
+
const validation = validateBlogMdx(mdx, { requireLeadingH1 });
|
|
1915
|
+
if (!validation.ok) {
|
|
1916
|
+
const rules = Array.from(new Set(validation.violations.map((v) => v.rule))).join(", ");
|
|
1917
|
+
return /* @__PURE__ */ jsxs2(
|
|
1918
|
+
"article",
|
|
1919
|
+
{
|
|
1920
|
+
className: joinClassNames("aranova-blog-post", classNames?.article),
|
|
1921
|
+
"data-error": "mdx-validation-failed",
|
|
1922
|
+
style,
|
|
1923
|
+
children: [
|
|
1924
|
+
/* @__PURE__ */ jsx2("style", { children: BLOG_BASE_CSS }),
|
|
1925
|
+
/* @__PURE__ */ jsxs2("p", { children: [
|
|
1926
|
+
"This post could not be displayed. (blocked: ",
|
|
1927
|
+
rules,
|
|
1928
|
+
")"
|
|
1929
|
+
] })
|
|
1930
|
+
]
|
|
1931
|
+
}
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
let content;
|
|
1935
|
+
try {
|
|
1936
|
+
const compiled = await compileMDX({
|
|
1937
|
+
source: mdx,
|
|
1938
|
+
components: merged,
|
|
1939
|
+
options: { mdxOptions: { rehypePlugins: [rehypeSlug] } }
|
|
1940
|
+
});
|
|
1941
|
+
content = compiled.content;
|
|
1942
|
+
} catch {
|
|
1943
|
+
return /* @__PURE__ */ jsxs2(
|
|
1944
|
+
"article",
|
|
1945
|
+
{
|
|
1946
|
+
className: joinClassNames("aranova-blog-post", classNames?.article),
|
|
1947
|
+
"data-error": "mdx-render-failed",
|
|
1948
|
+
style,
|
|
1949
|
+
children: [
|
|
1950
|
+
/* @__PURE__ */ jsx2("style", { children: BLOG_BASE_CSS }),
|
|
1951
|
+
/* @__PURE__ */ jsx2("p", { children: "This post could not be displayed." })
|
|
1952
|
+
]
|
|
1953
|
+
}
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
return /* @__PURE__ */ jsxs2(
|
|
1957
|
+
"article",
|
|
1958
|
+
{
|
|
1959
|
+
className: joinClassNames("aranova-blog-post", classNames?.article),
|
|
1960
|
+
"data-motion": motion,
|
|
1961
|
+
style,
|
|
1962
|
+
children: [
|
|
1963
|
+
/* @__PURE__ */ jsx2("style", { children: BLOG_BASE_CSS }),
|
|
1964
|
+
header,
|
|
1965
|
+
/* @__PURE__ */ jsxs2(
|
|
1966
|
+
"div",
|
|
1967
|
+
{
|
|
1968
|
+
className: joinClassNames("aranova-blog-layout", classNames?.layout),
|
|
1969
|
+
"data-toc-side": tocSide,
|
|
1970
|
+
children: [
|
|
1971
|
+
/* @__PURE__ */ jsx2("div", { className: joinClassNames("aranova-blog-content", classNames?.content), children: content }),
|
|
1972
|
+
aside ? /* @__PURE__ */ jsx2("aside", { className: joinClassNames("aranova-blog-aside", classNames?.aside), children: aside }) : null
|
|
1973
|
+
]
|
|
1974
|
+
}
|
|
1975
|
+
)
|
|
1976
|
+
]
|
|
1977
|
+
}
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
// ../tracking-core/src/resources/blog/view/chrome/breadcrumbs.tsx
|
|
1982
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1983
|
+
function BlogBreadcrumbs({
|
|
1984
|
+
items,
|
|
1985
|
+
currentLabel,
|
|
1986
|
+
className
|
|
1987
|
+
}) {
|
|
1988
|
+
return /* @__PURE__ */ jsx3("nav", { "aria-label": "Breadcrumbs", className: joinClassNames("aranova-blog-breadcrumbs", className), children: /* @__PURE__ */ jsxs3("ol", { children: [
|
|
1989
|
+
items.map((item) => /* @__PURE__ */ jsxs3("li", { children: [
|
|
1990
|
+
/* @__PURE__ */ jsx3("a", { href: item.href, children: item.label }),
|
|
1991
|
+
/* @__PURE__ */ jsx3("span", { "aria-hidden": "true", className: "aranova-blog-breadcrumbs-separator", children: "/" })
|
|
1992
|
+
] }, `${item.href} ${item.label}`)),
|
|
1993
|
+
/* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3("span", { "aria-current": "page", children: currentLabel }) })
|
|
1994
|
+
] }) });
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// ../tracking-core/src/resources/blog/view/chrome/meta.tsx
|
|
1998
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1999
|
+
var DEFAULT_BLOG_LOCALE = "en-CA";
|
|
2000
|
+
function formatBlogDate(iso, locale) {
|
|
2001
|
+
if (!iso) return null;
|
|
2002
|
+
const date = new Date(iso);
|
|
2003
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
2004
|
+
let formatter;
|
|
2005
|
+
try {
|
|
2006
|
+
formatter = new Intl.DateTimeFormat(locale, { dateStyle: "long", timeZone: "UTC" });
|
|
2007
|
+
} catch {
|
|
2008
|
+
formatter = new Intl.DateTimeFormat(DEFAULT_BLOG_LOCALE, {
|
|
2009
|
+
dateStyle: "long",
|
|
2010
|
+
timeZone: "UTC"
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
return { dateTime: date.toISOString().slice(0, 10), label: formatter.format(date) };
|
|
2014
|
+
}
|
|
2015
|
+
function BlogPostMeta({
|
|
2016
|
+
publishedAt,
|
|
2017
|
+
updatedAt,
|
|
2018
|
+
readingTimeMinutes,
|
|
2019
|
+
tags,
|
|
2020
|
+
locale = DEFAULT_BLOG_LOCALE,
|
|
2021
|
+
className
|
|
2022
|
+
}) {
|
|
2023
|
+
const published = formatBlogDate(publishedAt, locale);
|
|
2024
|
+
const updated = formatBlogDate(updatedAt, locale);
|
|
2025
|
+
const showUpdated = updated !== null && updated.dateTime !== published?.dateTime;
|
|
2026
|
+
const showReadingTime = typeof readingTimeMinutes === "number" && readingTimeMinutes > 0;
|
|
2027
|
+
const shownTags = (tags ?? []).filter((tag) => tag.length > 0);
|
|
2028
|
+
const items = [];
|
|
2029
|
+
if (published) {
|
|
2030
|
+
items.push({
|
|
2031
|
+
key: "published",
|
|
2032
|
+
node: /* @__PURE__ */ jsx4("time", { dateTime: published.dateTime, children: published.label })
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
if (showUpdated && updated) {
|
|
2036
|
+
items.push({
|
|
2037
|
+
key: "updated",
|
|
2038
|
+
node: /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
2039
|
+
"Updated ",
|
|
2040
|
+
/* @__PURE__ */ jsx4("time", { dateTime: updated.dateTime, children: updated.label })
|
|
2041
|
+
] })
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
if (showReadingTime) {
|
|
2045
|
+
items.push({ key: "reading-time", node: /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
2046
|
+
readingTimeMinutes,
|
|
2047
|
+
" min read"
|
|
2048
|
+
] }) });
|
|
2049
|
+
}
|
|
2050
|
+
if (items.length === 0 && shownTags.length === 0) return null;
|
|
2051
|
+
return /* @__PURE__ */ jsxs4("div", { className: joinClassNames("aranova-blog-meta", className), children: [
|
|
2052
|
+
items.map((item, index) => /* @__PURE__ */ jsxs4("span", { className: "aranova-blog-meta-item", children: [
|
|
2053
|
+
index > 0 ? /* @__PURE__ */ jsx4("span", { "aria-hidden": "true", className: "aranova-blog-meta-separator", children: "\xB7" }) : null,
|
|
2054
|
+
item.node
|
|
2055
|
+
] }, item.key)),
|
|
2056
|
+
shownTags.length > 0 ? /* @__PURE__ */ jsx4("span", { className: "aranova-blog-meta-tags", children: shownTags.map((tag) => /* @__PURE__ */ jsx4("span", { className: "aranova-blog-meta-tag", children: tag }, tag)) }) : null
|
|
2057
|
+
] });
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// ../tracking-core/src/resources/blog/view/chrome/toc.tsx
|
|
2061
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2062
|
+
function BlogTocShell({
|
|
2063
|
+
headings,
|
|
2064
|
+
title = "On this page",
|
|
2065
|
+
className
|
|
2066
|
+
}) {
|
|
2067
|
+
if (headings.length === 0) return null;
|
|
2068
|
+
return /* @__PURE__ */ jsxs5("details", { open: true, className: joinClassNames("aranova-blog-toc", className), children: [
|
|
2069
|
+
/* @__PURE__ */ jsx5("summary", { className: "aranova-blog-toc-title", children: title }),
|
|
2070
|
+
/* @__PURE__ */ jsx5("nav", { "aria-label": "Table of contents", children: /* @__PURE__ */ jsx5("ol", { children: headings.map((heading) => /* @__PURE__ */ jsx5("li", { "data-depth": heading.depth, children: /* @__PURE__ */ jsx5("a", { href: `#${heading.id}`, title: heading.text, children: heading.text }) }, heading.id)) }) })
|
|
2071
|
+
] });
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
// ../tracking-core/src/resources/blog/view/blog-post.tsx
|
|
2075
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2076
|
+
async function BlogPost({
|
|
2077
|
+
slug,
|
|
2078
|
+
client,
|
|
2079
|
+
defaultTheme,
|
|
2080
|
+
components,
|
|
2081
|
+
notFound,
|
|
2082
|
+
showMeta = false,
|
|
2083
|
+
showToc = false,
|
|
2084
|
+
tocSide = "right",
|
|
2085
|
+
showBreadcrumbs = false,
|
|
2086
|
+
breadcrumbs,
|
|
2087
|
+
locale,
|
|
2088
|
+
tocSlot,
|
|
2089
|
+
progressSlot,
|
|
2090
|
+
classNames
|
|
2091
|
+
}) {
|
|
2092
|
+
const post = await client.getPost(slug);
|
|
2093
|
+
if (!post) {
|
|
2094
|
+
return notFound ?? /* @__PURE__ */ jsx6("article", { className: "aranova-blog-post", "data-error": "not-found", children: /* @__PURE__ */ jsx6("p", { children: "This post could not be found." }) });
|
|
2095
|
+
}
|
|
2096
|
+
const theme = resolveBlogTheme(
|
|
2097
|
+
sanitizeBlogTheme(defaultTheme),
|
|
2098
|
+
sanitizeBlogTheme(post.theme_overrides)
|
|
2099
|
+
);
|
|
2100
|
+
const merged = { ...createDefaultComponents(post.images, classNames), ...components };
|
|
2101
|
+
const crumbs = showBreadcrumbs && breadcrumbs && breadcrumbs.length > 0 ? /* @__PURE__ */ jsx6(
|
|
2102
|
+
BlogBreadcrumbs,
|
|
2103
|
+
{
|
|
2104
|
+
items: breadcrumbs,
|
|
2105
|
+
currentLabel: post.title,
|
|
2106
|
+
className: classNames?.breadcrumbs
|
|
2107
|
+
}
|
|
2108
|
+
) : null;
|
|
2109
|
+
const meta = showMeta ? /* @__PURE__ */ jsx6(
|
|
2110
|
+
BlogPostMeta,
|
|
2111
|
+
{
|
|
2112
|
+
publishedAt: post.published_at,
|
|
2113
|
+
updatedAt: post.updated_at,
|
|
2114
|
+
readingTimeMinutes: estimateReadingTime(post.mdx),
|
|
2115
|
+
locale,
|
|
2116
|
+
className: classNames?.meta
|
|
2117
|
+
}
|
|
2118
|
+
) : null;
|
|
2119
|
+
const progress = progressSlot ? progressSlot() : null;
|
|
2120
|
+
const header = progress || crumbs || meta ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
2121
|
+
progress,
|
|
2122
|
+
crumbs,
|
|
2123
|
+
meta
|
|
2124
|
+
] }) : void 0;
|
|
2125
|
+
const headings = showToc ? extractBlogHeadings(post.mdx) : [];
|
|
2126
|
+
const aside = headings.length > 0 ? tocSlot?.(headings) ?? /* @__PURE__ */ jsx6(BlogTocShell, { headings, className: classNames?.toc }) : void 0;
|
|
2127
|
+
return /* @__PURE__ */ jsx6(
|
|
2128
|
+
BlogPostView,
|
|
2129
|
+
{
|
|
2130
|
+
mdx: post.mdx,
|
|
2131
|
+
theme,
|
|
2132
|
+
components: merged,
|
|
2133
|
+
header,
|
|
2134
|
+
aside,
|
|
2135
|
+
classNames,
|
|
2136
|
+
tocSide
|
|
2137
|
+
}
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
// src/blog-post.tsx
|
|
2142
|
+
import { ReadingProgress, TocPanel } from "@aranova/tracking-next/blog-client";
|
|
2143
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2144
|
+
function BlogPost2({
|
|
2145
|
+
showProgress = false,
|
|
2146
|
+
tocCollapse = "vertical",
|
|
2147
|
+
...props
|
|
2148
|
+
}) {
|
|
2149
|
+
const tocSide = props.tocSide ?? "right";
|
|
2150
|
+
return /* @__PURE__ */ jsx7(
|
|
2151
|
+
BlogPost,
|
|
2152
|
+
{
|
|
2153
|
+
...props,
|
|
2154
|
+
tocSlot: (headings) => /* @__PURE__ */ jsx7(
|
|
2155
|
+
TocPanel,
|
|
2156
|
+
{
|
|
2157
|
+
headings,
|
|
2158
|
+
className: props.classNames?.toc,
|
|
2159
|
+
collapse: tocCollapse,
|
|
2160
|
+
side: tocSide
|
|
2161
|
+
}
|
|
2162
|
+
),
|
|
2163
|
+
progressSlot: showProgress ? () => /* @__PURE__ */ jsx7(ReadingProgress, { className: props.classNames?.progress }) : void 0
|
|
2164
|
+
}
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
export {
|
|
2168
|
+
BASE_BLOG_THEME,
|
|
2169
|
+
BLOG_BASE_CSS,
|
|
2170
|
+
BLOG_BREAKPOINTS,
|
|
2171
|
+
BLOG_BREAKPOINT_MIN_WIDTHS_PX,
|
|
2172
|
+
BLOG_MDX_ALLOWED_COMPONENTS,
|
|
2173
|
+
BLOG_MDX_COMPONENT_GRAMMAR,
|
|
2174
|
+
BLOG_MDX_MAX_DEPTH,
|
|
2175
|
+
BLOG_MDX_MAX_NODES,
|
|
2176
|
+
BLOG_MDX_MAX_SIZE,
|
|
2177
|
+
BLOG_THEME_PRESETS,
|
|
2178
|
+
BlogBreadcrumbs,
|
|
2179
|
+
BlogPost2 as BlogPost,
|
|
2180
|
+
BlogPostMeta,
|
|
2181
|
+
BlogPostView,
|
|
2182
|
+
BlogTocShell,
|
|
2183
|
+
DEFAULT_BLOG_LOCALE,
|
|
2184
|
+
blogHeroImageSchema,
|
|
2185
|
+
blogImageSchema,
|
|
2186
|
+
blogManifestEntrySchema,
|
|
2187
|
+
blogManifestSchema,
|
|
2188
|
+
blogManifestWireSchema,
|
|
2189
|
+
blogPostContentSchema,
|
|
2190
|
+
blogPostContentWireSchema,
|
|
2191
|
+
blogSeoSchema,
|
|
2192
|
+
blogThemeColorsSchema,
|
|
2193
|
+
blogThemeOverrideSchema,
|
|
2194
|
+
blogThemeSchema,
|
|
2195
|
+
blogThemeSpacingSchema,
|
|
2196
|
+
blogThemeToCssVars,
|
|
2197
|
+
blogThemeTypographySchema,
|
|
2198
|
+
buildBlogPostMetadata,
|
|
2199
|
+
buildBlogPostingJsonLd,
|
|
2200
|
+
buildBlogSitemapEntries,
|
|
2201
|
+
buildFaqJsonLd,
|
|
2202
|
+
createBlogClient,
|
|
2203
|
+
createBlogFontTheme,
|
|
2204
|
+
createDefaultComponents,
|
|
2205
|
+
estimateReadingTime,
|
|
2206
|
+
extractBlogHeadings,
|
|
2207
|
+
extractFaqPairs,
|
|
2208
|
+
joinClassNames,
|
|
2209
|
+
nullishField,
|
|
2210
|
+
parseBlogManifest,
|
|
2211
|
+
parseBlogPostContent,
|
|
2212
|
+
resolveBlogTheme,
|
|
2213
|
+
responsive,
|
|
2214
|
+
sanitizeBlogTheme,
|
|
2215
|
+
serializeJsonLd,
|
|
2216
|
+
toBlogBreakpointMap,
|
|
2217
|
+
validateBlogMdx
|
|
2218
|
+
};
|
|
2219
|
+
//# sourceMappingURL=blog-server.mjs.map
|