@getvitops/astro 8.3.0-alpha.3 → 8.3.0-alpha.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @getvitops/astro
2
2
 
3
+ ## 8.3.0-alpha.5
4
+
5
+ ### Minor Changes
6
+
7
+ - bb61a35: **Call tracking numbers: show an ad visitor a number that attributes the phone call.** Two additive config blocks, no CSS, no breaking change — all formats unaffected.
8
+
9
+ - **`site.ads.google.callTracking`** — Google Ads _website call conversions_. `{ label, number, cache? }` makes the consent-gated Google tag fetch a Google forwarding number (Canada supported) and swap it in for visitors who arrived from a Google ad. Google replaces text only, so the capture script also rewrites the matching `tel:` href. Needs `pixelId` and `site.tracking.enabled`; rejected on any other platform (only Google can put a number on the website — Microsoft's exist only inside the ad, in the US/UK/AU).
10
+ - **`site.tracking.phoneNumbers`** — per-platform numbers for every other platform: the real `number`, `sources` keyed by `site.ads` platform (plus `default-paid`), and an informational `provider`. You provision the numbers with a call-tracking service (Twilio, CallRail, …). The landing URL decides first, then the `_ac` cookie, so a visitor who declines `marketing` sees a tracking number on the landing page only. An organic visitor is never swapped, and the server-rendered HTML always carries the real number.
11
+ - The `tel:` tap beacon now carries which number was shown (`phoneSource`), and the conversion notification says so.
12
+ - `vitops ads lint` flags a call-tracking provider that is not in `site.legal.privacyPolicy.processors`, and notes that `callTracking` plus `conversionLabel.call` can count one call twice (make one secondary in Google Ads).
13
+ - `@getvitops/utils/tracking` exports the pure `selectPhoneNumber()` and helpers; the capture script mirrors it and `tracking.test.ts` pins the two.
14
+ - `@getvitops/skills` gains a **`vitops-ads`** skill (overview plus a `call-tracking.md` topic). The templates gitignore synced skills by prefix (`vitops-*`, `emdash-*`), so it needs no `.gitignore` line of its own.
15
+
16
+ `@getvitops/core`, `@getvitops/vite` and `@getvitops/emdash` — no consumer-facing change in these packages.
17
+
18
+ - 4d94c8b: **Added: `site.chrome` — a configurable masthead and nav behaviour.** Non-breaking.
19
+
20
+ - `site.chrome.masthead` (new `@getvitops/config` schema): `position` (`static|sticky|fixed`), `behavior` (`always|hide-on-scroll`), `surface` (`solid|glass|transparent|transparent-until-scrolled`), `tone` (a `colors.roles` key), `layout` (`full|inset|floating`), `width`, `elevation` (`shadow`, `border`, `onScroll`), `height` and `inset`. `height` and `inset` take fluid lengths (`calc()`, `clamp()`, `min()`, `max()`, with `cqi`/`vw`/… units), checked against a closed grammar because they are written into CSS. `validateConfig` rejects a `tone` or `shadow` the design system does not define (only when the default theme states them directly, not via `extends`).
21
+ - `site.chrome.nav`: `compact` (`drawer|dropdown`), `drawerSide`, `breakpoint`/`mobileBreakpoint`/`megaBreakpoint`, `depth`, `megaDepth`.
22
+ - `<Masthead config={site}>` (`@getvitops/astro`, slots `brand`/default/`actions`) and `resolveMasthead()` / `resolveNavOptions()`. A prop overrides one field per layout. The key is `chrome`/`masthead` on purpose: `<header>` is valid in any section and `banner` already means the announcement bar and the landmark role.
23
+ - `<SiteNav>` gains `compact="dropdown"` (a columnar panel anchored under the toggle), `drawerSide` (previously hardcoded to the end edge in Astro), `megaDepth` and `megaPanel`. `.sitenav--mega` now styles second-level items as column headings and has an optional featured column.
24
+ - `@getvitops/emdash`: `VitopsMegaPanel` fills that column from the widget area `mega-<item url slug>` (or a `mega-area:<name>` class on the menu item). No area means no column.
25
+ - `@getvitops/create` (emdash template): the layout renders through `<Masthead>`; `site.json` ships a sticky glass masthead with a shadow once scrolled. The hand-written `.navbar { block-size: 64px }` is gone (use `masthead.height`).
26
+ - CSS (`patterns/masthead.css`, `patterns/sitenav.css`): **all formats**, including tailwind, where the patterns are inlined into `@layer components`. Hide-on-scroll and the `*-on-scroll` states are `scroll-state()` container queries that only subtract from a settled (solid, elevated, visible) bar, so a browser without them (Firefox today) shows that bar rather than a transparent one. A sticky or fixed masthead sets `--header-offset` / `--top-offset` so anchors land below it.
27
+ - `@getvitops/generator`: new `TIERS.masthead`; `TIERS.sitenav` documents the new props.
28
+
29
+ ### Patch Changes
30
+
31
+ - 785b8b3: Docs only: the `site.ads.google.conversionLabel` field descriptions and `vitops ads` now state which Google Ads category each conversion action needs (`call` → Phone call lead, `email` → Contact, `form` → Submit lead form). The category is set on the action in Google Ads, not by the tag, so a wrong one reports under the wrong goal with no code-side signal. `vitopsFormConversion()`'s docblock also warns against firing a form conversion from a success-page query parameter. No behaviour change.
32
+ - Updated dependencies [785b8b3]
33
+ - Updated dependencies [bb61a35]
34
+ - Updated dependencies [4d94c8b]
35
+ - @getvitops/config@8.3.0-alpha.5
36
+ - @getvitops/utils@8.3.0-alpha.5
37
+ - @getvitops/generator@8.3.0-alpha.5
38
+ - @getvitops/core@8.3.0-alpha.5
39
+ - @getvitops/vite@8.3.0-alpha.5
40
+
41
+ ## 8.3.0-alpha.4
42
+
43
+ ### Minor Changes
44
+
45
+ - 322dd6a: Block fixes from the vitops.ca build (findings 11–13, 15, 17, 18, 21).
46
+
47
+ - **Steps.** The marker is an outlined disc in the tone (number and ring in `--tone-solid`, falling back to `currentColor`), so a steps block with no tone no longer shows black discs with no number. Items keep `align-content: start` and no `.rhythm li + li` margin, so markers in a row line up. Two modifiers through the block's `className`: `steps--cards` (each step in a card) and `steps--animated` (the connector draws and the marker fills as each step scrolls in; static under reduced motion or without scroll-driven animations). `steps` is now a config pattern: `patterns.items.steps` in `site.json` owns the card geometry (`content` group tokens) and the marker size (`--steps-marker-size`, `--steps-line-width`); both templates and the reference config ship an entry. all formats.
48
+ - **Stats.** `<wc-counter>` leaves a figure it cannot count as authored (`"1 in 6"`, `"24/7"`, `"—"`) instead of counting down from zero to a wrong value. `animate: false` on the block still turns the count-up off for a whole block.
49
+ - **Logos.** `vitops_logos` takes an optional `kicker`, `note` and `linkLabel`/`linkHref`; with any of them set, the row is wrapped in `.logos-block`. The list keeps the block's attributes either way.
50
+ - **Pricing.** Plans take `yearly` (a second price line) and `note` (under the plan name).
51
+ - **`<SiteFooter>`** has a `brand` named slot, rendered as the first column (`.sitefooter__brand`) in the site's own markup.
52
+
53
+ ### Patch Changes
54
+
55
+ - Updated dependencies [99e66a2]
56
+ - Updated dependencies [00a9d86]
57
+ - Updated dependencies [d64fb20]
58
+ - Updated dependencies [322dd6a]
59
+ - Updated dependencies [322dd6a]
60
+ - @getvitops/core@8.3.0-alpha.4
61
+ - @getvitops/generator@8.3.0-alpha.4
62
+ - @getvitops/utils@8.3.0-alpha.4
63
+ - @getvitops/vite@8.3.0-alpha.4
64
+ - @getvitops/config@8.3.0-alpha.4
65
+
3
66
  ## 8.3.0-alpha.3
4
67
 
5
68
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import { i as TRACKING_COOKIE, n as ResolveTrackingContext, o as TrackingCategor
2
2
  import { Config, LegalOutput, SiteFont, StylesheetFormat } from "@getvitops/generator";
3
3
  import { generateIconInclude } from "@getvitops/utils";
4
4
  import { AdProvider } from "@getvitops/utils/ads";
5
- import { AdPropertyConfig } from "@getvitops/config";
5
+ import { AdPropertyConfig, ChromeNavConfig, MastheadBehavior, MastheadConfig, MastheadLayout, MastheadPosition, MastheadSurface, MastheadWidth } from "@getvitops/config";
6
6
  import { OutputKind } from "@getvitops/utils/media";
7
7
  import { AstroIntegration } from "astro";
8
8
  export * from "@getvitops/utils";
@@ -787,7 +787,7 @@ export default function vitops(opts?: GetvitopsOptions): AstroIntegration;
787
787
  * type, not a copy — so a plain object is still enough for a consumer to pass, and a field the
788
788
  * schema renames fails here instead of silently reading `undefined`.
789
789
  */
790
- type GetvitopsAdProperty = Pick<AdPropertyConfig, 'accountId' | 'pixelId' | 'conversionLabel' | 'conversionActionId' | 'reporting' | 'category' | 'enabled'>;
790
+ type GetvitopsAdProperty = Pick<AdPropertyConfig, 'accountId' | 'pixelId' | 'conversionLabel' | 'conversionActionId' | 'callTracking' | 'reporting' | 'category' | 'enabled'>;
791
791
  type GetvitopsAdsOptions = Partial<Record<AdProvider, GetvitopsAdProperty>>;
792
792
  /** One ad tag. `key` is the platform, so a caller can find one without matching prose. */
793
793
  interface ResolvedAdTag extends GatedTag {
@@ -878,6 +878,75 @@ export declare function slugFromContent(file: string): string | undefined;
878
878
  * flip presented as a fact.
879
879
  */
880
880
  export declare function gitLastmod(options?: GitLastmodOptions): Promise<(item: GetvitopsSitemapEntry) => GetvitopsSitemapEntry>;
881
+ /**
882
+ * Pure decisions behind `<SiteNav />` / `<SiteFooter />` — which menus render, to what
883
+ * depth, at which container breakpoint, and how an item reads. Kept out of the `.astro`
884
+ * files so they are asserted in `sitenav.test.ts` without rendering (the same split
885
+ * `tree.ts` makes for `<Tree />`).
886
+ *
887
+ * The item shape is structural, not imported from `emdash`: `@getvitops/astro` has no
888
+ * dependency on the CMS, and an EmDash `MenuItem` already satisfies it.
889
+ */
890
+ type NavBreakpoint = 'sm' | 'md' | 'lg' | 'xl';
891
+ /** What the toggle opens below a nav's breakpoint. */
892
+ type NavCompactMode = 'drawer' | 'dropdown';
893
+ type NavDrawerSide = 'start' | 'end';
894
+ /** A masthead override: any `site.chrome.masthead` field, plus `elevation` merged key-by-key. */
895
+ type MastheadOptions = MastheadConfig;
896
+ interface ResolvedMasthead {
897
+ position: MastheadPosition;
898
+ behavior: MastheadBehavior;
899
+ surface: MastheadSurface;
900
+ tone: string | undefined;
901
+ layout: MastheadLayout;
902
+ width: MastheadWidth;
903
+ shadow: string | undefined;
904
+ border: boolean;
905
+ elevateOnScroll: boolean;
906
+ height: string | undefined;
907
+ inset: string | undefined;
908
+ }
909
+ /**
910
+ * What `resolveMasthead` needs from a config — a whole `Config`, or just `{ site: { chrome } }`.
911
+ * Deliberately loose: a plain `import site from './site.json'` types every enum as `string`, which
912
+ * the strict `ChromeConfig` would reject (the same trap `resolveOrganizationName` fell into). The
913
+ * values are checked at runtime instead, with the names of the allowed ones in the error.
914
+ */
915
+ interface ChromeSource {
916
+ site?: {
917
+ chrome?: {
918
+ masthead?: Record<string, any>;
919
+ nav?: Record<string, any>;
920
+ };
921
+ };
922
+ }
923
+ export declare function resolveMasthead(config: ChromeSource | undefined, override?: MastheadOptions): ResolvedMasthead;
924
+ /** Modifier classes on `.masthead`. */
925
+ export declare function mastheadClass(m: ResolvedMasthead): string[];
926
+ /**
927
+ * The `.tone` custom properties for a role, as a `style` string. Each is a `var()` chain that
928
+ * degrades to the surface role, so a role the site never defined is a neutral bar, not a
929
+ * transparent one. `<role>:solid` fills with the role's solid and its computed foreground.
930
+ */
931
+ export declare function mastheadStyle(m: ResolvedMasthead): string | undefined;
932
+ /**
933
+ * Page-level rule for a custom height and inset — `--header-offset` reads both from `:root`, and
934
+ * the vars inherit down to the masthead. The inset is written only for a layout that has one (a
935
+ * full-width bar sits flush) and then always, defaulted to the same `0.75rem` the element falls
936
+ * back to, since `--header-offset` adds `--masthead-inset` unconditionally.
937
+ */
938
+ export declare function mastheadRootCss(m: ResolvedMasthead): string | undefined;
939
+ /** `<SiteNav>` props from `site.chrome.nav`, with an optional override. */
940
+ interface ResolvedNavOptions {
941
+ breakpoint?: NavBreakpoint;
942
+ mobileBreakpoint?: NavBreakpoint;
943
+ megaBreakpoint?: NavBreakpoint;
944
+ depth?: number;
945
+ megaDepth?: number;
946
+ compact?: NavCompactMode;
947
+ drawerSide?: NavDrawerSide;
948
+ }
949
+ export declare function resolveNavOptions(config: ChromeSource | undefined, override?: ChromeNavConfig): ResolvedNavOptions;
881
950
  interface ResolveSeoDefaultsOptions {
882
951
  /** Build locale. Default `site.defaultLocale`; a value with no entry falls back to the default. */
883
952
  locale?: string;
@@ -890,5 +959,5 @@ export declare function resolveSeoDefaults(config: Config, options?: ResolveSeoD
890
959
  * setting `openGraph.locale` in code doesn't discard the config's fallback image.
891
960
  */
892
961
  export declare function mergeSeoDefaults(fromConfig: GetvitopsSeoOptions, override: GetvitopsSeoOptions | undefined): GetvitopsSeoOptions;
893
- export { type AnalyticsStrategy, type ClarityOptions, type ConsentCategory, type ContentSecurityPolicyOptions, type GatedTag, type GetvitopsAdProperty, type GetvitopsAdsOptions, type GetvitopsAnalyticsOptions, type GetvitopsConsentOptions, type GetvitopsCssOptions, type GetvitopsFaviconOptions, type GetvitopsMediaOptions, type GetvitopsOptions, type GetvitopsSeoOptions, type GetvitopsSitemapChangeFreq, type GetvitopsSitemapEntry, type GetvitopsSitemapOptions, type GetvitopsTrackingOptions, type GitLastmodOptions, type GoogleAnalyticsOptions, type InlineScriptOptions, type MatomoOptions, type OptionalConsentCategory, type PlausibleOptions, type ResolveAdsContext, type ResolveAnalyticsContext, type ResolveSeoDefaultsOptions, type ResolveTrackingContext, type ResolvedAdTag, type ResolvedAds, type ResolvedAnalytics, type ResolvedSeo, type ResolvedTag, type ResolvedTracking, type SeoContext, type SeoNameTag, type SeoOgType, type SeoPropertyTag, type SeoProps, type SeoTwitterCard, TRACKING_COOKIE, type TrackingCategory, resolveTracking };
962
+ export { type AnalyticsStrategy, type ClarityOptions, type ConsentCategory, type ContentSecurityPolicyOptions, type GatedTag, type GetvitopsAdProperty, type GetvitopsAdsOptions, type GetvitopsAnalyticsOptions, type GetvitopsConsentOptions, type GetvitopsCssOptions, type GetvitopsFaviconOptions, type GetvitopsMediaOptions, type GetvitopsOptions, type GetvitopsSeoOptions, type GetvitopsSitemapChangeFreq, type GetvitopsSitemapEntry, type GetvitopsSitemapOptions, type GetvitopsTrackingOptions, type GitLastmodOptions, type GoogleAnalyticsOptions, type InlineScriptOptions, type MastheadOptions, type MatomoOptions, type OptionalConsentCategory, type PlausibleOptions, type ResolveAdsContext, type ResolveAnalyticsContext, type ResolveSeoDefaultsOptions, type ResolveTrackingContext, type ResolvedAdTag, type ResolvedAds, type ResolvedAnalytics, type ResolvedMasthead, type ResolvedNavOptions, type ResolvedSeo, type ResolvedTag, type ResolvedTracking, type SeoContext, type SeoNameTag, type SeoOgType, type SeoPropertyTag, type SeoProps, type SeoTwitterCard, TRACKING_COOKIE, type TrackingCategory, resolveTracking };
894
963
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":["MediaOutputKind"],"sources":["../src/analytics.ts","../src/seo.ts","../src/integration.ts","../src/ads.ts","../src/csp.ts","../src/lastmod.ts","../src/seo-defaults.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":["MediaOutputKind"],"sources":["../src/analytics.ts","../src/seo.ts","../src/integration.ts","../src/ads.ts","../src/csp.ts","../src/lastmod.ts","../src/components/sitenav.ts","../src/components/masthead.ts","../src/seo-defaults.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -14,6 +14,7 @@ import { t } from "@getvitops/utils/content";
14
14
  import { createHash } from "node:crypto";
15
15
  import { execFile } from "node:child_process";
16
16
  import { promisify } from "node:util";
17
+ import { MASTHEAD_BEHAVIORS, MASTHEAD_LAYOUTS, MASTHEAD_POSITIONS, MASTHEAD_SURFACES, MASTHEAD_WIDTHS, NAV_BREAKPOINTS, NAV_COMPACT_MODES, NAV_DRAWER_SIDES } from "@getvitops/config";
17
18
 
18
19
  export * from "@getvitops/utils"
19
20
 
@@ -235,6 +236,8 @@ function consentCategories(tags) {
235
236
  * config file is not a plausible thing to allow either.
236
237
  */
237
238
  const SAFE_ID = /^[\w.-]+$/;
239
+ /** A displayed phone number: digits and the punctuation people write around them. */
240
+ const SAFE_PHONE = /^\+?[\d\s().-]{7,20}$/;
238
241
  /** Resolve the configured ad properties into the tags `<Ads />` emits. */
239
242
  function resolveAds(ads, ctx = {}) {
240
243
  const warnings = [];
@@ -276,6 +279,15 @@ function resolveAds(ads, ctx = {}) {
276
279
  conversionLabel: cleanLabels
277
280
  } : rest;
278
281
  }
282
+ if (entry.callTracking) {
283
+ const { label, number } = entry.callTracking;
284
+ const bad = !SAFE_ID.test(label) ? `callTracking.label "${label}" is not a valid label` : !SAFE_PHONE.test(number) ? `callTracking.number "${number}" is not a valid phone number` : !platform.websiteCallTracking ? `${platform.name} has no website call tracking` : null;
285
+ if (bad) {
286
+ warnings.push(`ads: ${provider} ${bad}. Website call tracking skipped.`);
287
+ const { callTracking: _drop, ...rest } = safeEntry;
288
+ safeEntry = rest;
289
+ }
290
+ }
279
291
  const category = entry.category ?? "marketing";
280
292
  tags.push({
281
293
  key: provider,
@@ -1132,7 +1144,9 @@ function vitops(opts = {}) {
1132
1144
  enabled: true,
1133
1145
  category: tracking.category,
1134
1146
  cookies: tracking.cookies,
1135
- abVariantCookie: experimentsConfig?.primaryCookie ?? null
1147
+ abVariantCookie: experimentsConfig?.primaryCookie ?? null,
1148
+ phoneNumbers: trackingOpts?.phoneNumbers ?? null,
1149
+ googleCallTracking: !!adsConfig?.properties?.google?.callTracking
1136
1150
  } : null,
1137
1151
  experiments: experimentsConfig?.experiments ?? {},
1138
1152
  matomoAbVariantDimensions: experimentsConfig?.matomoAbVariantDimensions ?? {},
@@ -1583,5 +1597,155 @@ function resolveSeo(defaults, props, ctx) {
1583
1597
  };
1584
1598
  }
1585
1599
 
1586
- export { TRACKING_COOKIE, consentCategories, contentSecurityPolicy, cspScriptHash, vitops as default, gitLastmod, inlineScriptHashes, mergeSeoDefaults, resolveAds, resolveAnalytics, resolveSeo, resolveSeoDefaults, resolveTracking, routeFromPage, slugFromContent };
1600
+ // src/components/masthead.ts
1601
+ /**
1602
+ * Pure decisions behind `<Masthead />` and the `<SiteNav />` options: merge `site.chrome` with a
1603
+ * per-layout override, and turn the result into classes, custom properties and `SiteNav` props.
1604
+ * Kept out of the `.astro` file so it is asserted without rendering (the `sitenav.ts` split). It
1605
+ * imports the schema's literal lists as values from `@getvitops/config` (a zod-free-at-runtime
1606
+ * leaf for these constants) and nothing from the generator, so it cannot drag it into a Worker bundle.
1607
+ */
1608
+ function oneOf(value, allowed, field) {
1609
+ if (value === void 0) return void 0;
1610
+ if (!allowed.includes(value)) throw new Error(`site.chrome ${field}: "${String(value)}" is not one of ${allowed.join(" | ")}.`);
1611
+ return value;
1612
+ }
1613
+ const LENGTH_FUNCTIONS = /* @__PURE__ */ new Set([
1614
+ "calc",
1615
+ "clamp",
1616
+ "min",
1617
+ "max"
1618
+ ]);
1619
+ const LENGTH_UNITS = /* @__PURE__ */ new Set([
1620
+ "px",
1621
+ "rem",
1622
+ "em",
1623
+ "ch",
1624
+ "%",
1625
+ "vw",
1626
+ "vh",
1627
+ "vi",
1628
+ "vb",
1629
+ "svw",
1630
+ "svh",
1631
+ "dvw",
1632
+ "dvh",
1633
+ "lvw",
1634
+ "lvh",
1635
+ "cqw",
1636
+ "cqh",
1637
+ "cqi",
1638
+ "cqb"
1639
+ ]);
1640
+ /**
1641
+ * A CSS length written into a `<style>` — so a closed grammar, not a blocklist. Numbers must carry
1642
+ * a unit from `LENGTH_UNITS` (or be a bare `0`), functions are limited to `calc|clamp|min|max`,
1643
+ * parentheses must balance, and nothing else is admitted: no `var()`, `url()`, `;`, braces or quotes.
1644
+ * `4rem`, `clamp(3.5rem, 8cqi, 4.5rem)` and `min(100%, 80rem)` pass; `4rem;}body{…` does not.
1645
+ */
1646
+ function isSafeLength(value) {
1647
+ if (!/^[0-9a-z%.,+\-*/() ]+$/.test(value)) return false;
1648
+ let depth = 0;
1649
+ for (const ch of value) if (ch === "(") depth++;
1650
+ else if (ch === ")" && --depth < 0) return false;
1651
+ if (depth !== 0) return false;
1652
+ for (const [, name] of value.matchAll(/([a-z]+)\(/g)) if (!LENGTH_FUNCTIONS.has(name)) return false;
1653
+ const tokens = value.replace(/[a-z]+\(/g, "(").split(/[\s,()+*/]+|(?<=\d)-(?=\d)|\s-\s/).filter(Boolean);
1654
+ if (tokens.length === 0) return false;
1655
+ return tokens.every((t) => {
1656
+ if (t === "0" || t === "-") return true;
1657
+ const m = /^-?(?:\d+\.?\d*|\.\d+)([a-z%]*)$/.exec(t);
1658
+ return m !== null && m[1] !== void 0 && LENGTH_UNITS.has(m[1]);
1659
+ });
1660
+ }
1661
+ const ROLE = /^[a-z][a-z0-9-]*$/;
1662
+ const SHADOW = /^[a-z0-9][a-z0-9-]*$/;
1663
+ function resolveMasthead(config, override = {}) {
1664
+ const base = config?.site?.chrome?.masthead ?? {};
1665
+ const pick = (k) => override[k] !== void 0 ? override[k] : base[k];
1666
+ const elevation = {
1667
+ ...base.elevation,
1668
+ ...override.elevation
1669
+ };
1670
+ const position = oneOf(pick("position"), MASTHEAD_POSITIONS, "masthead.position") ?? "static";
1671
+ const height = pick("height");
1672
+ if (height !== void 0 && !isSafeLength(height)) throw new Error(`Masthead: height "${height}" is not a plain CSS length (e.g. "4rem" or "clamp(3.5rem, 8cqi, 4.5rem)").`);
1673
+ const inset = pick("inset");
1674
+ if (inset !== void 0 && !isSafeLength(inset)) throw new Error(`Masthead: inset "${inset}" is not a plain CSS length (e.g. "0.75rem" or "clamp(0.5rem, 2cqi, 1.5rem)").`);
1675
+ const tone = pick("tone");
1676
+ if (tone !== void 0 && !ROLE.test(tone.replace(/:solid$/, ""))) throw new Error(`Masthead: tone "${tone}" must be a role name, optionally "<role>:solid".`);
1677
+ if (elevation.shadow !== void 0 && !SHADOW.test(elevation.shadow)) throw new Error(`Masthead: elevation.shadow "${elevation.shadow}" is not a shadow name.`);
1678
+ return {
1679
+ position,
1680
+ behavior: position === "static" ? "always" : oneOf(pick("behavior"), MASTHEAD_BEHAVIORS, "masthead.behavior") ?? "always",
1681
+ surface: oneOf(pick("surface"), MASTHEAD_SURFACES, "masthead.surface") ?? "solid",
1682
+ tone,
1683
+ layout: oneOf(pick("layout"), MASTHEAD_LAYOUTS, "masthead.layout") ?? "full",
1684
+ width: oneOf(pick("width"), MASTHEAD_WIDTHS, "masthead.width") ?? "content",
1685
+ shadow: elevation.shadow,
1686
+ border: elevation.border ?? false,
1687
+ elevateOnScroll: elevation.onScroll ?? false,
1688
+ height,
1689
+ inset
1690
+ };
1691
+ }
1692
+ /** Modifier classes on `.masthead`. */
1693
+ function mastheadClass(m) {
1694
+ const scrollOnly = m.elevateOnScroll;
1695
+ return [
1696
+ "masthead",
1697
+ ...m.position !== "static" ? [`masthead--${m.position}`] : [],
1698
+ ...m.behavior === "hide-on-scroll" ? ["masthead--hide-on-scroll"] : [],
1699
+ m.surface !== "solid" ? `masthead--${m.surface}` : "",
1700
+ m.layout !== "full" ? `masthead--${m.layout}` : "",
1701
+ ...m.shadow ? ["masthead--shadow", ...scrollOnly ? ["masthead--shadow-on-scroll"] : []] : [],
1702
+ ...m.border ? ["masthead--border", ...scrollOnly ? ["masthead--border-on-scroll"] : []] : [],
1703
+ ...m.tone ? ["tone"] : []
1704
+ ].filter(Boolean);
1705
+ }
1706
+ /**
1707
+ * The `.tone` custom properties for a role, as a `style` string. Each is a `var()` chain that
1708
+ * degrades to the surface role, so a role the site never defined is a neutral bar, not a
1709
+ * transparent one. `<role>:solid` fills with the role's solid and its computed foreground.
1710
+ */
1711
+ function mastheadStyle(m) {
1712
+ const parts = [];
1713
+ if (m.tone) {
1714
+ const solid = m.tone.endsWith(":solid");
1715
+ const r = m.tone.replace(/:solid$/, "");
1716
+ parts.push(solid ? `--tone-bg: var(--color-bg-${r}-solid, var(--color-bg-${r}, var(--color-bg-surface)))` : `--tone-bg: var(--color-bg-${r}, var(--color-bg-${r}-muted, var(--color-bg-surface)))`, solid ? `--tone-fg: var(--color-text-on-${r}, var(--color-text-surface))` : `--tone-fg: var(--color-text-${r}, var(--color-text-surface))`, `--tone-border: var(--color-border-${r}, var(--color-border-surface))`);
1717
+ }
1718
+ if (m.shadow) parts.push(`--masthead-shadow: var(--shadow-${m.shadow})`);
1719
+ return parts.length ? parts.join("; ") : void 0;
1720
+ }
1721
+ /**
1722
+ * Page-level rule for a custom height and inset — `--header-offset` reads both from `:root`, and
1723
+ * the vars inherit down to the masthead. The inset is written only for a layout that has one (a
1724
+ * full-width bar sits flush) and then always, defaulted to the same `0.75rem` the element falls
1725
+ * back to, since `--header-offset` adds `--masthead-inset` unconditionally.
1726
+ */
1727
+ function mastheadRootCss(m) {
1728
+ const decls = [m.height ? `--masthead-height:${m.height}` : "", m.layout !== "full" ? `--masthead-inset:${m.inset ?? "0.75rem"}` : ""].filter(Boolean);
1729
+ return decls.length ? `:root{${decls.join(";")}}` : void 0;
1730
+ }
1731
+ function resolveNavOptions(config, override = {}) {
1732
+ const m = stripUndefined({
1733
+ ...config?.site?.chrome?.nav,
1734
+ ...stripUndefined(override)
1735
+ });
1736
+ return stripUndefined({
1737
+ breakpoint: oneOf(m.breakpoint, NAV_BREAKPOINTS, "nav.breakpoint"),
1738
+ mobileBreakpoint: oneOf(m.mobileBreakpoint, NAV_BREAKPOINTS, "nav.mobileBreakpoint"),
1739
+ megaBreakpoint: oneOf(m.megaBreakpoint, NAV_BREAKPOINTS, "nav.megaBreakpoint"),
1740
+ depth: m.depth,
1741
+ megaDepth: m.megaDepth,
1742
+ compact: oneOf(m.compact, NAV_COMPACT_MODES, "nav.compact"),
1743
+ drawerSide: oneOf(m.drawerSide, NAV_DRAWER_SIDES, "nav.drawerSide")
1744
+ });
1745
+ }
1746
+ function stripUndefined(o) {
1747
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
1748
+ }
1749
+
1750
+ export { TRACKING_COOKIE, consentCategories, contentSecurityPolicy, cspScriptHash, vitops as default, gitLastmod, inlineScriptHashes, mastheadClass, mastheadRootCss, mastheadStyle, mergeSeoDefaults, resolveAds, resolveAnalytics, resolveMasthead, resolveNavOptions, resolveSeo, resolveSeoDefaults, resolveTracking, routeFromPage, slugFromContent };
1587
1751
  //# sourceMappingURL=index.mjs.map