@aranova/tracking-next 0.19.2 → 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 +66 -1
- 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.d.mts +29 -24
- package/dist/index.d.ts +29 -24
- package/dist/index.js +8 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -13
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +6 -11
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +6 -11
- package/dist/middleware.mjs.map +1 -1
- package/dist/phone.js +6 -11
- package/dist/phone.js.map +1 -1
- package/dist/phone.mjs +6 -11
- package/dist/phone.mjs.map +1 -1
- package/dist/server.js +6 -11
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +6 -11
- package/dist/server.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,248 @@
|
|
|
1
|
+
import { BlogImage, BlogClassNameOverrides, BlogTheme, BlogClient, BlogThemeOverride } from './blog.js';
|
|
2
|
+
export { BASE_BLOG_THEME, BLOG_BREAKPOINTS, BLOG_BREAKPOINT_MIN_WIDTHS_PX, BLOG_MDX_ALLOWED_COMPONENTS, BLOG_MDX_COMPONENT_GRAMMAR, BLOG_MDX_MAX_DEPTH, BLOG_MDX_MAX_NODES, BLOG_MDX_MAX_SIZE, BLOG_THEME_PRESETS, BlogBreakpoint, BlogBreakpointMap, BlogClientConfig, BlogFaqPair, BlogFontSources, BlogHeroImage, BlogJsonLdOptions, BlogManifest, BlogManifestEntry, BlogMdxComponentGrammarEntry, BlogMdxComponentName, BlogMdxValidateOptions, BlogMdxValidationResult, BlogMdxViolation, BlogPostContent, BlogPostMetadata, BlogPostMetadataImage, BlogSeo, BlogSeoOptions, BlogSitemapEntry, BlogThemeColors, BlogThemePresetName, BlogThemeSpacing, BlogThemeTypography, NextFetchInit, ResponsiveValue, blogHeroImageSchema, blogImageSchema, blogManifestEntrySchema, blogManifestSchema, blogManifestWireSchema, blogPostContentSchema, blogPostContentWireSchema, blogSeoSchema, blogThemeColorsSchema, blogThemeOverrideSchema, blogThemeSchema, blogThemeSpacingSchema, blogThemeToCssVars, blogThemeTypographySchema, buildBlogPostMetadata, buildBlogPostingJsonLd, buildBlogSitemapEntries, buildFaqJsonLd, createBlogClient, createBlogFontTheme, extractFaqPairs, joinClassNames, nullishField, parseBlogManifest, parseBlogPostContent, resolveBlogTheme, responsive, sanitizeBlogTheme, serializeJsonLd, toBlogBreakpointMap, validateBlogMdx } from './blog.js';
|
|
3
|
+
import { B as BlogHeading } from './toc-C_OvH3ES.js';
|
|
4
|
+
export { e as estimateReadingTime, a as extractBlogHeadings } from './toc-C_OvH3ES.js';
|
|
5
|
+
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
6
|
+
import 'zod';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The CLOSED component interface the MDX renderer accepts. Generated MDX may
|
|
10
|
+
* only reference these elements/components; anything else fails MDX rendering
|
|
11
|
+
* (caught by the view's fallback). Callers may override any subset via the
|
|
12
|
+
* `components` prop on `BlogPost` / `BlogPostView`.
|
|
13
|
+
*/
|
|
14
|
+
interface BlogComponents {
|
|
15
|
+
h1: (props: ComponentPropsWithoutRef<"h1">) => ReactNode;
|
|
16
|
+
h2: (props: ComponentPropsWithoutRef<"h2">) => ReactNode;
|
|
17
|
+
h3: (props: ComponentPropsWithoutRef<"h3">) => ReactNode;
|
|
18
|
+
h4: (props: ComponentPropsWithoutRef<"h4">) => ReactNode;
|
|
19
|
+
h5: (props: ComponentPropsWithoutRef<"h5">) => ReactNode;
|
|
20
|
+
h6: (props: ComponentPropsWithoutRef<"h6">) => ReactNode;
|
|
21
|
+
p: (props: ComponentPropsWithoutRef<"p">) => ReactNode;
|
|
22
|
+
a: (props: ComponentPropsWithoutRef<"a">) => ReactNode;
|
|
23
|
+
img: (props: ComponentPropsWithoutRef<"img">) => ReactNode;
|
|
24
|
+
pre: (props: ComponentPropsWithoutRef<"pre">) => ReactNode;
|
|
25
|
+
code: (props: ComponentPropsWithoutRef<"code">) => ReactNode;
|
|
26
|
+
blockquote: (props: ComponentPropsWithoutRef<"blockquote">) => ReactNode;
|
|
27
|
+
ul: (props: ComponentPropsWithoutRef<"ul">) => ReactNode;
|
|
28
|
+
ol: (props: ComponentPropsWithoutRef<"ol">) => ReactNode;
|
|
29
|
+
li: (props: ComponentPropsWithoutRef<"li">) => ReactNode;
|
|
30
|
+
table: (props: ComponentPropsWithoutRef<"table">) => ReactNode;
|
|
31
|
+
hr: (props: ComponentPropsWithoutRef<"hr">) => ReactNode;
|
|
32
|
+
/** Styled aside for tips/notes: `<Callout>…</Callout>` in MDX. */
|
|
33
|
+
Callout: (props: {
|
|
34
|
+
children?: ReactNode;
|
|
35
|
+
}) => ReactNode;
|
|
36
|
+
/** Primary-colored button-styled anchor: `<Cta href label/>` in MDX. */
|
|
37
|
+
Cta: (props: {
|
|
38
|
+
href: string;
|
|
39
|
+
label: string;
|
|
40
|
+
}) => ReactNode;
|
|
41
|
+
/** Renders an image from the post's image records by id: `<BlogImage id/>`. */
|
|
42
|
+
BlogImage: (props: {
|
|
43
|
+
id: string;
|
|
44
|
+
}) => ReactNode;
|
|
45
|
+
/** One FAQ entry (question heading + prose answer children): `<FaqItem question>…</FaqItem>`. */
|
|
46
|
+
FaqItem: (props: {
|
|
47
|
+
question: string;
|
|
48
|
+
children?: ReactNode;
|
|
49
|
+
}) => ReactNode;
|
|
50
|
+
/** Styled TL;DR box holding a markdown bullet list: `<KeyTakeaways>…</KeyTakeaways>`. */
|
|
51
|
+
KeyTakeaways: (props: {
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
}) => ReactNode;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Default renderers. `images` is closed over so `<BlogImage id/>` in MDX can
|
|
57
|
+
* resolve the post's image records without threading props through MDX.
|
|
58
|
+
*
|
|
59
|
+
* Images render as plain `<img>` (with intrinsic width/height + lazy loading),
|
|
60
|
+
* NOT `next/image` — tracking-core is framework-free, and the CDN host would
|
|
61
|
+
* otherwise need to be whitelisted in every consumer's `next.config`. A Next
|
|
62
|
+
* site wanting `next/image` can override `img`/`BlogImage` via `components`.
|
|
63
|
+
*
|
|
64
|
+
* `classNames` merges a site-supplied class onto the fixed base class of each
|
|
65
|
+
* styleable component (see {@link BlogClassNameOverrides}) — additive, never
|
|
66
|
+
* a replacement of the `.aranova-blog-*` selector.
|
|
67
|
+
*/
|
|
68
|
+
declare function createDefaultComponents(images: BlogImage[], classNames?: BlogClassNameOverrides): BlogComponents;
|
|
69
|
+
|
|
70
|
+
interface BlogPostViewProps {
|
|
71
|
+
/** Raw MDX source (the `mdx` field of the post content JSON). */
|
|
72
|
+
mdx: string;
|
|
73
|
+
/** A fully-RESOLVED theme (see `resolveBlogTheme`). Default: `BASE_BLOG_THEME`. */
|
|
74
|
+
theme?: BlogTheme;
|
|
75
|
+
/** Overrides merged over the default component set. */
|
|
76
|
+
components?: Partial<BlogComponents>;
|
|
77
|
+
/** Article chrome rendered above the content/aside layout (breadcrumbs, meta, …). */
|
|
78
|
+
header?: ReactNode;
|
|
79
|
+
/** Sidebar chrome (e.g. `BlogTocShell`); omitted entirely when not provided. */
|
|
80
|
+
aside?: ReactNode;
|
|
81
|
+
/** Entrance-motion opt-in, stamped as `data-motion` for the base CSS. Default `"none"`. */
|
|
82
|
+
motion?: "none" | "subtle";
|
|
83
|
+
/** Site-supplied classes merged onto the fixed base classes (additive, per surface). */
|
|
84
|
+
classNames?: BlogClassNameOverrides;
|
|
85
|
+
/**
|
|
86
|
+
* Reject content before the H1 (frontmatter, a stray metadata block, prose)
|
|
87
|
+
* — only meaningful for a whole post. Default `true`; direct-fragment
|
|
88
|
+
* callers (component-grammar tests) pass `false`.
|
|
89
|
+
*/
|
|
90
|
+
requireLeadingH1?: boolean;
|
|
91
|
+
/** Which side the aside rail sits on at desktop width. Default `"right"`. */
|
|
92
|
+
tocSide?: "left" | "right";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Renders MDX inside a themed `<article class="aranova-blog-post">`. The theme
|
|
96
|
+
* flows in as sanitized `--ara-blog-*` CSS variables on the article element;
|
|
97
|
+
* `BLOG_BASE_CSS` (a static constant — nothing interpolated) consumes them.
|
|
98
|
+
*
|
|
99
|
+
* Structure: `{header}` chrome first, then a `.aranova-blog-layout` holding the
|
|
100
|
+
* `.aranova-blog-content` column and (when provided) an `.aranova-blog-aside`
|
|
101
|
+
* rail — the base CSS turns the layout into a sticky-aside grid on wide
|
|
102
|
+
* viewports and keeps it a plain block on mobile.
|
|
103
|
+
*
|
|
104
|
+
* MDX is compiled via `compileMDX` — the awaitable form of `<MDXRemote/>` from
|
|
105
|
+
* `next-mdx-remote/rsc` — because an element-level `<MDXRemote/>` compiles
|
|
106
|
+
* during React's render, where a malformed document would throw past any
|
|
107
|
+
* try/catch here. `rehype-slug` stamps GitHub-style anchor ids on every
|
|
108
|
+
* heading (slug parity with `extractBlogHeadings` in ../toc.ts). On compile
|
|
109
|
+
* failure a typed fallback article renders instead; this component never
|
|
110
|
+
* throws into the host page.
|
|
111
|
+
*/
|
|
112
|
+
declare function BlogPostView({ mdx, theme, components, header, aside, motion, classNames, requireLeadingH1, tocSide, }: BlogPostViewProps): Promise<ReactNode>;
|
|
113
|
+
|
|
114
|
+
interface BlogBreadcrumbItem {
|
|
115
|
+
label: string;
|
|
116
|
+
href: string;
|
|
117
|
+
}
|
|
118
|
+
interface BlogBreadcrumbsProps {
|
|
119
|
+
/** Ancestor links, outermost first (e.g. Home → Blog). */
|
|
120
|
+
items: readonly BlogBreadcrumbItem[];
|
|
121
|
+
/** The current page (usually the post title) — rendered as text, not a link. */
|
|
122
|
+
currentLabel: string;
|
|
123
|
+
/** Site-supplied class merged onto the fixed `.aranova-blog-breadcrumbs` class. */
|
|
124
|
+
className?: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Accessible breadcrumb trail: ancestor links separated by a decorative `/`,
|
|
128
|
+
* ending in the current page as `<span aria-current="page">`. Styled by the
|
|
129
|
+
* `.aranova-blog-breadcrumbs` section of the base CSS.
|
|
130
|
+
*/
|
|
131
|
+
declare function BlogBreadcrumbs({ items, currentLabel, className, }: BlogBreadcrumbsProps): ReactNode;
|
|
132
|
+
|
|
133
|
+
interface BlogPostProps$1 {
|
|
134
|
+
slug: string;
|
|
135
|
+
client: BlogClient;
|
|
136
|
+
/** Client-registered default theme layer (sits between the base theme and per-post overrides). */
|
|
137
|
+
defaultTheme?: BlogThemeOverride;
|
|
138
|
+
/** Overrides merged over the default component set. */
|
|
139
|
+
components?: Partial<BlogComponents>;
|
|
140
|
+
/** Rendered when the slug is unknown/unfetchable. Default: a minimal not-found article. */
|
|
141
|
+
notFound?: ReactNode;
|
|
142
|
+
/** Render the byline row (published/updated dates + reading time). Default `false`. */
|
|
143
|
+
showMeta?: boolean;
|
|
144
|
+
/** Render the sticky table-of-contents rail (omitted when the post has no h2/h3). Default `false`. */
|
|
145
|
+
showToc?: boolean;
|
|
146
|
+
/** Which side the ToC rail sits on at desktop width. Default `"right"`. */
|
|
147
|
+
tocSide?: "left" | "right";
|
|
148
|
+
/** Render the breadcrumb trail (requires `breadcrumbs`). Default `false`. */
|
|
149
|
+
showBreadcrumbs?: boolean;
|
|
150
|
+
/** Ancestor links for the breadcrumb trail, outermost first (e.g. Home → Blog). */
|
|
151
|
+
breadcrumbs?: readonly BlogBreadcrumbItem[];
|
|
152
|
+
/** BCP-47 locale for the byline dates. Default `"en-CA"`. */
|
|
153
|
+
locale?: string;
|
|
154
|
+
/**
|
|
155
|
+
* Component-injection slot for the table of contents: when provided (and
|
|
156
|
+
* `showToc` finds an outline), it renders INSTEAD of the zero-JS
|
|
157
|
+
* `BlogTocShell` default. This is how `@aranova/tracking-next`'s enhanced
|
|
158
|
+
* `BlogPost` wires in the animated `TocPanel` client island — tracking-core
|
|
159
|
+
* cannot import it back from tracking-next (core is the dependency), and the
|
|
160
|
+
* island must reach server bundles only via the
|
|
161
|
+
* `@aranova/tracking-next/blog-client` self-specifier so its `"use client"`
|
|
162
|
+
* boundary survives `splitting: false` bundling.
|
|
163
|
+
*/
|
|
164
|
+
tocSlot?: (headings: readonly BlogHeading[]) => ReactNode;
|
|
165
|
+
/**
|
|
166
|
+
* Component-injection slot rendered at the very top of the header chrome
|
|
167
|
+
* (before breadcrumbs/byline). Same rationale as `tocSlot` — the enhanced
|
|
168
|
+
* wrapper injects the `ReadingProgress` island here under `showProgress`.
|
|
169
|
+
*/
|
|
170
|
+
progressSlot?: () => ReactNode;
|
|
171
|
+
/**
|
|
172
|
+
* Site-supplied classes merged onto the fixed base class of each styleable
|
|
173
|
+
* surface (article/layout/MDX components/chrome) — additive, never a
|
|
174
|
+
* replacement of the `.aranova-blog-*` selector. For a full component
|
|
175
|
+
* rewrite instead, use `components`.
|
|
176
|
+
*/
|
|
177
|
+
classNames?: BlogClassNameOverrides;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Static, author-controlled stylesheet for the blog post view. Scoped under
|
|
182
|
+
* `.aranova-blog-post` and driven entirely by the `--ara-blog-*` CSS custom
|
|
183
|
+
* properties emitted by `blogThemeToCssVars`.
|
|
184
|
+
*
|
|
185
|
+
* SECURITY: this string is a compile-time constant — NOTHING from post content
|
|
186
|
+
* or theme data is ever interpolated into it. All per-post variation flows
|
|
187
|
+
* through the sanitized CSS variables on the `<article>` element.
|
|
188
|
+
*/
|
|
189
|
+
declare const BLOG_BASE_CSS = "/* Aranova blog post base stylesheet \u2014 authored source of truth.\n *\n * Scoped under `.aranova-blog-post` and driven entirely by the sanitized\n * `--ara-blog-*` CSS custom properties emitted by `blogThemeToCssVars` on the\n * article element. Host sites usually run Tailwind preflight, so:\n *\n * - element rules use `.aranova-blog-post :where(el)` \u2014 one class of\n * specificity (0,1,0), enough to beat preflight's bare element resets\n * while every rule in this sheet stays flat and order-resolvable;\n * - component classes (`.aranova-blog-callout`, \u2026) keep their natural\n * class-on-class specificity.\n *\n * Responsive-capable tokens (h1/h2/h3/h4/body/small sizes, body line-height,\n * content max-width, block gap, section gap, radius) are NEVER consumed via their raw\n * `--ara-blog-*` vars here \u2014 the generated block below re-points internal\n * `--_*` vars per breakpoint, and all rules consume those.\n *\n * Rhythm rules near the end target BOTH `.aranova-blog-post` (content is a\n * direct child of the article today) and `.aranova-blog-content` (a later\n * phase wraps content in that div) so that phase needs no CSS changes.\n *\n * No raw colors anywhere: every color flows through the theme vars, which\n * already carry `light-dark(...)` values when a dark palette is present.\n */\n\n/* Responsive token plumbing \u2014 GENERATED by build-styles.ts from theme.ts\n (BLOG_BREAKPOINT_MIN_WIDTHS_PX + the responsive() token set). Mobile-first:\n each breakpoint falls back through every narrower key down to the base var.\n Authored rules consume ONLY the --_* internal vars for these tokens. */\n.aranova-blog-post {\n --_h1-size: var(--ara-blog-type-h1-size);\n --_h2-size: var(--ara-blog-type-h2-size);\n --_h3-size: var(--ara-blog-type-h3-size);\n --_h4-size: var(--ara-blog-type-h4-size);\n --_body-size: var(--ara-blog-type-body-size);\n --_body-line-height: var(--ara-blog-type-body-line-height);\n --_small-size: var(--ara-blog-type-small-size);\n --_content-max-width: var(--ara-blog-space-content-max-width);\n --_block-gap: var(--ara-blog-space-block-gap);\n --_section-gap: var(--ara-blog-space-section-gap);\n --_radius: var(--ara-blog-radius);\n}\n@media (min-width: 640px) {\n .aranova-blog-post {\n --_h1-size: var(--ara-blog-type-h1-size-sm, var(--ara-blog-type-h1-size));\n --_h2-size: var(--ara-blog-type-h2-size-sm, var(--ara-blog-type-h2-size));\n --_h3-size: var(--ara-blog-type-h3-size-sm, var(--ara-blog-type-h3-size));\n --_h4-size: var(--ara-blog-type-h4-size-sm, var(--ara-blog-type-h4-size));\n --_body-size: var(--ara-blog-type-body-size-sm, var(--ara-blog-type-body-size));\n --_body-line-height: var(--ara-blog-type-body-line-height-sm, var(--ara-blog-type-body-line-height));\n --_small-size: var(--ara-blog-type-small-size-sm, var(--ara-blog-type-small-size));\n --_content-max-width: var(--ara-blog-space-content-max-width-sm, var(--ara-blog-space-content-max-width));\n --_block-gap: var(--ara-blog-space-block-gap-sm, var(--ara-blog-space-block-gap));\n --_section-gap: var(--ara-blog-space-section-gap-sm, var(--ara-blog-space-section-gap));\n --_radius: var(--ara-blog-radius-sm, var(--ara-blog-radius));\n }\n}\n@media (min-width: 768px) {\n .aranova-blog-post {\n --_h1-size: var(--ara-blog-type-h1-size-md, var(--ara-blog-type-h1-size-sm, var(--ara-blog-type-h1-size)));\n --_h2-size: var(--ara-blog-type-h2-size-md, var(--ara-blog-type-h2-size-sm, var(--ara-blog-type-h2-size)));\n --_h3-size: var(--ara-blog-type-h3-size-md, var(--ara-blog-type-h3-size-sm, var(--ara-blog-type-h3-size)));\n --_h4-size: var(--ara-blog-type-h4-size-md, var(--ara-blog-type-h4-size-sm, var(--ara-blog-type-h4-size)));\n --_body-size: var(--ara-blog-type-body-size-md, var(--ara-blog-type-body-size-sm, var(--ara-blog-type-body-size)));\n --_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)));\n --_small-size: var(--ara-blog-type-small-size-md, var(--ara-blog-type-small-size-sm, var(--ara-blog-type-small-size)));\n --_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)));\n --_block-gap: var(--ara-blog-space-block-gap-md, var(--ara-blog-space-block-gap-sm, var(--ara-blog-space-block-gap)));\n --_section-gap: var(--ara-blog-space-section-gap-md, var(--ara-blog-space-section-gap-sm, var(--ara-blog-space-section-gap)));\n --_radius: var(--ara-blog-radius-md, var(--ara-blog-radius-sm, var(--ara-blog-radius)));\n }\n}\n@media (min-width: 1024px) {\n .aranova-blog-post {\n --_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))));\n --_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))));\n --_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))));\n --_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))));\n --_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))));\n --_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))));\n --_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))));\n --_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))));\n --_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))));\n --_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))));\n --_radius: var(--ara-blog-radius-lg, var(--ara-blog-radius-md, var(--ara-blog-radius-sm, var(--ara-blog-radius))));\n }\n}\n@media (min-width: 1280px) {\n .aranova-blog-post {\n --_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)))));\n --_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)))));\n --_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)))));\n --_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)))));\n --_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)))));\n --_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)))));\n --_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)))));\n --_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)))));\n --_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)))));\n --_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)))));\n --_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)))));\n }\n}\n@media (min-width: 1536px) {\n .aranova-blog-post {\n --_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))))));\n --_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))))));\n --_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))))));\n --_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))))));\n --_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))))));\n --_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))))));\n --_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))))));\n --_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))))));\n --_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))))));\n --_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))))));\n --_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))))));\n }\n}\n@media (min-width: 1920px) {\n .aranova-blog-post {\n --_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)))))));\n --_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)))))));\n --_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)))))));\n --_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)))))));\n --_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)))))));\n --_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)))))));\n --_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)))))));\n --_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)))))));\n --_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)))))));\n --_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)))))));\n --_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)))))));\n }\n}\n\n.aranova-blog-post {\n /* Internal spacing scale, derived from the responsive block gap. */\n --_flow: var(--_block-gap);\n --_flow-sm: calc(var(--_block-gap) * 0.5);\n --_flow-lg: calc(var(--_block-gap) * 1.75);\n --_flow-section: var(--_section-gap);\n /* Optional heading color token \u2014 themes that don't set it fall back to text. */\n --_heading-color: var(--ara-blog-color-heading, var(--ara-blog-color-text));\n\n max-width: var(--_content-max-width);\n margin-inline: auto;\n background: var(--ara-blog-color-background);\n color: var(--ara-blog-color-text);\n font-family: var(--ara-blog-type-body-family);\n font-weight: var(--ara-blog-type-body-weight);\n font-size: var(--_body-size);\n line-height: var(--_body-line-height);\n overflow-wrap: break-word;\n}\n@media (max-width: 639.99px) {\n .aranova-blog-post {\n padding-inline: 1.25rem;\n }\n}\n\n/* --- Headings ------------------------------------------------------------ */\n.aranova-blog-post :where(h1, h2, h3, h4, h5, h6) {\n font-family: var(--ara-blog-type-heading-family);\n font-weight: var(--ara-blog-type-heading-weight);\n line-height: 1.25;\n margin-block: 0;\n color: var(--_heading-color);\n scroll-margin-top: 5rem;\n}\n.aranova-blog-post :where(h1, h2, h3) {\n /* `pretty` fills each line greedily (only the LAST line is massaged to avoid\n * an orphan word) \u2014 so a heading uses the full measure. `balance` was leaving\n * short first lines. */\n text-wrap: pretty;\n}\n.aranova-blog-post :where(h1) {\n font-size: var(--_h1-size);\n letter-spacing: var(--ara-blog-type-heading-letter-spacing);\n}\n.aranova-blog-post :where(h2) {\n font-size: var(--_h2-size);\n letter-spacing: var(--ara-blog-type-heading-letter-spacing);\n}\n.aranova-blog-post :where(h3) {\n font-size: var(--_h3-size);\n letter-spacing: calc(var(--ara-blog-type-heading-letter-spacing) * 0.5);\n}\n.aranova-blog-post :where(h4) {\n font-size: var(--_h4-size);\n font-weight: 600;\n letter-spacing: calc(var(--ara-blog-type-heading-letter-spacing) * 0.5);\n}\n.aranova-blog-post :where(h5) {\n font-size: var(--_body-size);\n font-weight: 600;\n}\n.aranova-blog-post :where(h6) {\n font-size: var(--_small-size);\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--ara-blog-color-text-muted);\n}\n\n/* --- Text ---------------------------------------------------------------- */\n.aranova-blog-post :where(p) {\n margin-block: 0;\n}\n/* Lead paragraph: the one directly after the post title. */\n.aranova-blog-post :where(h1) + :where(p) {\n font-size: 1.15em;\n color: color-mix(in srgb, var(--ara-blog-color-text) 72%, var(--ara-blog-color-text-muted));\n}\n.aranova-blog-post :where(a) {\n color: var(--ara-blog-color-link);\n text-decoration: underline;\n text-underline-offset: 0.15em;\n}\n.aranova-blog-post :where(a):focus-visible {\n outline: 2px solid var(--ara-blog-color-link);\n outline-offset: 2px;\n border-radius: 2px;\n}\n\n/* --- Lists ---------------------------------------------------------------- */\n.aranova-blog-post :where(ul, ol) {\n margin-block: 0;\n padding-inline-start: 1.5em;\n}\n.aranova-blog-post :where(ul) {\n list-style-type: disc;\n}\n.aranova-blog-post :where(ul ul) {\n list-style-type: circle;\n}\n.aranova-blog-post :where(ul ul ul) {\n list-style-type: square;\n}\n.aranova-blog-post :where(ol) {\n list-style-type: decimal;\n}\n.aranova-blog-post :where(ol ol) {\n list-style-type: lower-alpha;\n}\n.aranova-blog-post :where(ul) > li::marker {\n color: var(--ara-blog-color-text-muted);\n}\n.aranova-blog-post :where(ol) > li::marker {\n font-weight: 600;\n color: var(--_heading-color);\n}\n.aranova-blog-post :where(li + li) {\n margin-block-start: 0.35em;\n}\n.aranova-blog-post :where(li > ul, li > ol) {\n margin-block-start: 0.35em;\n}\n\n/* --- Blockquote ------------------------------------------------------------ */\n.aranova-blog-post :where(blockquote) {\n margin: 0;\n padding-block: 0.25em;\n padding-inline: 1.25em 0.5em;\n border-inline-start: 3px solid var(--ara-blog-color-primary);\n color: var(--ara-blog-color-text-muted);\n font-style: italic;\n}\n\n/* --- Code ------------------------------------------------------------------ */\n.aranova-blog-post :where(code) {\n background: var(--ara-blog-color-code-background);\n border-radius: calc(var(--_radius) / 2);\n padding: 0.15em 0.35em;\n font-size: 0.9em;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n}\n.aranova-blog-post :where(pre) {\n margin: 0;\n background: var(--ara-blog-color-code-background);\n border: 1px solid var(--ara-blog-color-border);\n border-radius: var(--_radius);\n padding: 1em;\n overflow-x: auto;\n line-height: 1.5;\n}\n.aranova-blog-post :where(pre code) {\n background: none;\n border-radius: 0;\n padding: 0;\n font-size: var(--_small-size);\n}\n\n/* --- Media ------------------------------------------------------------------ */\n.aranova-blog-post :where(img) {\n max-width: 100%;\n height: auto;\n display: block;\n border: 1px solid var(--ara-blog-color-border);\n border-radius: var(--_radius);\n}\n.aranova-blog-post :where(figure) {\n margin: 0;\n}\n.aranova-blog-post :where(figcaption) {\n margin-block-start: 0.5em;\n font-size: var(--_small-size);\n color: var(--ara-blog-color-text-muted);\n text-align: var(--ara-blog-type-caption-align, center);\n}\n\n/* --- Tables ------------------------------------------------------------------ */\n.aranova-blog-post .aranova-blog-table-wrap {\n overflow-x: auto;\n border: 1px solid var(--ara-blog-color-border);\n border-radius: var(--_radius);\n}\n.aranova-blog-post :where(table) {\n border-collapse: collapse;\n width: 100%;\n font-size: var(--_small-size);\n}\n.aranova-blog-post :where(th, td) {\n border: 0;\n border-block-end: 1px solid var(--ara-blog-color-border);\n padding: 0.6em 0.85em;\n text-align: left;\n}\n.aranova-blog-post :where(th) {\n background: var(--ara-blog-color-surface);\n border-block-end: 2px solid var(--ara-blog-color-border);\n font-weight: 600;\n}\n.aranova-blog-post :where(tbody tr:nth-child(even) td) {\n background: color-mix(\n in srgb,\n var(--ara-blog-color-surface) 55%,\n var(--ara-blog-color-background)\n );\n}\n.aranova-blog-post :where(tbody tr:last-child) :where(th, td) {\n border-block-end: 0;\n}\n\n/* --- Section break ------------------------------------------------------------ */\n.aranova-blog-post :where(hr) {\n border: none;\n border-top: 2px solid var(--ara-blog-color-border);\n width: 4rem;\n margin-inline: auto;\n}\n\n/* --- Callout ------------------------------------------------------------------ */\n.aranova-blog-post .aranova-blog-callout {\n background: var(--ara-blog-color-surface);\n border: 1px solid var(--ara-blog-color-border);\n border-inline-start: 4px solid var(--ara-blog-color-primary);\n border-radius: var(--_radius);\n padding: 0.85em 1.1em;\n}\n.aranova-blog-post .aranova-blog-callout > * + * {\n margin-block-start: var(--_flow-sm);\n}\n\n/* --- CTA ------------------------------------------------------------------ */\n.aranova-blog-post .aranova-blog-cta {\n display: inline-block;\n background: var(--ara-blog-color-primary);\n color: var(--ara-blog-color-primary-foreground);\n border-radius: var(--_radius);\n padding: 0.6em 1.2em;\n font-weight: 600;\n text-decoration: none;\n transition:\n filter 150ms ease,\n transform 150ms ease;\n}\n.aranova-blog-post .aranova-blog-cta:hover {\n filter: brightness(1.08);\n transform: translateY(-1px);\n}\n.aranova-blog-post .aranova-blog-cta:active {\n filter: none;\n transform: none;\n}\n.aranova-blog-post .aranova-blog-cta:focus-visible {\n outline: 2px solid var(--ara-blog-color-primary);\n outline-offset: 2px;\n}\n\n/* --- FAQ (details/summary accordion) ------------------------------------------ */\n.aranova-blog-post .aranova-blog-faq-item {\n border-block-start: 1px solid var(--ara-blog-color-border);\n padding-block: calc(var(--_flow) * 0.75);\n}\n.aranova-blog-post .aranova-blog-faq-question {\n cursor: pointer;\n list-style: none;\n position: relative;\n padding-inline-end: 1.75em;\n}\n.aranova-blog-post .aranova-blog-faq-question::-webkit-details-marker {\n display: none;\n}\n/* Rotating chevron (border trick \u2014 colors via theme vars only). */\n.aranova-blog-post .aranova-blog-faq-question::after {\n content: \"\";\n position: absolute;\n inset-block-start: 50%;\n inset-inline-end: 0.35em;\n width: 0.45em;\n height: 0.45em;\n border-inline-end: 2px solid var(--ara-blog-color-text-muted);\n border-block-end: 2px solid var(--ara-blog-color-text-muted);\n transform: translateY(-70%) rotate(45deg);\n transition: transform 150ms ease;\n}\n.aranova-blog-post .aranova-blog-faq-item[open] > .aranova-blog-faq-question::after {\n transform: translateY(-30%) rotate(225deg);\n}\n.aranova-blog-post .aranova-blog-faq-question:focus-visible {\n outline: 2px solid var(--ara-blog-color-link);\n outline-offset: 2px;\n border-radius: 2px;\n}\n.aranova-blog-post .aranova-blog-faq-answer {\n padding-block-start: var(--_flow-sm);\n}\n.aranova-blog-post .aranova-blog-faq-answer > * + * {\n margin-block-start: var(--_flow-sm);\n}\n/* Progressive open/close animation where the platform supports it. */\n@supports (interpolate-size: allow-keywords) {\n .aranova-blog-post .aranova-blog-faq-item {\n interpolate-size: allow-keywords;\n }\n .aranova-blog-post .aranova-blog-faq-item::details-content {\n block-size: 0;\n overflow-y: clip;\n transition:\n block-size 200ms ease,\n content-visibility 200ms allow-discrete;\n }\n .aranova-blog-post .aranova-blog-faq-item[open]::details-content {\n block-size: auto;\n }\n}\n\n/* --- Key takeaways ------------------------------------------------------------ */\n.aranova-blog-post .aranova-blog-key-takeaways {\n background: var(--ara-blog-color-surface);\n border: 1px solid var(--ara-blog-color-border);\n border-radius: var(--_radius);\n padding: 1em 1.25em;\n}\n.aranova-blog-post .aranova-blog-key-takeaways :where(ul) {\n margin-block: 0;\n}\n.aranova-blog-post .aranova-blog-key-takeaways :where(ul) > li::marker {\n color: var(--ara-blog-color-primary);\n}\n\n/* --- Article layout (content column + optional aside rail) -----------------------\n * `.aranova-blog-layout` is a plain block on narrow viewports (the aside, when\n * present, stacks after the content). On wide viewports a grid opens ONLY when\n * an aside exists, and the article widens by exactly the rail + gap so the\n * content column keeps its `content_max_width`. */\n.aranova-blog-post .aranova-blog-layout {\n /* Flex column (not block) so the aside can be VISUALLY hoisted above the\n * content on narrow viewports while the content stays first in DOM\n * (reading order / SEO). */\n display: flex;\n flex-direction: column;\n}\n.aranova-blog-post .aranova-blog-layout > .aranova-blog-aside {\n order: -1;\n margin-block-end: var(--_flow-lg);\n}\n@media (min-width: 1100px) {\n .aranova-blog-post:has(.aranova-blog-aside) {\n max-width: calc(var(--_content-max-width) + 240px + 3rem);\n }\n .aranova-blog-post .aranova-blog-layout:has(.aranova-blog-aside) {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 240px;\n column-gap: 3rem;\n align-items: start;\n }\n .aranova-blog-post .aranova-blog-layout:has(.aranova-blog-aside) > .aranova-blog-aside {\n /* Reset the mobile hoist: grid auto-placement honors `order`, so -1 would\n * drop the aside into the FIRST column. */\n order: 0;\n margin-block-end: 0;\n position: sticky;\n top: 5rem;\n align-self: start;\n max-height: calc(100vh - 6rem);\n overflow: auto;\n }\n /* Rail on the LEFT: flip the column order (content DOM stays first for\n * reading order / SEO; only the visual columns swap). */\n .aranova-blog-post .aranova-blog-layout[data-toc-side=\"left\"]:has(.aranova-blog-aside) {\n grid-template-columns: 240px minmax(0, 1fr);\n }\n /* Horizontal collapse, CLOSED: the rail column shrinks to the compact toggle\n * (auto) and the article reflows into the reclaimed space. */\n .aranova-blog-post\n .aranova-blog-layout:has(\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"]:not([data-open=\"true\"])\n ) {\n grid-template-columns: minmax(0, 1fr) auto;\n }\n .aranova-blog-post\n .aranova-blog-layout[data-toc-side=\"left\"]:has(\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"]:not([data-open=\"true\"])\n ) {\n grid-template-columns: auto minmax(0, 1fr);\n }\n .aranova-blog-post\n .aranova-blog-layout[data-toc-side=\"left\"]:has(.aranova-blog-aside)\n > .aranova-blog-content {\n order: 2;\n }\n .aranova-blog-post\n .aranova-blog-layout[data-toc-side=\"left\"]:has(.aranova-blog-aside)\n > .aranova-blog-aside {\n order: 1;\n }\n}\n\n/* --- Byline / meta row (chrome) --------------------------------------------------- */\n.aranova-blog-post .aranova-blog-meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.35em 0.5em;\n font-size: var(--_small-size);\n color: var(--ara-blog-color-text-muted);\n padding-block-end: var(--_flow);\n border-block-end: 1px solid var(--ara-blog-color-border);\n}\n.aranova-blog-post .aranova-blog-meta-item {\n display: inline-flex;\n align-items: baseline;\n gap: 0.5em;\n white-space: nowrap;\n}\n.aranova-blog-post .aranova-blog-meta-tags {\n display: inline-flex;\n flex-wrap: wrap;\n gap: 0.35em;\n margin-inline-start: auto;\n}\n.aranova-blog-post .aranova-blog-meta-tag {\n background: var(--ara-blog-color-surface);\n border: 1px solid var(--ara-blog-color-border);\n border-radius: 999px;\n padding: 0.1em 0.7em;\n white-space: nowrap;\n}\n\n/* --- Breadcrumbs (chrome) ---------------------------------------------------------- */\n.aranova-blog-post .aranova-blog-breadcrumbs {\n font-size: var(--_small-size);\n color: var(--ara-blog-color-text-muted);\n}\n.aranova-blog-post .aranova-blog-breadcrumbs ol {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: 0.2em 0.5em;\n}\n.aranova-blog-post .aranova-blog-breadcrumbs li {\n display: inline-flex;\n align-items: baseline;\n gap: 0.5em;\n}\n.aranova-blog-post .aranova-blog-breadcrumbs a {\n color: var(--ara-blog-color-text-muted);\n text-decoration: none;\n}\n.aranova-blog-post .aranova-blog-breadcrumbs a:hover {\n color: var(--ara-blog-color-link);\n text-decoration: underline;\n text-underline-offset: 0.15em;\n}\n.aranova-blog-post .aranova-blog-breadcrumbs [aria-current=\"page\"] {\n color: var(--ara-blog-color-text);\n}\n\n/* --- Table of contents (chrome) -----------------------------------------------------\n * Zero-JS shell: a native details/summary disclosure. Chevron follows the FAQ\n * accordion's border-trick pattern (theme vars only, no raw colors). */\n.aranova-blog-post .aranova-blog-toc {\n font-size: var(--_small-size);\n background: var(--ara-blog-color-surface);\n border: 1px solid var(--ara-blog-color-border);\n border-radius: var(--_radius);\n padding: 0.85em 1.1em;\n}\n.aranova-blog-post .aranova-blog-toc-title {\n cursor: pointer;\n list-style: none;\n position: relative;\n padding-inline-end: 1.75em;\n font-weight: 600;\n color: var(--_heading-color);\n}\n.aranova-blog-post .aranova-blog-toc-title::-webkit-details-marker {\n display: none;\n}\n.aranova-blog-post .aranova-blog-toc-title::after {\n content: \"\";\n position: absolute;\n inset-block-start: 50%;\n inset-inline-end: 0.35em;\n width: 0.45em;\n height: 0.45em;\n border-inline-end: 2px solid var(--ara-blog-color-text-muted);\n border-block-end: 2px solid var(--ara-blog-color-text-muted);\n transform: translateY(-70%) rotate(45deg);\n transition: transform 150ms ease;\n}\n.aranova-blog-post .aranova-blog-toc[open] > .aranova-blog-toc-title::after {\n transform: translateY(-30%) rotate(225deg);\n}\n.aranova-blog-post .aranova-blog-toc-title:focus-visible {\n outline: 2px solid var(--ara-blog-color-link);\n outline-offset: 2px;\n border-radius: 2px;\n}\n.aranova-blog-post .aranova-blog-toc ol {\n list-style: none;\n margin: 0.75em 0 0;\n padding: 0;\n}\n.aranova-blog-post .aranova-blog-toc li {\n margin: 0;\n}\n.aranova-blog-post .aranova-blog-toc li + li {\n margin-block-start: 0.3em;\n}\n.aranova-blog-post .aranova-blog-toc li[data-depth=\"3\"] {\n padding-inline-start: 0.9em;\n font-size: 0.94em;\n}\n.aranova-blog-post .aranova-blog-toc a {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n overflow: hidden;\n line-height: 1.35;\n color: var(--ara-blog-color-text-muted);\n text-decoration: none;\n}\n.aranova-blog-post .aranova-blog-toc a:hover {\n color: var(--ara-blog-color-link);\n text-decoration: underline;\n text-underline-offset: 0.15em;\n}\n\n/* --- Table of contents (client island) ------------------------------------------\n * Animated companion to the zero-JS shell above: same box, but a real <button>\n * header (aria-expanded) and a grid-template-rows 0fr\u21941fr collapse. The global\n * reduced-motion block at the end of this sheet disables the transition. */\n.aranova-blog-post .aranova-blog-toc-panel {\n font-size: var(--_small-size);\n background: var(--ara-blog-color-surface);\n border: 1px solid var(--ara-blog-color-border);\n border-radius: var(--_radius);\n padding: 0.85em 1.1em;\n}\n.aranova-blog-post .aranova-blog-toc-panel-title {\n /* Button reset \u2014 host sites may or may not run Tailwind preflight. */\n appearance: none;\n background: none;\n border: 0;\n margin: 0;\n padding: 0;\n padding-inline-end: 1.75em;\n width: 100%;\n text-align: start;\n font: inherit;\n cursor: pointer;\n position: relative;\n font-weight: 600;\n color: var(--_heading-color);\n}\n/* Rotating chevron (border trick \u2014 same pattern as the FAQ / zero-JS TOC). */\n.aranova-blog-post .aranova-blog-toc-panel-title::after {\n content: \"\";\n position: absolute;\n inset-block-start: 50%;\n inset-inline-end: 0.35em;\n width: 0.45em;\n height: 0.45em;\n border-inline-end: 2px solid var(--ara-blog-color-text-muted);\n border-block-end: 2px solid var(--ara-blog-color-text-muted);\n transform: translateY(-70%) rotate(45deg);\n transition: transform 250ms ease;\n}\n.aranova-blog-post\n .aranova-blog-toc-panel[data-open=\"true\"]\n > .aranova-blog-toc-panel-title::after {\n transform: translateY(-30%) rotate(225deg);\n}\n.aranova-blog-post .aranova-blog-toc-panel-title:focus-visible {\n outline: 2px solid var(--ara-blog-color-link);\n outline-offset: 2px;\n border-radius: 2px;\n}\n/* Collapsible body: the grid row animates 0fr\u21941fr; the clip wrapper hides overflow. */\n.aranova-blog-post .aranova-blog-toc-panel-body {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows 250ms ease;\n}\n.aranova-blog-post .aranova-blog-toc-panel[data-open=\"true\"] > .aranova-blog-toc-panel-body {\n grid-template-rows: 1fr;\n}\n/* Horizontal collapse: a clean binary toggle along the inline axis. Open = the\n * normal rail; closed = the whole panel is a compact chevron chip and the rail\n * grid column reclaims the space (via `:has()` on the layout, below). No width\n * animation: grid `auto` tracks can't interpolate, and a sliding clip either\n * overflows the sticky aside (horizontal scrollbar) or reflows the entries \u2014\n * instant + correct beats janky. */\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"]\n > .aranova-blog-toc-panel-body {\n display: block;\n grid-template-rows: none;\n}\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"]:not([data-open=\"true\"])\n > .aranova-blog-toc-panel-body {\n display: none;\n}\n/* Closed panel compacts to a small rounded chevron chip: the label is clipped\n * out of a square toggle box and the panel hugs it (the rail's `auto` grid\n * column then shrinks to the chip). */\n.aranova-blog-post .aranova-blog-toc-panel[data-collapse=\"horizontal\"]:not([data-open=\"true\"]) {\n width: fit-content;\n padding: 0.6em;\n}\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"]:not([data-open=\"true\"])\n > .aranova-blog-toc-panel-title {\n display: block;\n width: 1.4em;\n height: 1.4em;\n overflow: hidden;\n text-indent: 150%;\n white-space: nowrap;\n padding-inline-end: 0;\n}\n/* Horizontal chevron points along the fold axis (\u2039/\u203A), flipping on open/close\n * per side, instead of the vertical \u2304/\u2303. */\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"]\n > .aranova-blog-toc-panel-title::after {\n inset-inline: auto 0.5em;\n}\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"]:not([data-open=\"true\"])\n > .aranova-blog-toc-panel-title::after {\n inset-inline: 0;\n margin-inline: auto;\n}\n/* Left rail: open folds left (\u2039 = rotate 135deg), closed expands right (\u203A = -45deg). */\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"][data-side=\"left\"][data-open=\"true\"]\n > .aranova-blog-toc-panel-title::after {\n transform: translateY(-30%) rotate(135deg);\n}\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"][data-side=\"left\"]:not([data-open=\"true\"])\n > .aranova-blog-toc-panel-title::after {\n transform: translateY(-70%) rotate(-45deg);\n}\n/* Right rail: mirror \u2014 open folds right (\u203A), closed expands left (\u2039). */\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"][data-side=\"right\"][data-open=\"true\"]\n > .aranova-blog-toc-panel-title::after {\n transform: translateY(-70%) rotate(-45deg);\n}\n.aranova-blog-post\n .aranova-blog-toc-panel[data-collapse=\"horizontal\"][data-side=\"right\"]:not([data-open=\"true\"])\n > .aranova-blog-toc-panel-title::after {\n transform: translateY(-30%) rotate(135deg);\n}\n.aranova-blog-post .aranova-blog-toc-panel-clip {\n overflow: hidden;\n min-height: 0;\n}\n.aranova-blog-post .aranova-blog-toc-panel ol {\n list-style: none;\n margin: 0.75em 0 0;\n padding: 0;\n}\n.aranova-blog-post .aranova-blog-toc-panel li {\n margin: 0;\n}\n.aranova-blog-post .aranova-blog-toc-panel li + li {\n margin-block-start: 0.3em;\n}\n.aranova-blog-post .aranova-blog-toc-panel li[data-depth=\"3\"] {\n padding-inline-start: 0.9em;\n font-size: 0.94em;\n}\n.aranova-blog-post .aranova-blog-toc-panel-link {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n overflow: hidden;\n line-height: 1.35;\n color: var(--ara-blog-color-text-muted);\n text-decoration: none;\n border-inline-start: 2px solid transparent;\n padding-inline-start: 0.6em;\n margin-inline-start: -0.75em;\n transition: color 150ms ease;\n}\n.aranova-blog-post .aranova-blog-toc-panel-link:hover {\n color: var(--ara-blog-color-link);\n text-decoration: underline;\n text-underline-offset: 0.15em;\n}\n/* Scroll-spy active entry: primary color + inline-start accent. */\n.aranova-blog-post .aranova-blog-toc-panel-link[data-active=\"true\"] {\n color: var(--ara-blog-color-primary);\n border-inline-start-color: var(--ara-blog-color-primary);\n font-weight: 600;\n}\n\n/* --- Reading progress (client island) --------------------------------------------\n * Fixed strip along the top of the viewport; fill = scaleX(scrolled fraction),\n * driven directly from rAF (no transition \u2014 it tracks position, so the\n * reduced-motion block has nothing to disable and the bar stays useful). */\n.aranova-blog-post .aranova-blog-progress {\n position: fixed;\n inset-block-start: 0;\n inset-inline: 0;\n z-index: 60;\n background: var(--ara-blog-color-primary);\n transform: scaleX(0);\n transform-origin: 0 50%;\n pointer-events: none;\n}\n\n/* --- Vertical rhythm ------------------------------------------------------------\n * Kept AFTER the element rules: the owl selector and the :where()-wrapped\n * element rules all sit at (0,1,0), so source order lets rhythm win over the\n * elements' margin resets, and the per-heading overrides below win over the\n * owl. `:is(...)` targets both today's direct-child layout and the later\n * `.aranova-blog-content` wrapper at class specificity. */\n:is(.aranova-blog-post, .aranova-blog-content) > * + * {\n margin-block-start: var(--_flow);\n}\n:is(.aranova-blog-post, .aranova-blog-content) > :where(h2) {\n margin-block-start: var(--_flow-section);\n}\n:is(.aranova-blog-post, .aranova-blog-content) > :where(h3, h4) {\n margin-block-start: var(--_flow-lg);\n}\n:is(.aranova-blog-post, .aranova-blog-content) > :where(figure) {\n margin-block: var(--_flow-lg);\n}\n:is(.aranova-blog-post, .aranova-blog-content) > :where(hr) {\n margin-block: var(--_flow-section);\n}\n\n/* --- Motion ------------------------------------------------------------------\n * Opt-in: a later phase stamps data-motion=\"subtle\" on the article; without\n * the attribute nothing animates. */\n@media (prefers-reduced-motion: no-preference) {\n .aranova-blog-post[data-motion=\"subtle\"] {\n animation: aranova-blog-fade-up 300ms ease-out both;\n }\n}\n@keyframes aranova-blog-fade-up {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: none;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .aranova-blog-post,\n .aranova-blog-post *,\n .aranova-blog-post *::before,\n .aranova-blog-post *::after {\n animation: none !important;\n transition: none !important;\n }\n}\n";
|
|
190
|
+
|
|
191
|
+
/** Fallback locale for date formatting (the agency's home market). */
|
|
192
|
+
declare const DEFAULT_BLOG_LOCALE = "en-CA";
|
|
193
|
+
interface BlogPostMetaProps {
|
|
194
|
+
/** ISO date(-time) string; invalid/absent values render nothing. */
|
|
195
|
+
publishedAt?: string;
|
|
196
|
+
/** ISO date(-time) string; only shown when it differs from `publishedAt`'s day. */
|
|
197
|
+
updatedAt?: string | null;
|
|
198
|
+
/** Whole minutes (see `estimateReadingTime`); non-positive values render nothing. */
|
|
199
|
+
readingTimeMinutes?: number;
|
|
200
|
+
tags?: readonly string[];
|
|
201
|
+
/** BCP-47 locale for date formatting. Default {@link DEFAULT_BLOG_LOCALE}. */
|
|
202
|
+
locale?: string;
|
|
203
|
+
/** Site-supplied class merged onto the fixed `.aranova-blog-meta` class. */
|
|
204
|
+
className?: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Post byline row: published date (+ "Updated …" when meaningfully different),
|
|
208
|
+
* "· N min read", and tag pills. Renders `null` when there is nothing to show.
|
|
209
|
+
* Styled by the `.aranova-blog-meta` section of the base CSS (muted small row
|
|
210
|
+
* with a bottom divider).
|
|
211
|
+
*/
|
|
212
|
+
declare function BlogPostMeta({ publishedAt, updatedAt, readingTimeMinutes, tags, locale, className, }: BlogPostMetaProps): ReactNode;
|
|
213
|
+
|
|
214
|
+
interface BlogTocShellProps {
|
|
215
|
+
/** The post outline — see `extractBlogHeadings`. Empty array renders nothing. */
|
|
216
|
+
headings: readonly BlogHeading[];
|
|
217
|
+
/** Disclosure summary label. Default `"On this page"`. */
|
|
218
|
+
title?: string;
|
|
219
|
+
/** Site-supplied class merged onto the fixed `.aranova-blog-toc` class. */
|
|
220
|
+
className?: string;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* No-JS table of contents: a native `<details open>` disclosure holding an
|
|
224
|
+
* anchor list (h3 entries indented under their h2). Styled by the
|
|
225
|
+
* `.aranova-blog-toc` section of the base CSS (chevron consistent with the
|
|
226
|
+
* FAQ disclosure pattern).
|
|
227
|
+
*/
|
|
228
|
+
declare function BlogTocShell({ headings, title, className, }: BlogTocShellProps): ReactNode;
|
|
229
|
+
|
|
230
|
+
interface BlogPostProps extends Omit<BlogPostProps$1, "tocSlot" | "progressSlot"> {
|
|
231
|
+
/** Render the fixed top-of-viewport reading progress bar. Default `false`. */
|
|
232
|
+
showProgress?: boolean;
|
|
233
|
+
/**
|
|
234
|
+
* ToC collapse axis: `"vertical"` (default) folds up under the title,
|
|
235
|
+
* `"horizontal"` folds sideways toward `tocSide`. Only affects the animated
|
|
236
|
+
* `TocPanel` island rendered here.
|
|
237
|
+
*/
|
|
238
|
+
tocCollapse?: "vertical" | "horizontal";
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* `BlogPost` with the animated islands wired in: `showToc` renders the
|
|
242
|
+
* collapsible scroll-spy `TocPanel` (instead of core's zero-JS shell) and
|
|
243
|
+
* `showProgress` adds the `ReadingProgress` bar to the header chrome. All
|
|
244
|
+
* other props pass straight through to tracking-core's `BlogPost`.
|
|
245
|
+
*/
|
|
246
|
+
declare function BlogPost({ showProgress, tocCollapse, ...props }: BlogPostProps): ReactNode;
|
|
247
|
+
|
|
248
|
+
export { BLOG_BASE_CSS, type BlogBreadcrumbItem, BlogBreadcrumbs, type BlogBreadcrumbsProps, BlogClassNameOverrides, BlogClient, type BlogComponents, BlogHeading, BlogImage, BlogPost, BlogPostMeta, type BlogPostMetaProps, type BlogPostProps, BlogPostView, type BlogPostViewProps, BlogTheme, BlogThemeOverride, BlogTocShell, type BlogTocShellProps, DEFAULT_BLOG_LOCALE, createDefaultComponents };
|