/* Theme base styles — orchestrates the project's CSS partials in cascade order.

   CASCADE ORDER (locked TICKET-DEV-003, 2026-05-26):

     1. Tokens      — :root design tokens (colors, spacing, motion, etc.)
                      ALL downstream layers may reference these var(--*) names.
     2. Generic     — browser-normalization (reset + normalize.css)
     3. Objects     — layout primitives (grid, content wrappers, DND sections)
     4. Elements    — base HTML element styling (body, headings, buttons, forms, tables)
     5. Components  — global UI components (header, footer, shared module patterns)
     6. Utilities   — overriding helper classes (last so they win)

   Per-template page-specific styles (css/templates/_<page>.css) load AFTER
   this file via the `template_css` variable wired in templates/layouts/base.html.
   That order is intentional: templates can override anything above.

   New partials must obey the cascade — colors and tokens reference the
   layer above them, never the reverse. */

/* 1. Tokens — three-layer architecture mirroring the Juniper Design System:

     1a. Primitives — raw color ramps + width primitives. NEVER referenced by
         component CSS; only semantic.css aliases to these.
     1b. Semantic   — role-based aliases (fg-primary, bg-primary, interactive-*,
         spacing, radius, motion, etc.) + light/dark mode via [data-mode].
     1c. Typography — Manrope family + tier-based font-size/line-height/
         letter-spacing/font-weight.

   Load order matters: primitives → semantic → typography. Each layer reads
   from the layer above; never the reverse.
*/

/* Primitive tokens — raw values from the Juniper Design System.

   These are the foundation layer of the token architecture. They hold raw
   values (color ramps, width primitives) that are NEVER referenced directly
   by component CSS. Component CSS reads SEMANTIC tokens (semantic.css), which
   alias to these primitives.

   Layer hierarchy:
     primitives.css (raw values)  →  semantic.css (role aliases)  →  component CSS

   Per-client variance lives here: each EIC client repo defines its own Primary
   / Secondary / Tertiary brand scales. Neutral is shared across all clients
   (mirrors Tailwind's Zinc).

   Naming: drop Juniper's category prefix ("Color/Brand/" → just brand-name),
   keep the meaningful path. Example: Juniper "Color/Brand/Primary/500" becomes
   `--color-primary-500`.
*/

:root {
  /* ════════════════════════════════════════════════════════════════════════
     COLOR — Brand base
     ════════════════════════════════════════════════════════════════════════ */

  --color-white: #ffffff;
  --color-black: #000000;

  /* ════════════════════════════════════════════════════════════════════════
     COLOR — Neutral (Zinc scale; immutable across all EIC clients)
     ════════════════════════════════════════════════════════════════════════ */

  --color-neutral-50: #fafafa;
  --color-neutral-100: #f4f4f5;
  --color-neutral-200: #e4e4e7;
  --color-neutral-300: #d4d4d8;
  --color-neutral-400: #a1a1aa;
  --color-neutral-500: #71717a;
  --color-neutral-600: #52525b;
  --color-neutral-700: #3f3f46;
  --color-neutral-800: #27272a;
  --color-neutral-900: #18181b;
  --color-neutral-950: #09090b;

  /* ════════════════════════════════════════════════════════════════════════
     COLOR — Primary brand (DT Group teal)
     ════════════════════════════════════════════════════════════════════════ */

  --color-primary-50: #f9fcff;
  --color-primary-100: #ecf7ff;
  --color-primary-200: #d1ecff;
  --color-primary-300: #bad9ee;
  --color-primary-400: #71beee;
  --color-primary-500: #009adb;
  --color-primary-600: #0074a7;
  --color-primary-700: #005379;
  --color-primary-800: #00344f;
  --color-primary-900: #001d2e;
  --color-primary-950: #000d17;

  /* ════════════════════════════════════════════════════════════════════════
     COLOR — Secondary brand (DT Group magenta)
     ════════════════════════════════════════════════════════════════════════ */

  --color-secondary-50: #fffafb;
  --color-secondary-100: #fff1f4;
  --color-secondary-200: #ffdee5;
  --color-secondary-300: #fec1cf;
  --color-secondary-400: #ff8aab;
  --color-secondary-500: #fa2d80;
  --color-secondary-600: #c5005f;
  --color-secondary-700: #8f0044;
  --color-secondary-800: #5e002a;
  --color-secondary-900: #380016;
  --color-secondary-950: #1d0009;

  /* ════════════════════════════════════════════════════════════════════════
     COLOR — Tertiary brand (DT Group olive)
     ════════════════════════════════════════════════════════════════════════ */

  --color-tertiary-50: #fdffdf;
  --color-tertiary-100: #f7fbc1;
  --color-tertiary-200: #eaedb4;
  --color-tertiary-300: #d7da1a;
  --color-tertiary-400: #b9bc3b;
  --color-tertiary-500: #949600;
  --color-tertiary-600: #707100;
  --color-tertiary-700: #505100;
  --color-tertiary-800: #323300;
  --color-tertiary-900: #1c1c00;
  --color-tertiary-950: #0c0c00;

  /* ════════════════════════════════════════════════════════════════════════
     WIDTH — Primitive widths in pixels (Juniper Width scale)
     ════════════════════════════════════════════════════════════════════════ */

  --width-xs: 224px;
  --width-md: 376px;
  --width-lg: 480px;
  --width-2xl: 1024px;
  --width-3xl: 1280px;
}
/* Semantic tokens — role-based aliases over primitive values.

   These tokens express DESIGN INTENT (foreground primary, interactive primary,
   page padding) rather than raw values. Component CSS reads ONLY semantic
   tokens — never primitives directly — so per-client variance + light/dark
   theming both work via the cascade.

   Mode adaptation:
     Default (:root)          → Light mode values
     [data-mode="dark"]       → Dark mode override (semantic tokens swap to
                                dark-mode values within the scoped subtree)

   Section-level data-mode attribute drives the theme. The Hero section sets
   data-mode="dark" → semantic tokens resolve to dark values within Hero →
   button.button { background: var(--color-interactive-primary) } resolves to
   white pill. Same button in a light section resolves to dark pill. Pure CSS,
   no per-variant CSS.

   Mirrors Juniper's Semantic tokens collection. Values sourced from the
   Juniper Light.tokens.json + Dark.tokens.json exports.
*/

:root {
  /* ════════════════════════════════════════════════════════════════════════
     LIGHT MODE (default) — applied to :root so light is the canonical state
     ════════════════════════════════════════════════════════════════════════ */

  /* ─── Foreground ──────────────────────────────────────────────────────── */

  --color-fg-primary: var(--color-neutral-950);
  --color-fg-secondary: var(--color-neutral-800);
  --color-fg-tertiary: var(--color-neutral-700);
  --color-fg-on-primary: var(--color-white);
  --color-fg-on-primary-disabled: var(--color-neutral-300);
  --color-fg-on-secondary: var(--color-neutral-950);
  --color-fg-on-success: var(--color-white);
  --color-fg-on-warning: var(--color-white);
  --color-fg-on-error: var(--color-white);
  --color-fg-on-info: var(--color-white);
  --color-fg-light: var(--color-neutral-50);
  --color-fg-dark: var(--color-neutral-950);
  --color-fg-disabled: var(--color-neutral-300);
  --color-fg-placeholder: var(--color-neutral-400);

  /* ─── Background ──────────────────────────────────────────────────────── */

  --color-bg-canvas: var(--color-white);
  --color-bg-primary: var(--color-neutral-50);
  --color-bg-secondary: var(--color-neutral-100);
  --color-bg-tertiary: var(--color-neutral-200);
  --color-bg-light: var(--color-neutral-50);
  --color-bg-dark: var(--color-neutral-950);
  --color-bg-modal-overlay: rgba(0, 0, 0, 0.36);

  /* Navbar scroll-state backdrop. Declared per-mode (light here, dark in
     the [data-mode="dark"] block) using primitives directly — the earlier
     `color-mix(var(--color-bg-canvas) ...)` version eagerly resolved the
     nested var-chain at :root time and locked the light value across both
     modes. Pairs with backdrop-filter blur in `_header.css`. */
  --color-nav-backdrop: color-mix(in srgb, var(--color-white) 60%, transparent);

  /* ─── Interaction ─────────────────────────────────────────────────────── */

  --color-interactive-primary: var(--color-neutral-950);
  --color-interactive-primary-hover: var(--color-neutral-900);
  --color-interactive-primary-active: var(--color-neutral-950);
  --color-interactive-primary-disabled: var(--color-neutral-600);

  --color-interactive-secondary: var(--color-neutral-50);
  --color-interactive-secondary-hover: var(--color-neutral-100);
  --color-interactive-secondary-active: var(--color-neutral-50);
  --color-interactive-secondary-disabled: var(--color-neutral-300);

  --color-interactive-tertiary: var(--color-white);
  --color-interactive-tertiary-hover: var(--color-neutral-50);
  --color-interactive-tertiary-active: var(--color-white);
  --color-interactive-tertiary-disabled: var(--color-white);

  /* Brand accent — constant across modes (brand identity is constant) */
  --color-interactive-accent: var(--color-primary-500);
  --color-accent-secondary: var(--color-secondary-500);

  --color-interactive-foreground: var(--color-primary-600);
  --color-interactive-foreground-hover: var(--color-primary-500);
  --color-interactive-foreground-active: var(--color-primary-600);
  --color-interactive-foreground-disabled: var(--color-primary-300);

  --color-interactive-border: var(--color-primary-600);
  --color-interactive-border-hover: var(--color-primary-500);

  --color-interactive-focus: rgba(0, 116, 167, 0.24);

  /* ─── Border ──────────────────────────────────────────────────────────── */

  --color-border-primary: var(--color-neutral-300);
  --color-border-secondary: var(--color-neutral-400);
  --color-border-tertiary: var(--color-neutral-100);
  --color-border-disabled: var(--color-neutral-200);

  /* ════════════════════════════════════════════════════════════════════════
     SPACING — Juniper convention: spacing-N = N × 2px
     ════════════════════════════════════════════════════════════════════════ */

  --spacing-0: 0;
  --spacing-1: 2px;
  --spacing-2: 4px;
  --spacing-3: 6px;
  --spacing-4: 8px;
  --spacing-6: 12px;
  --spacing-8: 16px;
  --spacing-10: 20px;
  --spacing-12: 24px;
  --spacing-16: 32px;
  --spacing-20: 40px;
  --spacing-24: 48px;
  --spacing-28: 56px;
  --spacing-32: 64px;
  --spacing-40: 80px;
  --spacing-48: 96px;
  --spacing-64: 128px;
  --spacing-128: 256px;

  /* ════════════════════════════════════════════════════════════════════════
     LAYOUT — Page padding, section padding, container widths
     ════════════════════════════════════════════════════════════════════════ */

  --page-padding-global: 64px;
  --section-padding-small: 48px;
  --section-padding-medium: 80px;

  --container-large: 1280px;

  /* Section content container — aliases --container-large at desktop;
     constrained to --max-width-large in the 576-991 mid-band via the
     media-query override at the bottom of this file so multi-column
     section layouts don't sprawl awkwardly between phone (<576) and
     small desktop (>=992). Section module CSS reads this token; the
     navbar, footer full-bleed math, dnd-area objects, and blog
     templates stay on --container-large. */
  --container-section: var(--container-large);

  --max-width-xsmall: 400px;
  --max-width-large: 768px;
  --max-width-2xl: 1024px;
  --max-width-body-text: 565px;

  /* ════════════════════════════════════════════════════════════════════════
     RADIUS — Border radius scale
     ════════════════════════════════════════════════════════════════════════ */

  --radius-none: 0;
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-2xl: 20px;
  --radius-full: 99999px;

  /* ════════════════════════════════════════════════════════════════════════
     MOTION — Timing tokens + the project's standard reveal easing curve.
     Per-element easings stay component-specific; --ease-out-strong is the
     default for page-entrance reveal animations (Emil's stronger ease-out
     variant — more punch than CSS's built-in `ease-out` keyword).
     ════════════════════════════════════════════════════════════════════════ */

  --motion-instant: 0ms;
  --motion-fast: 150ms;
  --motion-base: 200ms;
  --motion-slow: 300ms;

  --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);

  /* ════════════════════════════════════════════════════════════════════════
     SHADOW — Elevation tiers (kept from prior token system)
     ════════════════════════════════════════════════════════════════════════ */

  --shadow-sm: 0px 1px 3px rgba(0, 0, 0, 0.11), 0px 1px 2px rgba(0, 0, 0, 0.07);
  --shadow-md: 0px 4px 6px rgba(0, 0, 0, 0.12), 0px 2px 4px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0px 10px 15px rgba(0, 0, 0, 0.15), 0px 4px 6px rgba(0, 0, 0, 0.1);

  /* ════════════════════════════════════════════════════════════════════════
     STROKE
     ════════════════════════════════════════════════════════════════════════ */

  --stroke-divider-width: 1px;

  /* ════════════════════════════════════════════════════════════════════════
     BREAKPOINTS — documentation + JS consumption only

     CSS @media queries CANNOT consume var(), so these tokens do not drive
     layout. Single source of truth for the values is theme.json's
     responsive_breakpoints; these declarations mirror those values so JS can
     read them via getComputedStyle().
     ════════════════════════════════════════════════════════════════════════ */

  --breakpoint-mobile-max: 575px;
  --breakpoint-tablet-min: 576px;
  --breakpoint-tablet-max: 991px;
  --breakpoint-desktop-min: 992px;

  /* ════════════════════════════════════════════════════════════════════════
     COMPONENT-SCOPED — Header, footer dimensions
     ════════════════════════════════════════════════════════════════════════ */

  --header-height: 90px;
}

/* ════════════════════════════════════════════════════════════════════════════
   DARK MODE — semantic tokens swap when [data-mode="dark"] is set on any
   ancestor element. Sections that should render dark (Hero, CTA banner,
   Footer) set data-mode="dark" on their root element.

   Note: bg-secondary and bg-tertiary use the Primary scale (teal-tinted) in
   dark mode rather than Neutral — the DT Group brand identity baked into
   dark-mode backgrounds. Source: Juniper Dark.tokens.json.
   ══════════════════════════════════════════════════════════════════════════ */

[data-mode="dark"] {
  /* ─── Foreground ──────────────────────────────────────────────────────── */

  --color-fg-primary: var(--color-neutral-50);
  --color-fg-secondary: var(--color-neutral-300);
  --color-fg-tertiary: var(--color-neutral-400);
  --color-fg-on-primary: var(--color-neutral-950);
  --color-fg-on-primary-disabled: var(--color-neutral-500);
  --color-fg-on-secondary: var(--color-neutral-950);
  --color-fg-on-success: var(--color-white);
  --color-fg-on-warning: var(--color-white);
  --color-fg-on-error: var(--color-white);
  --color-fg-on-info: var(--color-white);
  --color-fg-light: var(--color-neutral-50);
  --color-fg-dark: var(--color-neutral-950);
  --color-fg-disabled: var(--color-neutral-500);
  --color-fg-placeholder: var(--color-neutral-400);

  /* ─── Background ──────────────────────────────────────────────────────── */

  --color-bg-canvas: var(--color-neutral-950);
  --color-bg-primary: var(--color-neutral-900);
  --color-bg-secondary: var(--color-primary-900);
  --color-bg-tertiary: var(--color-primary-800);
  --color-bg-light: var(--color-neutral-50);
  --color-bg-dark: var(--color-neutral-950);

  /* Dark-section navbar backdrop — matches the section's dark canvas. */
  --color-nav-backdrop: color-mix(in srgb, var(--color-neutral-950) 60%, transparent);

  /* ─── Interaction ─────────────────────────────────────────────────────── */

  --color-interactive-primary: var(--color-neutral-50);
  --color-interactive-primary-hover: var(--color-neutral-100);
  --color-interactive-primary-active: var(--color-neutral-50);
  --color-interactive-primary-disabled: var(--color-neutral-300);

  --color-interactive-secondary: var(--color-neutral-950);
  --color-interactive-secondary-hover: var(--color-neutral-900);
  --color-interactive-secondary-active: var(--color-neutral-950);
  --color-interactive-secondary-disabled: var(--color-neutral-600);

  --color-interactive-tertiary: var(--color-neutral-950);
  --color-interactive-tertiary-hover: var(--color-neutral-900);
  --color-interactive-tertiary-active: var(--color-neutral-950);
  --color-interactive-tertiary-disabled: var(--color-neutral-950);

  /* Brand accent unchanged across modes */

  --color-interactive-foreground: var(--color-primary-400);
  --color-interactive-foreground-hover: var(--color-primary-500);
  --color-interactive-foreground-active: var(--color-primary-400);
  --color-interactive-foreground-disabled: var(--color-primary-950);

  --color-interactive-border: var(--color-primary-600);
  --color-interactive-border-hover: var(--color-primary-600);

  --color-interactive-focus: rgba(0, 154, 219, 0.36);

  /* ─── Border ──────────────────────────────────────────────────────────── */

  --color-border-primary: var(--color-neutral-800);
  --color-border-secondary: var(--color-neutral-700);
  --color-border-tertiary: var(--color-primary-700);
  --color-border-disabled: var(--color-neutral-500);
}

/* ════════════════════════════════════════════════════════════════════════════
   MOBILE OVERRIDES — desktop-first pattern. Layout tokens shift at <992px
   to match the Juniper / Figma mobile spec (375px design target). Components
   that read these tokens adapt automatically without per-module @media blocks.
   Source: Figma Mobile - Home frame (node 161:6366). Color + spacing scale
   tokens do not shift between mobile/desktop.
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 991px) {
  :root {
    --page-padding-global: 32px;
    --section-padding-small: 32px;
    --section-padding-medium: 48px;
  }
}

/* Section content cap for the 576-991 mid-band — sections constrain their
   inner container to --max-width-large (768) so multi-column layouts don't
   sprawl awkwardly between phone (<576) and small desktop (>=992). At <576
   the existing page-padding shrink already keeps content tight. */
@media (min-width: 576px) and (max-width: 991px) {
  :root {
    --container-section: var(--max-width-large);
  }
}
/* Typography tokens — Juniper's Typography collection.

   Tier-based naming (Display / Header / Body / Supporting) communicates visual
   intent rather than h-tag coupling. HTML markup picks the appropriate tier
   via _typography.css declarations.

   Sizes use px for primitives + rem for root-relative scaling where needed.
   Letter-spacing values are in em (Juniper's "-3" letter-spacing value =
   -3% = -0.03em). All sizes and line-heights map to the Juniper Figma values
   exactly.

   Per the DEV-022 lock, font weights split into 5 semantic tokens:
   display / header / body / body-medium / body-semibold. Two share value 600
   with intentional semantic split — allows per-client adjustment of one
   without the other.
*/

/* ════════════════════════════════════════════════════════════════════════
   FONT FACE — Self-hosted Manrope (woff2, latin + latin-ext subsets).
   Sourced from Fontsource (Google Fonts WOFF2 builds). Weights 400 / 500 /
   600 match our 5 font-weight semantic tokens (display 400, header 500,
   body 400, body-medium 500, body-semibold 600).

   Two @font-face rules per weight with distinct unicode-range values:
   the browser only fetches the latin-ext subset when a page actually
   contains characters in that range (European diacritics — é, à, ñ,
   etc.). For an English-only page, only the latin files load.

   `font-display: swap` shows fallback during the brief load window;
   preload <link> tags in base.html make that window tiny in practice.
   ════════════════════════════════════════════════════════════════════════ */

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("https://146887469.fs1.hubspotusercontent-eu1.net/hubfs/146887469/raw_assets/public/eic-cms-theme/fonts/manrope/manrope-latin-400-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("https://146887469.fs1.hubspotusercontent-eu1.net/hubfs/146887469/raw_assets/public/eic-cms-theme/fonts/manrope/manrope-latin-ext-400-normal.woff2") format("woff2");
  unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
    U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("https://146887469.fs1.hubspotusercontent-eu1.net/hubfs/146887469/raw_assets/public/eic-cms-theme/fonts/manrope/manrope-latin-500-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("https://146887469.fs1.hubspotusercontent-eu1.net/hubfs/146887469/raw_assets/public/eic-cms-theme/fonts/manrope/manrope-latin-ext-500-normal.woff2") format("woff2");
  unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
    U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("https://146887469.fs1.hubspotusercontent-eu1.net/hubfs/146887469/raw_assets/public/eic-cms-theme/fonts/manrope/manrope-latin-600-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("https://146887469.fs1.hubspotusercontent-eu1.net/hubfs/146887469/raw_assets/public/eic-cms-theme/fonts/manrope/manrope-latin-ext-600-normal.woff2") format("woff2");
  unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
    U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* ════════════════════════════════════════════════════════════════════════
     FONT FAMILY — Manrope (DT Group + Juniper standard)
     ════════════════════════════════════════════════════════════════════════ */

  --font-family-primary: Manrope, sans-serif;

  /* ════════════════════════════════════════════════════════════════════════
     FONT WEIGHT — 5 semantic tokens (per DEV-022)
     `--font-weight-header` and `--font-weight-body-medium` intentionally share
     value 500/600 but split semantic intent — same shape, distinct meaning,
     allows per-client variance on one without the other.
     ════════════════════════════════════════════════════════════════════════ */

  --font-weight-display: 400;
  --font-weight-header: 500;
  --font-weight-body: 400;
  --font-weight-body-medium: 600;
  --font-weight-body-semibold: 600;

  /* ════════════════════════════════════════════════════════════════════════
     BODY SIZE — root font-size. 16px at ≥576 (1rem = 16px); shifted to
     14px at ≤575 (small-mobile root scale-down, applied in
     `css/elements/_typography.css`). The 576–991 band uses the per-token
     @media overrides below WITHOUT a root shift. See
     `[[hybrid-rem-typography]]` for the full two-stage scaling rationale.
     ════════════════════════════════════════════════════════════════════════ */

  --font-size-base: 16px;
  --font-size-base-tablet: 16px;
  --font-size-base-mobile: 14px;

  /* ════════════════════════════════════════════════════════════════════════
     FONT SIZE — All typography tokens use rem so browser font-size zoom
     respects user accessibility preferences. 1rem = 16px (root, above).
     Line-heights also in rem. Letter-spacings stay in em — already
     font-size-relative.
     ════════════════════════════════════════════════════════════════════════ */

  /* Display tier (large hero / page-title surfaces) */

  --font-size-display-xs: 2.5rem;
  --font-size-display-sm: 3rem;
  --font-size-display-md: 3.75rem;
  --font-size-display-lg: 4.5rem;

  --line-height-display-xs: 3rem;
  --line-height-display-sm: 3.5rem;
  --line-height-display-md: 4.25rem;
  --line-height-display-lg: 5rem;

  --letter-spacing-display-xs: -0.01em;
  --letter-spacing-display-sm: -0.02em;
  --letter-spacing-display-md: -0.03em;
  --letter-spacing-display-lg: -0.04em;

  /* Header tier (section heading surfaces, body-font weight) */

  --font-size-header-xs: 1.25rem;
  --font-size-header-sm: 1.5rem;
  --font-size-header-md: 1.875rem;
  --font-size-header-lg: 2.25rem;

  --line-height-header-xs: 1.75rem;
  --line-height-header-sm: 2rem;
  --line-height-header-md: 2.25rem;
  --line-height-header-lg: 2.5rem;

  --letter-spacing-header-xs: 0;
  --letter-spacing-header-sm: 0;
  --letter-spacing-header-md: -0.01em;
  --letter-spacing-header-lg: -0.01em;

  /* Body tier (body text + small headers) */

  --font-size-body-xs: 0.75rem;
  --font-size-body-sm: 0.875rem;
  --font-size-body-md: 1rem;
  --font-size-body-lg: 1.125rem;

  --line-height-body-xs: 1rem;
  --line-height-body-sm: 1.25rem;
  --line-height-body-md: 1.5rem;
  --line-height-body-lg: 1.75rem;

  --letter-spacing-body-xs: 0;
  --letter-spacing-body-sm: 0;
  --letter-spacing-body-md: 0;
  --letter-spacing-body-lg: 0;

  /* Supporting tier (captions, labels, fine print) */

  --font-size-supporting-small: 0.75rem;
  --font-size-supporting-label: 0.875rem;

  --line-height-supporting-small: 1rem;
  --line-height-supporting-label: 1.25rem;

  --letter-spacing-supporting-small: 0;
  --letter-spacing-supporting-label: 0;
}

/* ════════════════════════════════════════════════════════════════════════════
   MOBILE OVERRIDES — desktop-first pattern. Type tokens shift at <992px to
   match the Juniper / Figma mobile spec (375px design target). Components
   that read these tokens adapt automatically without per-module @media
   blocks. Letter-spacing is em-based and stays constant across modes —
   ratios scale correctly with the font-size shift. Source: Figma Mobile -
   Home frame (node 161:6366).
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 991px) {
  :root {
    --font-size-display-xs: 2.25rem;
    --line-height-display-xs: 2.5rem;
    --font-size-display-md: 2.5rem;
    --line-height-display-md: 3rem;
  }
}

/* 2. Generic — reset + normalize browser defaults */

*,
*:before,
*:after {
  box-sizing: border-box;
}
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */

/* Document
   ========================================================================== */

/**
 * 1. Correct the line height in all browsers.
 * 2. Prevent adjustments of font size after orientation changes in iOS.
 */

html {
  line-height: 1.15; /* 1 */
  -webkit-text-size-adjust: 100%; /* 2 */
}

/* Sections
   ========================================================================== */

/**
 * Remove the margin in all browsers.
 */

body {
  margin: 0;
}

/**
 * Correct the font size and margin on `h1` elements within `section` and
 * `article` contexts in Chrome, Firefox, and Safari.
 */

h1 {
  font-size: 2em;
  margin: 0.67em 0;
}

/* Grouping content
   ========================================================================== */

/**
 * Add the correct box sizing in Firefox.
 */

hr {
  box-sizing: content-box;
  height: 0;
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

pre {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/* Text-level semantics
   ========================================================================== */

/**
 * 1. Remove the bottom border in Chrome 57-
 * 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.
 */

abbr[title] {
  border-bottom: none; /* 1 */
  text-decoration: underline; /* 2 */
  text-decoration: underline dotted; /* 2 */
}

/**
 * Add the correct font weight in Chrome, Edge, and Safari.
 */

b,
strong {
  font-weight: bolder;
}

/**
 * 1. Correct the inheritance and scaling of font size in all browsers.
 * 2. Correct the odd `em` font sizing in all browsers.
 */

code,
kbd,
samp {
  font-family: monospace, monospace; /* 1 */
  font-size: 1em; /* 2 */
}

/**
 * Add the correct font size in all browsers.
 */

small {
  font-size: 80%;
}

/**
 * Prevent `sub` and `sup` elements from affecting the line height in
 * all browsers.
 */

sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub {
  bottom: -0.25em;
}

sup {
  top: -0.5em;
}

/* Forms
   ========================================================================== */

/**
 * 1. Change the font styles in all browsers.
 * 2. Remove the margin in Firefox and Safari.
 */

button,
input,
optgroup,
select,
textarea {
  font-family: inherit; /* 1 */
  font-size: 100%; /* 1 */
  line-height: 1.15; /* 1 */
  margin: 0; /* 2 */
}

/**
 * Remove the inheritance of text transform in Edge and Firefox.
 * 1. Remove the inheritance of text transform in Firefox.
 */

button,
select {
  /* 1 */
  text-transform: none;
}

/**
 * Correct the inability to style clickable types in iOS and Safari.
 */

button,
[type="button"],
[type="reset"],
[type="submit"] {
  -webkit-appearance: button;
}

/**
 * Remove the inner border and padding in Firefox.
 */

button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
  border-style: none;
  padding: 0;
}

/**
 * Restore the focus styles unset by the previous rule.
 */

button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
  outline: 1px dotted ButtonText;
}

/**
 * Correct the padding in Firefox.
 */

fieldset {
  padding: 0.35em 0.75em 0.625em;
}

/**
 * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.
 */

legend {
  padding: 0;
}

/**
 * Add the correct vertical alignment in Chrome, Firefox, and Opera.
 */

progress {
  vertical-align: baseline;
}

/**
 * Correct the cursor style of increment and decrement buttons in Chrome.
 */

[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
  height: auto;
}

/**
 * 1. Correct the odd appearance in Chrome and Safari.
 * 2. Correct the outline style in Safari.
 */

[type="search"] {
  -webkit-appearance: textfield; /* 1 */
  outline-offset: -2px; /* 2 */
}

/**
 * Remove the inner padding in Chrome and Safari on macOS.
 */

[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
}

/**
 * 1. Correct the inability to style clickable types in iOS and Safari.
 * 2. Change font properties to `inherit` in Safari.
 */

::-webkit-file-upload-button {
  -webkit-appearance: button; /* 1 */
  font: inherit; /* 2 */
}

/* Interactive
   ========================================================================== */

/*
 * Add the correct display in Edge and Firefox.
 */

details {
  display: block;
}

/*
 * Add the correct display in all browsers.
 */

summary {
  display: list-item;
}

/* 3. Objects — non-cosmetic layout primitives (grids, wrappers, DND containers) */



/* CSS variables */

:root {
  --column-gap: 2.13%;
  --column-width-multiplier: 8.333;
}

/* Mobile layout */

.row-fluid {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
}


  .row-fluid .span1,
  .row-fluid .span2,
  .row-fluid .span3,
  .row-fluid .span4,
  .row-fluid .span5,
  .row-fluid .span6,
  .row-fluid .span7,
  .row-fluid .span8,
  .row-fluid .span9,
  .row-fluid .span10,
  .row-fluid .span11,
  .row-fluid .span12{
  min-height: 1px;
  width: 100%;
}

/* Desktop layout */

@media (min-width: 992px) {
  .row-fluid {
    flex-wrap: nowrap;
    justify-content: space-between;
  }

  
    .row-fluid .span1 {
      width: calc(var(--column-width-multiplier) * 1% * 1 - var(--column-gap) * (11 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span2 {
      width: calc(var(--column-width-multiplier) * 1% * 2 - var(--column-gap) * (10 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span3 {
      width: calc(var(--column-width-multiplier) * 1% * 3 - var(--column-gap) * (9 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span4 {
      width: calc(var(--column-width-multiplier) * 1% * 4 - var(--column-gap) * (8 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span5 {
      width: calc(var(--column-width-multiplier) * 1% * 5 - var(--column-gap) * (7 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span6 {
      width: calc(var(--column-width-multiplier) * 1% * 6 - var(--column-gap) * (6 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span7 {
      width: calc(var(--column-width-multiplier) * 1% * 7 - var(--column-gap) * (5 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span8 {
      width: calc(var(--column-width-multiplier) * 1% * 8 - var(--column-gap) * (4 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span9 {
      width: calc(var(--column-width-multiplier) * 1% * 9 - var(--column-gap) * (3 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span10 {
      width: calc(var(--column-width-multiplier) * 1% * 10 - var(--column-gap) * (2 * var(--column-width-multiplier) / 100));
    }
  
    .row-fluid .span11 {
      width: calc(var(--column-width-multiplier) * 1% * 11 - var(--column-gap) * (1 * var(--column-width-multiplier) / 100));
    }
  
}
/* Content wrapper — centered, with theme-driven max-width */

.content-wrapper {
  margin: 0 auto;
  max-width: var(--container-large);
}

/* DND section row — centered, with theme-driven max-width */

.dnd-section > .row-fluid {
  margin: 0 auto;
  max-width: var(--container-large);
}

/* DND section + vertical-spacing wrapper — theme-driven vertical padding */

.dnd-section,
.content-wrapper--vertical-spacing {
  padding: var(--section-padding-medium) 0;
}

.dnd-section .dnd-column {
  padding: 0 var(--spacing-12);
}

/* Full-bleed — sections that own their own background + theming via
   data-mode get edge-to-edge treatment automatically. Robust to saved-page
   state (the :has() trigger fires even when the dnd_section element didn't
   receive the explicit modifier class). Future CTA banner, footer module
   rebuilds that set their own data-mode inherit this same behavior. */

.dnd-section--full-bleed,
.dnd-section--full-bleed.dnd-section,
.dnd-section:has([data-mode]) {
  padding: 0;
}

.dnd-section--full-bleed > .row-fluid,
.dnd-section:has([data-mode]) > .row-fluid {
  max-width: none;
}

.dnd-section--full-bleed .dnd-column,
.dnd-section:has([data-mode]) .dnd-column {
  padding: 0;
}

@media (max-width: 991px) /* mobile + tablet */ {
  .content-wrapper,
  .dnd-section > .row-fluid {
    max-width: 768px;
  }

  .dnd-section,
  .content-wrapper--vertical-spacing {
    padding: var(--section-padding-medium) var(--spacing-32);
  }

  .dnd-section--full-bleed,
  .dnd-section--full-bleed.dnd-section,
  .dnd-section:has([data-mode]) {
    padding: 0;
  }

  .dnd-section .dnd-column {
    padding: 0;
  }
}

@media (max-width: 575px) /* mobile */ {
  .dnd-section,
  .content-wrapper--vertical-spacing {
    padding: var(--section-padding-medium) var(--spacing-12);
  }

  .dnd-section--full-bleed,
  .dnd-section--full-bleed.dnd-section,
  .dnd-section:has([data-mode]) {
    padding: 0;
  }
}

/* 4. Elements — base HTML element styling */

/* Root font-size — drives rem-based scaling. Mobile/tablet variants applied
   via media queries; values sourced from typography-scale tokens. */

html {
  font-size: var(--font-size-base);
}

@media (max-width: 991px) /* mobile + tablet */ {
  html {
    font-size: var(--font-size-base-tablet);
  }
}

@media (max-width: 575px) /* mobile */ {
  html {
    font-size: var(--font-size-base-mobile);
  }
}

/* Body — brand-font baseline. The fixed header is transparent and overlays
   content from y=0; per-section padding-top compensation (where needed) is
   handled by individual modules. The overflow-wrap is meant to prevent
   long/large words from breaking mobile responsiveness (e.g. horizontal
   scrolling). It is preferred to reduce font sizes on mobile to address this,
   with this CSS specifically helping with extreme scenarios where a reduction
   in font size is not possible. */

body {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-body);
  color: var(--color-fg-primary);
  font-size: var(--font-size-base);
  line-height: 1.4;
  overflow-wrap: break-word;

  /* Crisper text rendering on macOS / iOS WebKit; matches the static-detail
     polish baseline. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Word-break behavior for languages whose break rules differ from the default.
   If your content is not translated into these languages, safe to remove. */

html[lang^="ja"] body,
html[lang^="zh"] body,
html[lang^="ko"] body {
  line-break: strict;
  overflow-wrap: normal;
  word-break: break-all;
}

/* Paragraphs */

p {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-body);
  font-size: 1rem;
  margin: 0 0 1.4rem;
  text-wrap: pretty;
}

/* Anchors */

a {
  cursor: pointer;
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-body);
  color: var(--color-interactive-foreground);
}

a:hover,
a:focus {
  color: var(--color-interactive-foreground-hover);
}

a:active {
  color: var(--color-interactive-foreground-active);
}

/* Headings — baseline margins, then per-heading typography from tokens.

   The cascade here is intentional but ASYMMETRIC: h1–h4 lose their bottom
   margin (per-heading line-height handles vertical rhythm), h5/h6 retain a
   1.4rem bottom margin. Preserves pre-restructure rendered behavior; flagged
   for design review (carried forward from TICKET-DEV-003). */

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0 0 1.4rem;
  text-wrap: balance;
}

h1,
h2,
h3,
h4 {
  margin: 0;
}

h1,
.h1 {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-display);
  color: var(--color-fg-primary);
  font-size: var(--font-size-display-lg);
  line-height: var(--line-height-display-lg);
  letter-spacing: var(--letter-spacing-display-lg);
}

h2,
.h2 {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-display);
  color: var(--color-fg-primary);
  font-size: var(--font-size-display-md);
  line-height: var(--line-height-display-md);
  letter-spacing: var(--letter-spacing-display-md);
}

h3,
.h3 {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-display);
  color: var(--color-fg-primary);
  font-size: var(--font-size-display-sm);
  line-height: var(--line-height-display-sm);
  letter-spacing: var(--letter-spacing-display-sm);
}

h4,
.h4 {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-header);
  color: var(--color-fg-primary);
  font-size: var(--font-size-header-md);
  line-height: var(--line-height-header-md);
  letter-spacing: var(--letter-spacing-header-md);
}

h5,
.h5 {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-body-medium);
  color: var(--color-fg-primary);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  letter-spacing: var(--letter-spacing-body-md);
}

h6,
.h6 {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-body-medium);
  color: var(--color-fg-primary);
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  letter-spacing: var(--letter-spacing-body-sm);
}

small,
.small {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-body);
  color: var(--color-fg-primary);
  font-size: var(--font-size-supporting-small);
  line-height: var(--line-height-supporting-small);
  letter-spacing: var(--letter-spacing-supporting-small);
}

/* Lists */

ul,
ol {
  margin: 0 0 1.4rem;
}

ul ul,
ol ul,
ul ol,
ol ol {
  margin: 0;
}

ul.no-list {
  list-style: none;
  margin: 0;
  padding-left: 0;
}

/* Code blocks */

pre {
  overflow: auto;
}

code {
  vertical-align: bottom;
}

/* Blockquote */

blockquote {
  margin: 0;
  padding: 0;
  border: none;
}

/* Horizontal rules */

hr {
  border: none;
  border-bottom: 1px solid var(--color-border-primary);
}

/* Image alt text */

img {
  font-size: 0.583rem;
  word-break: normal;
}
/* Buttons — Juniper-aligned, semantic-token-driven.

   Single button shape; visual identity (color, contrast) comes from the
   surrounding section's data-mode attribute via semantic token cascade.
   No light/dark button variants in CSS — semantic tokens adapt automatically.

   Variants (primary / secondary / tertiary) map to Juniper's Color/Interaction/*
   semantic roles via .button--primary / --secondary / --tertiary modifiers. */

button,
.button,
.hs-button,
input[type="button"],
input[type="submit"],
input[type="reset"],
.hs-blog-post-listing__post-button {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-body);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-4);
  padding: var(--spacing-4) var(--spacing-8);
  border-radius: var(--radius-full);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  text-decoration: none;
  text-align: center;
  cursor: pointer;
  transition: background-color var(--motion-base) ease-in-out, color var(--motion-base) ease-in-out,
    border-color var(--motion-base) ease-in-out, opacity var(--motion-base) ease-in-out,
    transform var(--motion-base) ease-out;
  border: none;
  white-space: nowrap;
}

button:hover,
button:focus,
button:active,
.button:hover,
.button:focus,
.button:active,
.hs-button:hover,
.hs-button:focus,
.hs-button:active {
  text-decoration: none;
}

/* Primary — adapts via interactive-primary semantic. In dark section: white
   pill + dark text. In light section: dark pill + white text. */

button,
.button,
.hs-button,
input[type="button"],
input[type="submit"],
.hs-blog-post-listing__post-button,
.button--primary,
.hs-button--primary {
  background-color: var(--color-interactive-primary);
  color: var(--color-fg-on-primary);
}

button:hover,
button:focus,
.button:hover,
.button:focus,
.hs-button:hover,
.hs-button:focus,
input[type="button"]:hover,
input[type="button"]:focus,
input[type="submit"]:hover,
input[type="submit"]:focus,
.hs-blog-post-listing__post-button:hover,
.hs-blog-post-listing__post-button:focus,
.button--primary:hover,
.button--primary:focus,
.hs-button--primary:hover,
.hs-button--primary:focus {
  background-color: var(--color-interactive-primary-hover);
  color: var(--color-fg-on-primary);
}

button:active,
.button:active,
.hs-button:active,
input[type="button"]:active,
input[type="submit"]:active,
.hs-blog-post-listing__post-button:active,
.button--primary:active,
.hs-button--primary:active {
  background-color: var(--color-interactive-primary-active);
  color: var(--color-fg-on-primary);
}

/* Secondary — inverse of primary in any given mode */

.button--secondary,
.hs-button--secondary,
input[type="reset"] {
  background-color: var(--color-interactive-secondary);
  color: var(--color-fg-on-secondary);
}

.button--secondary:hover,
.button--secondary:focus,
.hs-button--secondary:hover,
.hs-button--secondary:focus,
input[type="reset"]:hover,
input[type="reset"]:focus {
  background-color: var(--color-interactive-secondary-hover);
  color: var(--color-fg-on-secondary);
}

.button--secondary:active,
.hs-button--secondary:active,
input[type="reset"]:active {
  background-color: var(--color-interactive-secondary-active);
  color: var(--color-fg-on-secondary);
}

/* Tertiary — ghost / minimal-fill; border carries identity */

.button--tertiary,
.hs-button--tertiary {
  background-color: var(--color-interactive-tertiary);
  color: var(--color-fg-primary);
  border: 1px solid var(--color-border-primary);
}

.button--tertiary:hover,
.button--tertiary:focus,
.hs-button--tertiary:hover,
.hs-button--tertiary:focus {
  background-color: var(--color-interactive-tertiary-hover);
  color: var(--color-fg-primary);
  border-color: var(--color-interactive-border-hover);
}

.button--tertiary:active,
.hs-button--tertiary:active {
  background-color: var(--color-interactive-tertiary-active);
  color: var(--color-fg-primary);
  border-color: var(--color-interactive-border);
}

/* Scale-on-press — tactile feedback across all button variants. Floor of
   0.97 per Emil's principle (anything below 0.95 reads exaggerated). The
   transform property is already in the base transition list above. */

button:active,
.button:active,
.hs-button:active,
input[type="button"]:active,
input[type="submit"]:active,
input[type="reset"]:active {
  transform: scale(0.97);
}

/* Disabled state — opacity desaturates while preserving theme color identity */

button:disabled,
.button:disabled,
.hs-button:disabled,
input[type="button"]:disabled,
input[type="submit"]:disabled,
input[type="reset"]:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Disabled buttons don't get the press-scale */

button:disabled:active,
.button:disabled:active,
.hs-button:disabled:active,
input[type="button"]:disabled:active,
input[type="submit"]:disabled:active,
input[type="reset"]:disabled:active {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  button:active,
  .button:active,
  .hs-button:active,
  input[type="button"]:active,
  input[type="submit"]:active,
  input[type="reset"]:active {
    transform: none;
  }
}

/* Square button modifier for icon-only buttons (mobile menu toggle / close) */

.button--square {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-8) !important;
  width: 56px;
  height: 56px;
  min-width: 56px;
  min-height: 56px;
}

.button--square svg {
  display: block;
}

/* No-button modifier — unstyled button reset for keyboard-focusable elements
   that shouldn't visually look like buttons. */

.no-button,
.no-button:hover,
.no-button:focus,
.no-button:active {
  background: none;
  border: none;
  border-radius: 0;
  color: initial;
  font-family: inherit;
  font-size: inherit;
  font-style: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  margin-bottom: 0;
  padding: 0;
  text-align: left;
  text-decoration: none;
  transition: none;
}
/* Form container + submitted-message wrapper */

form,
.submitted-message {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-body);
}

/* Fields wrapper */

.hs-form-field {
  margin-bottom: var(--spacing-12);
}

/* Form title */

.form-title {
  font-size: var(--font-size-body-md);
  color: var(--color-fg-primary);
  margin-bottom: 0;
  padding-bottom: var(--spacing-12);
}

/* Labels */

form label {
  display: block;
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  font-weight: var(--font-weight-body);
  margin-bottom: var(--spacing-4);
  color: var(--color-fg-primary);
}

/* Help text */

form legend {
  font-size: var(--font-size-body-sm);
  color: var(--color-fg-secondary);
}

/* Inputs (text + textarea + select) */

form input[type="text"],
form input[type="search"],
form input[type="email"],
form input[type="password"],
form input[type="tel"],
form input[type="number"],
form input[type="file"],
form select,
form textarea {
  display: inline-block;
  font-family: var(--font-family-primary);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  padding: var(--spacing-6);
  width: 100%;
  border: 1px solid var(--color-border-primary);
  background-color: var(--color-bg-canvas);
  color: var(--color-fg-primary);
  border-radius: var(--radius-lg);
}

form textarea {
  resize: vertical;
  min-height: 180px;
}

form input:focus:not(:focus-visible),
form select:focus:not(:focus-visible),
form textarea:focus:not(:focus-visible) {
  outline: none;
}

form input:focus-visible,
form select:focus-visible,
form textarea:focus-visible {
  outline: 2px solid var(--color-interactive-primary);
  outline-offset: 2px;
}

form fieldset {
  max-width: 100% !important;
}

/* Placeholders */

::placeholder {
  color: var(--color-fg-placeholder);
}

/* Checkbox / radio lists — base reset */

form .inputs-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

form input[type="checkbox"],
form input[type="radio"] {
  cursor: pointer;
  margin-right: var(--spacing-4);
}

/* Checkbox lists stay vertical */

form .inputs-list > li.hs-form-checkbox {
  display: block;
  margin: var(--spacing-6) 0;
}

form .inputs-list .hs-form-checkbox input,
form .inputs-list .hs-form-checkbox span {
  vertical-align: middle;
}

/* Radio lists render as pills — fieldsets containing only radios flex-wrap their
   items, each label styled as a pill that fills when the underlying radio is
   checked. Targets `.hs-form-radio` specifically so checkboxes are untouched.
   Pattern source: Contact page "Which best describes you?" question. */

form .hs-form-radio-display {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}

/* Radio fieldtype's question label sits further from the pill row than the
   default `form label` margin-bottom (8px) provides — matches the Figma
   form spec (16px) and gives the question + pills clear group identity. */

form .hs-fieldtype-radio > label {
  margin-bottom: var(--spacing-8);
}

form .hs-fieldtype-radio .input,
form .hs-fieldtype-radio .inputs-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-4);
}

form .hs-fieldtype-radio .inputs-list > li.hs-form-radio {
  display: inline-flex;
  margin: 0;
}

form .hs-form-radio .hs-form-radio-display {
  padding: var(--spacing-4) var(--spacing-8);
  border: 1px solid var(--color-border-primary);
  border-radius: var(--radius-full);
  background-color: var(--color-bg-canvas);
  color: var(--color-fg-primary);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  transition:
    background-color var(--motion-base) ease,
    color var(--motion-base) ease,
    border-color var(--motion-base) ease;
}

form .hs-form-radio .hs-form-radio-display:hover {
  background-color: var(--color-bg-secondary);
}

form .hs-form-radio:has(input[type="radio"]:checked) .hs-form-radio-display {
  background-color: var(--color-interactive-primary);
  color: var(--color-fg-on-primary);
  border-color: var(--color-interactive-primary);
}

form .hs-form-radio input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

form .hs-form-radio input[type="radio"]:focus-visible + span {
  outline: 2px solid var(--color-interactive-primary);
  outline-offset: 2px;
}

/* Date picker — structural + theme primary-color for selected/hover/today */

.hs-dateinput {
  position: relative;
}

.hs-dateinput:before {
  content: "\01F4C5";
  position: absolute;
  right: 10%;
  top: 50%;
  transform: translateY(-50%);
}

.hs-fieldtype-date .input .hs-dateinput:before {
  color: var(--color-fg-placeholder);
}

.fn-date-picker .pika-table thead th {
  color: var(--color-white);
}

.fn-date-picker td.is-selected .pika-button {
  border-radius: 0;
  box-shadow: none;
  background: var(--color-interactive-accent);
}

.fn-date-picker td .pika-button:hover,
.fn-date-picker td .pika-button:focus {
  border-radius: 0 !important;
  color: var(--color-white);
  background-color: var(--color-interactive-accent) !important;
}

.fn-date-picker td.is-today .pika-button {
  color: var(--color-interactive-accent);
}

/* File input — strip the default chrome */

form input[type="file"] {
  background-color: transparent;
  border: initial;
  padding: initial;
}

/* Rich-text content inside forms */

form .hs-richtext,
form .hs-richtext p {
  font-size: 0.875rem;
  margin: 0 0 1.4rem;
}

form .hs-richtext img {
  max-width: 100% !important;
}

/* GDPR consent */

.legal-consent-container .hs-form-booleancheckbox-display > span,
.legal-consent-container .hs-form-booleancheckbox-display > span p {
  margin-left: 1rem !important;
}

/* Validation */

.hs-form-required {
  color: #ef6b51;
}

.hs-input.invalid.error {
  border-color: #ef6b51;
}

.hs-error-msg {
  color: #ef6b51;
  margin-top: 0.35rem;
}

/* Submit button — inherits structural styling from _buttons.css; semantic
   tokens adapt to the surrounding section's data-mode. */

form input[type="submit"],
form .hs-button {
  background-color: var(--color-interactive-primary);
  color: var(--color-fg-on-primary);
}

form input[type="submit"]:hover,
form input[type="submit"]:focus,
form .hs-button:hover,
form .hs-button:focus {
  background-color: var(--color-interactive-primary-hover);
  color: var(--color-fg-on-primary);
}

form input[type="submit"]:active,
form .hs-button:active {
  background-color: var(--color-interactive-primary-active);
  color: var(--color-fg-on-primary);
}

/* Mobile — match the 48px hit-area floor used by Hero / CTA primary buttons
   so every primary CTA on mobile reads at the same height. WCAG 2.5.5
   baseline is 44; 48 exceeds it. Desktop stays on the global 40px button. */

@media (max-width: 991px) {
  form input[type="submit"],
  form .hs-button {
    min-height: 48px;
  }
}

/* Captcha */

.grecaptcha-badge {
  margin: 0 auto;
}

/* =============================================================================
   HubSpot Forms Client (hsfc) — new rendering system (post-2024 default)

   HubSpot rolled out a new forms rendering engine, identifiable by
   `data-hsfc-id="Renderer"` on the form wrapper. It emits an entirely
   different class namespace than the legacy system above (`.hsfc-*` instead
   of `.hs-form-*`), so all styling needs to be redeclared.

   Two layers:
   1. `:root` CSS variable overrides — HubSpot's documented customization
      surface (per "Override Embedded Form Styles with CSS Variables" doc).
      Covers font / colors / sizes / radii / padding across button + text
      input + textarea + labels + error alert.
   2. Structural CSS overrides at hsfc's `[data-hsfc-id="Renderer"]` scope
      for anything variables can't drive — notably the pill-radio treatment
      (hsfc renders radios as circles by default; no variable turns them
      into pills) and press-scale on the submit to match our `.button`
      convention from PR #63.

   Runtime-injection gotcha: hsfc styles inject via `<script>` at page
   load, landing in `<head>` AFTER our theme CSS. hsfc rules use
   `[data-hsfc-id=Renderer] .hsfc-Foo` (specificity 0,2,0) and win at equal
   specificity via source order. Our overrides need matching or higher
   specificity to bite.

   Legacy `.hs-form-*` selectors above are preserved — sandbox portals
   still use them; removing would break the sandbox smoke-testing workflow.
   ============================================================================ */

/* Global + component variable overrides — cascade to every hsfc form */

:root {
  /* Global — font + color inheritance root */
  --hsf-global__font-family: var(--font-family-primary);
  --hsf-global__font-size: var(--font-size-body-md);
  --hsf-global__color: var(--color-fg-primary);

  /* Field labels + description */
  --hsf-field-label__font-family: var(--font-family-primary);
  --hsf-field-label__font-size: var(--font-size-body-md);
  --hsf-field-label__color: var(--color-fg-primary);
  --hsf-field-description__font-family: var(--font-family-primary);
  --hsf-field-description__font-size: var(--font-size-body-sm);
  --hsf-field-description__color: var(--color-fg-secondary);

  /* Text input — used by Name / Email / Phone-number field */
  --hsf-field-input__font-family: var(--font-family-primary);
  --hsf-field-input__font-size: var(--font-size-body-md);
  --hsf-field-input__color: var(--color-fg-primary);
  --hsf-field-input__background-color: var(--color-bg-canvas);
  --hsf-field-input__border-color: var(--color-border-primary);
  --hsf-field-input__border-style: solid;
  --hsf-field-input__border-width: 1px;
  --hsf-field-input__border-radius: var(--radius-lg);
  --hsf-field-input__padding: var(--spacing-6);
  --hsf-field-input__placeholder-color: var(--color-fg-placeholder);

  /* Textarea — used by Message field */
  --hsf-field-textarea__font-family: var(--font-family-primary);
  --hsf-field-textarea__font-size: var(--font-size-body-md);
  --hsf-field-textarea__color: var(--color-fg-primary);
  --hsf-field-textarea__background-color: var(--color-bg-canvas);
  --hsf-field-textarea__border-color: var(--color-border-primary);
  --hsf-field-textarea__border-style: solid;
  --hsf-field-textarea__border-width: 1px;
  --hsf-field-textarea__border-radius: var(--radius-lg);
  --hsf-field-textarea__padding: var(--spacing-6);
  --hsf-field-textarea__placeholder-color: var(--color-fg-placeholder);

  /* Submit button — brand pill; hover + focus match the semantic-token
     cascade from the site-wide `.button` (data-mode-adaptive). Weight +
     padding match legacy `.button` in `_buttons.css` exactly so both
     rendering engines produce visually identical CTAs. */
  --hsf-button__font-family: var(--font-family-primary);
  --hsf-button__font-size: var(--font-size-body-md);
  --hsf-button__font-weight: var(--font-weight-body);
  --hsf-button__color: var(--color-fg-on-primary);
  --hsf-button__background-color: var(--color-interactive-primary);
  --hsf-button__background-image: none;
  --hsf-button__border-radius: var(--radius-full);
  --hsf-button__border-style: none;
  --hsf-button__border-width: 0;
  --hsf-button__padding: var(--spacing-4) var(--spacing-8);
  --hsf-button__box-shadow: none;
  --hsf-button--hover__background-color: var(--color-interactive-primary-hover);
  --hsf-button--hover__color: var(--color-fg-on-primary);
  --hsf-button--focus__background-color: var(--color-interactive-primary-hover);
  --hsf-button--focus__color: var(--color-fg-on-primary);

  /* Error alert — validation-feedback red. Literal for now; tokenize into
     `--color-fg-error` if a second brand-red surface appears. */
  --hsf-erroralert__color: #d92d20;

  /* Form wrapper — suppress hsfc's default padding + border on the "Step"
     background element, since `.contact-form__card` provides the visual
     container already. Prevents double-padding. */
  --hsf-background__background-color: transparent;
  --hsf-background__background-image: none;
  --hsf-background__border-style: none;
  --hsf-background__border-width: 0;
  --hsf-background__border-color: transparent;
  --hsf-background__padding: 0;

  /* Layout spacing — align to legacy token values so both engines feel
     identical. hsfc defaults are 20px between rows + 10px between label
     and input; legacy uses 24px + 8px respectively. */
  --hsf-row__vertical-spacing: var(--spacing-12);
  --hsf-module__vertical-spacing: var(--spacing-4);

  /* Submit button alignment — hsfc defaults to right-align (`end`);
     legacy forms bottom-left-align the submit. Match legacy. */
  --hsf-navigationrow-buttons-single__justify-content: flex-start;
}

/* Structural overrides — must match or beat hsfc's specificity (0,2,0) */

/* Radio group — pill treatment. Hide the native circular input; style
   the parent label as the pill face. Mirrors the legacy `.hs-form-radio`
   pattern above for consistency between the two rendering engines. */

[data-hsfc-id="Renderer"] .hsfc-RadioFieldGroup__Options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-4);
}

[data-hsfc-id="Renderer"] .hsfc-RadioFieldGroup__Options label {
  display: inline-flex;
  align-items: center;
  gap: 0;
  padding: var(--spacing-4) var(--spacing-8);
  border: 1px solid var(--color-border-primary);
  border-radius: var(--radius-full);
  background-color: var(--color-bg-canvas);
  color: var(--color-fg-primary);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  cursor: pointer;
  transition:
    background-color var(--motion-base) ease,
    color var(--motion-base) ease,
    border-color var(--motion-base) ease;
}

[data-hsfc-id="Renderer"] .hsfc-RadioFieldGroup__Options label:hover {
  background-color: var(--color-bg-secondary);
}

[data-hsfc-id="Renderer"] .hsfc-RadioFieldGroup__Options label:has(input:checked) {
  background-color: var(--color-interactive-primary);
  color: var(--color-fg-on-primary);
  border-color: var(--color-interactive-primary);
}

[data-hsfc-id="Renderer"] .hsfc-RadioFieldGroup__Options label:has(input:focus-visible) {
  outline: 2px solid var(--color-interactive-primary);
  outline-offset: 2px;
}

/* Visually hide the native circular radio — pill label carries the affordance.
   Compound selector `[data-hsfc-id="Renderer"] .hsfc-RadioFieldGroup__Options
   .hsfc-RadioInput` (specificity 0,3,0) beats hsfc's own base `.hsfc-RadioInput`
   rule (0,2,0) which sets `position: relative; border-radius: 50%`. Necessary
   because hsfc's runtime-injected `<style>` loads AFTER our theme CSS — at
   equal specificity source order gives hsfc the win. */

[data-hsfc-id="Renderer"] .hsfc-RadioFieldGroup__Options .hsfc-RadioInput {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  appearance: none;
}

/* Submit button — override hsfc's default `translateY(-1px)` hover +
   `translateY(1px)` press with our project's press-scale convention
   (PR #63 for global `.button`). Hover stays static; active scales down. */

[data-hsfc-id="Renderer"] .hsfc-Button:not([disabled]):hover {
  transform: none;
}

[data-hsfc-id="Renderer"] .hsfc-Button:not([disabled]):active {
  transform: scale(0.97);
}

/* Mobile — match the 48px hit-area floor on the submit button so it
   aligns with Hero / CTA primary buttons per the mobile-CTA pattern. */

@media (max-width: 991px) {
  [data-hsfc-id="Renderer"] .hsfc-Button {
    min-height: 48px;
  }
}

/* ReCaptcha — hsfc reserves 60px of min-height on the container even when
   Google's badge is fixed-position (invisible v2 / v3, floats to bottom-right
   of viewport and takes zero flow space). Zeroing the min-height removes
   the wasted gap between the message field and the submit button. If a
   visible checkbox challenge (v2 non-invisible) is later enabled in the
   form admin, the container will expand to fit its actual content. */

[data-hsfc-id="Renderer"] .hsfc-ReCaptchaV2 {
  min-height: 0;
}


  /* Search button input field and suggestions */
  .body-container-wrapper .hs-search-field__button {
    padding: 15px;
  }

  .body-container-wrapper .hs-search-field__bar--button-inline .hs-search-field__button {
    margin-left: 6px;
    margin-bottom: 0;
  }

  .body-container-wrapper .hs-search-field__button svg {
    height: 15px;
    fill: var(--color-white);
  }

  .body-container-wrapper .hs-search-field__bar > form > .hs-search-field__input {
    padding: 10px;
  }

  .body-container-wrapper .hs-search-field__suggestions li a {
    color: #494a52;
    padding: 0.35rem 0.7rem;
    text-decoration: none;
    transition: background-color var(--motion-slow);
  }

/* Table — semantic-token-driven; adapts to section data-mode. */

table {
  border: 1px solid var(--color-border-primary);
  background-color: var(--color-bg-canvas);
  border-collapse: collapse;
  margin-bottom: 1.4rem;
  overflow-wrap: break-word;
}

/* Table cells */

td,
th {
  border: 1px solid var(--color-border-primary);
  padding: 18px;
  color: var(--color-fg-primary);
  vertical-align: top;
}

/* Table header */

thead th {
  background-color: transparent;
  color: var(--color-fg-primary);
  vertical-align: bottom;
}

/* Table footer */

tfoot td {
  background-color: var(--color-bg-canvas);
  color: var(--color-fg-primary);
}

/* 5. Components — global UI surfaces */

/* Header — Juniper-aligned navbar.

   Transparent background; always fixed/sticky at top (no hide-on-scroll).
   Theming via data-mode attribute set by js/modules/header.js's
   IntersectionObserver — semantic tokens cascade automatically so the logo,
   nav links, and CTA adapt color across dark + light sections without
   per-variant CSS. */

/* ════════════════════════════════════════════════════════════════════════
   ACCESSIBILITY
   ════════════════════════════════════════════════════════════════════════ */

.header__skip {
  position: absolute;
  left: -9999px;
  z-index: 999;
  padding: var(--spacing-8);
  background-color: var(--color-bg-canvas);
  color: var(--color-fg-primary);
  text-decoration: none;
}

.header__skip:focus {
  left: 0;
  top: 0;
}

/* ════════════════════════════════════════════════════════════════════════
   HEADER ROOT — fixed, transparent, full-width, no scroll-hide
   ════════════════════════════════════════════════════════════════════════ */

.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  background-color: transparent;
  padding: 0 var(--page-padding-global);
  /* Smooth fade-in/out of the scroll-state backdrop. backdrop-filter is
     listed explicitly so Safari (which requires the -webkit prefix) gets
     the transition on the prefixed property too. */
  transition: background-color var(--motion-base) ease, backdrop-filter var(--motion-base) ease,
    -webkit-backdrop-filter var(--motion-base) ease;
}

/* Scroll-state backdrop — fades in once the page has scrolled past a
   small threshold (managed in `js/modules/header.js`). Keeps the navbar
   text readable when content scrolls underneath. The backdrop color
   resolves via the [data-mode] cascade — see `--color-nav-backdrop` in
   `css/tokens/semantic.css`. */
.header[data-scrolled="true"] {
  background-color: var(--color-nav-backdrop);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

/* ════════════════════════════════════════════════════════════════════════
   CONTAINER — grid with three columns: left rail (logo), center (matches
   hero's 768 content column so nav-list aligns with the hero), right rail
   (currently empty — gives the layout symmetry and a future hook). The
   center column shrinks below its 768 max on narrow viewports so things
   don't overflow.

   Padding lives on .header (outside this 1280 cap) so the inner 1280
   container holds pure content width — matching every section's inner
   1280 container (which also has no padding, since sections wear their
   horizontal padding on the outer section element). Result: the nav row
   aligns horizontally with every section's content row at every viewport.
   ════════════════════════════════════════════════════════════════════════ */

.header__container {
  display: grid;
  grid-template-columns: 1fr minmax(0, var(--max-width-large)) 1fr;
  align-items: center;
  width: 100%;
  max-width: var(--container-section);
  height: var(--header-height);
  margin: 0 auto;
}

.header__logo {
  grid-column: 1;
}

/* Column wrapper — spans the 768 content area + the right rail. Nav-list
   sits at the left edge (aligned with the hero column); CTA sits at the
   right edge of the navbar container, symmetric to the logo on the left. */

.header__column {
  grid-column: 2 / 4;
  display: flex;
  justify-content: space-between;
  align-items: center;
  min-width: 0;
}

.header__mobile-toggle {
  grid-column: 3;
  justify-self: end;
}

/* ════════════════════════════════════════════════════════════════════════
   LOGO — color: currentColor adapts via data-mode cascade
   ════════════════════════════════════════════════════════════════════════ */

.header__logo {
  /* Hug the logo content so the wrapper (and the <a> click area inside)
     doesn't stretch to fill its grid cell — the parent grids place
     .header__logo in a 1fr column on desktop and a 1fr column inside the
     mobile-overlay header. Without this the click area covered the full
     column width even though the SVG is ~132px wide. */
  justify-self: start;
  color: var(--color-fg-primary);
}

.header__logo a {
  display: flex;
  align-items: center;
  text-decoration: none;
  color: inherit;
}

.header__logo-svg {
  display: block;
  height: 32px;
  width: auto;
}

/* Responsive logo — full wordmark on wide desktop (≥1200), submark on narrow
   desktop (992–1199) and mobile (<992). Both SVGs render in the DOM; CSS
   hides the inactive variant. The 1199px swap exists because the 1fr/1fr
   grid rails shrink below the wordmark's ~132px width once the container
   drops below ~1200px (the center column locks at 768 so the rails take
   whatever's left). Swapping to the submark keeps the nav-list aligned
   with the hero column at all desktop widths without pushing inline nav
   into the hamburger. Aspect ratios differ (527:128 full vs 138:128 submark),
   so the same `height: 32px` resolves to different widths via `width: auto`.
   Note: the mobile menu boundary stays at <992 — that's a separate concern
   from the logo swap. */

.header__logo-svg--mark {
  display: none;
}

@media (max-width: 1199px) {
  .header__logo-svg--full {
    display: none;
  }

  .header__logo-svg--mark {
    display: block;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   DESKTOP NAVIGATION — flex list of links, 32px gap, semantic-token colors
   ════════════════════════════════════════════════════════════════════════ */

@media (min-width: 992px) /* desktop */ {
  .header__nav--mobile,
  .header__mobile-toggle,
  .header__mobile-overlay {
    display: none;
  }

  .header__cta--mobile {
    display: none;
  }
}

.header__nav-list {
  display: flex;
  align-items: center;
  gap: var(--spacing-16);
  margin: 0;
  padding: 0;
  list-style: none;
}

.header__nav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-3) 0;
  background-color: transparent;
  color: var(--color-fg-primary);
  border-radius: var(--radius-xl);
  font-family: var(--font-family-primary);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  font-weight: var(--font-weight-body);
  text-decoration: none;
  transition: color var(--motion-base) ease-in-out;
  /* Nav text is a click target, not content — disable selection so a click
     never accidentally highlights the label. */
  user-select: none;
}

.header__nav-link:hover,
.header__nav-link:focus {
  background-color: transparent;
  color: var(--color-interactive-foreground-hover);
}

.header__nav-link:active {
  color: var(--color-interactive-foreground-active);
}

.header__nav-link:focus:not(:focus-visible) {
  outline: none;
}

.header__nav-link:focus-visible {
  outline: 2px solid var(--color-interactive-accent);
  outline-offset: 2px;
}

/* Dropdown trigger button — same shape as nav-link but resets <button>
   defaults that _buttons.css applies to plain <button> elements. The
   trigger carries both .header__nav-link + .header__nav-button classes,
   so the nav-link interactive-state rules above cover color shifts; the
   nav-button rule here only resets resting-state <button> defaults. */

.header__nav-button {
  border: none;
  cursor: pointer;
  font-family: inherit;
}

/* Services dropdown trigger is a <button> for disclosure semantics, but
   visually it's a nav link sibling to the <a> Home/About/Contact links.
   Strip the global button :active scale so all four nav links share the
   same press feel (color shift only — no scale on text nav links). */
.header__nav-button:active {
  transform: none;
}

.header__nav-icon {
  display: inline-flex;
  align-items: center;
  line-height: 0;
  transition: transform var(--motion-base) ease-in-out;
}

/* Mobile opens dropdowns on click (not hover), so the hover/focus-within
   triggers only apply at desktop widths where the popup also opens on
   hover. aria-expanded="true" always wins regardless of viewport so the
   chevron flips whenever the disclosure is open. */
@media (min-width: 992px) {
  .header__nav-item--dropdown:hover .header__nav-icon,
  .header__nav-item--dropdown:focus-within .header__nav-icon {
    transform: rotate(180deg);
  }
}

.header__nav-button[aria-expanded="true"] .header__nav-icon {
  transform: rotate(180deg);
}

/* ════════════════════════════════════════════════════════════════════════
   DROPDOWN — bg-canvas, semantic-driven; positioned below the trigger
   ════════════════════════════════════════════════════════════════════════ */

.header__nav-item--dropdown {
  position: relative;
}

.header__dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  min-width: 280px;
  margin: var(--spacing-2) 0 0 0;
  padding: var(--spacing-4);
  background-color: var(--color-bg-canvas);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  list-style: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px);
  z-index: 100;
  transition: opacity var(--motion-base), transform var(--motion-base), visibility var(--motion-base);
}

.header__nav-item--dropdown:hover .header__dropdown,
.header__nav-item--dropdown:focus-within .header__dropdown,
.header__nav-button[aria-expanded="true"] + .header__dropdown {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.header__dropdown li {
  margin: 0;
}

.header__dropdown a {
  display: block;
  padding: var(--spacing-6) var(--spacing-8);
  color: var(--color-fg-primary);
  border-radius: var(--radius-md);
  font-family: var(--font-family-primary);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  text-decoration: none;
  transition: color var(--motion-base) ease-in-out, background-color var(--motion-base) ease-in-out;
}

.header__dropdown a:hover,
.header__dropdown a:focus {
  background-color: var(--color-bg-secondary);
  color: var(--color-interactive-foreground-hover);
}

.header__dropdown a:active {
  background-color: var(--color-bg-secondary);
  color: var(--color-interactive-foreground-active);
}

/* ════════════════════════════════════════════════════════════════════════
   CTA — sized + styled by .button .button--primary in _buttons.css;
   icon size override for the trailing arrow-up-right (14px per Figma)
   ════════════════════════════════════════════════════════════════════════ */

.header__cta {
  flex-shrink: 0;
}

.header__cta-link {
  white-space: nowrap;
}

.header__cta-icon {
  width: 14px;
  height: 14px;
  transition: transform var(--motion-fast) var(--ease-out-strong);
}

.header__cta-link:hover .header__cta-icon,
.header__cta-link:focus-visible .header__cta-icon {
  transform: translate(2px, -2px);
}

@media (prefers-reduced-motion: reduce) {
  .header__cta-link:hover .header__cta-icon,
  .header__cta-link:focus-visible .header__cta-icon {
    transform: none;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   MOBILE — Juniper-aligned mobile-menu sheet (<992px).

   Layout: 90px navbar with logo + 44×44 round hamburger toggle. Tapping
   the toggle slides a full-screen sheet in from the right. The sheet has
   its own 90px navbar at top (logo + 44×44 round close button), then a
   centered content column holding the vertical nav-link list (with the
   Services dropdown expanding inline) and a full-width CTA pill at the
   bottom of the column.

   The overlay element carries data-mode="dark" so semantic tokens inside
   the sheet always resolve to dark-mode values — the sheet stays dark
   even on light pages. Background reads --color-bg-primary which in dark
   mode is neutral-900 (#18181b), matching the Figma spec.
   ════════════════════════════════════════════════════════════════════════ */

@media (max-width: 991px) {
  .header__column,
  .header__cta--desktop {
    display: none;
  }

  .header__container {
    grid-template-columns: 1fr auto;
  }

  /* ─── 44×44 round hamburger toggle ──────────────────────────────────── */

  .header__mobile-toggle {
    grid-column: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    color: var(--color-fg-primary);
    background-color: transparent;
    border: none;
    border-radius: var(--radius-full);
    cursor: pointer;
    transition: background-color var(--motion-fast) ease-in-out;
  }

  .header__mobile-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
  }

  /* ─── Full-screen sheet — slide-in from the right ───────────────────── */

  .header__mobile-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    /* dvh tracks browser-chrome state on mobile so the overlay always fills
       the visible viewport — vh alone leaves a gap when toolbars hide on iOS
       Safari + similar. vh line is the fallback for pre-Safari-15.4 /
       pre-Chrome-108 browsers that don't understand dvh. */
    height: 100vh;
    height: 100dvh;
    background-color: var(--color-bg-primary);
    transform: translateX(100%);
    z-index: 9999;
    overflow-y: auto;
    transition: transform var(--motion-slow) ease-in-out;
    display: flex;
    flex-direction: column;
  }

  .header__mobile-overlay[aria-hidden="false"] {
    transform: translateX(0);
  }

  /* ─── Sheet's own navbar — mirrors the outer .header layout ─────────── */

  .header__mobile-header {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    height: var(--header-height);
    padding: 0 var(--page-padding-global);
    flex-shrink: 0;
    /* Same content-cap math as the non-overlay header + footer-content +
       footer-legal — calc(container + 2*padding) so the inner content
       area resolves to var(--container-section) after the side padding,
       keeping the logo / hamburger-close alignment consistent with the
       sections + outer header at the 576-991 mid-band cap (768). */
    width: 100%;
    max-width: calc(var(--container-section) + 2 * var(--page-padding-global));
    margin: 0 auto;
  }

  .header__mobile-close {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    color: var(--color-fg-primary);
    background-color: transparent;
    border: none;
    border-radius: var(--radius-full);
    cursor: pointer;
    transition: background-color var(--motion-fast) ease-in-out;
  }

  /* Cascade guards + interactive states — the global button rule paints
     these icon-only buttons as filled pills on :hover / :focus / :active
     (a white-circle-on-dark-navbar look). Override to keep them transparent
     across all states. Press feedback comes via :active using a low-opacity
     tint of the same color the cascade would have applied — mode-adaptive
     via color-mix, so the SVG icon itself stays at full opacity. Mouse and
     touch users see no persistent focus ring after click; keyboard users
     still get the brand accent outline via :focus-visible. */

  .header__mobile-toggle:hover,
  .header__mobile-toggle:focus,
  .header__mobile-close:hover,
  .header__mobile-close:focus {
    background-color: transparent;
    color: var(--color-fg-primary);
  }

  .header__mobile-toggle:active,
  .header__mobile-close:active {
    background-color: color-mix(in srgb, var(--color-interactive-primary-hover) 15%, transparent);
    color: var(--color-fg-primary);
  }

  .header__mobile-toggle:focus:not(:focus-visible),
  .header__mobile-close:focus:not(:focus-visible) {
    outline: none;
  }

  .header__mobile-toggle:focus-visible,
  .header__mobile-close:focus-visible {
    outline: 2px solid var(--color-interactive-accent);
    outline-offset: 2px;
  }

  /* ─── Sheet content area — centered column, nav + bottom CTA ────────── */

  .header__nav--mobile {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-12);
    padding: var(--spacing-8) var(--page-padding-global) var(--spacing-64);
    flex: 1;
    /* Match the mobile-header content-cap math so nav items + CTA align
       with the logo / close above + the section content below. */
    width: 100%;
    max-width: calc(var(--container-section) + 2 * var(--page-padding-global));
    margin: 0 auto;
  }

  .header__nav--mobile .header__nav-list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .header__nav--mobile .header__nav-list > li {
    padding: 0;
  }

  .header__nav--mobile .header__nav-link {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    min-height: 48px;
    /* No horizontal padding — main nav-link text aligns with the logo's
       left edge (no inset). Vertical padding stays for tap-target height.
       The dropdown sub-items keep their own horizontal padding via the
       more-specific `.header__nav--mobile .header__dropdown a` rule below
       so the Services children stay visually indented. */
    padding: var(--spacing-4) 0;
    font-size: var(--font-size-body-lg);
    line-height: var(--line-height-body-lg);
    border-radius: var(--radius-lg);
  }

  /* ─── Services disclosure — expands inline, not popup ───────────────── */

  .header__nav--mobile .header__dropdown {
    position: static;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2);
    margin: 0;
    padding: 0 0 0 var(--spacing-8);
    max-height: 0;
    background-color: transparent;
    border-radius: 0;
    box-shadow: none;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    overflow: hidden;
    transition: opacity var(--motion-fast) ease-in-out, max-height var(--motion-slow) ease-in-out,
      padding-top var(--motion-slow) ease-in-out;
  }

  .header__nav-button[aria-expanded="true"] + .header__dropdown {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    padding-top: var(--spacing-2);
    max-height: 500px;
  }

  .header__nav--mobile .header__dropdown a {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 48px;
    padding: var(--spacing-4) var(--spacing-6);
    font-size: var(--font-size-body-lg);
    line-height: var(--line-height-body-lg);
    color: var(--color-fg-primary);
    background-color: transparent;
    border-radius: var(--radius-lg);
    text-decoration: none;
  }

  .header__nav--mobile .header__dropdown a:hover,
  .header__nav--mobile .header__dropdown a:focus {
    color: var(--color-interactive-foreground-hover);
  }

  .header__nav--mobile .header__dropdown a:active {
    color: var(--color-interactive-foreground-active);
  }

  /* ─── CTA at bottom of nav column ───────────────────────────────────── */

  .header__cta--mobile {
    width: 100%;
  }

  .header__cta--mobile .header__cta-link {
    width: 100%;
    min-height: 48px;
    justify-content: center;
  }
}
/* Site footer region — shared mesh-gradient backdrop spanning the CTA
   (when present) + the global footer (content + legal). One canvas covers
   all three slabs for visual continuity matching the Figma frame. Used
   only in `templates/layouts/base.html`'s footer block.

   `data-mode="dark"` on the wrapper drives the semantic-token cascade
   inside it, so child sections (CTA, footer-content, footer-legal) read
   dark-mode fg-primary / fg-secondary / fg-tertiary / border-tertiary
   without any per-component overrides.

   Section dividers — adjacent slabs are separated by a 1px border-tertiary
   line. Using `> * + *` skips the first child, so when `page_show_cta` is
   false on a given page the footer becomes the first child and correctly
   loses its top border. */

.site-footer-region {
  position: relative;
  isolation: isolate;
  background-color: var(--color-bg-canvas);
}

.site-footer-region__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
}

.site-footer-region > * + * {
  border-top: 1px solid var(--color-border-tertiary);
}
/* Footer — transparent wrapper containing footer-content + footer-legal.
   The mesh-gradient backdrop + dark-mode cascade are owned by the parent
   `.site-footer-region` in base.html. Per-section padding lives in the
   module CSS (footer-content.module/module.css + footer-legal.module/
   module.css), not here. */

.footer {
  display: flex;
  flex-direction: column;
}

.footer > * + * {
  border-top: 1px solid var(--color-border-tertiary);
}
/* Eyebrow — pill label component used to introduce content sections.

   Dot (brand-secondary magenta) + uppercase label inside a rounded pill.
   Data-mode adaptive: light mode = solid neutral surface; dark mode = glassy
   translucent surface over the section background. Dot color is constant
   across modes (brand identity).

   Markup is emitted by `templates/macros/eyebrow.html`. */

.eyebrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-4);
  padding: var(--spacing-4) var(--spacing-8);
  background-color: var(--color-bg-primary);
  border: 1px solid var(--color-border-primary);
  border-radius: var(--radius-full);
  /* Decorative section-introducer pill — never quoted as content. */
  user-select: none;
}

.eyebrow__dot {
  width: 12px;
  height: 12px;
  border-radius: var(--radius-full);
  background-color: var(--color-accent-secondary);
  flex-shrink: 0;
}

.eyebrow__label {
  font-family: var(--font-family-primary);
  font-size: var(--font-size-supporting-small);
  font-weight: 500;
  line-height: 14px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--color-fg-primary);
  white-space: nowrap;
}

/* Dark-mode glassy override — Juniper-sourced literal rgba values. The
   eyebrow on a dark section uses a translucent white surface instead of
   the opaque neutral surface the semantic cascade would otherwise yield;
   this is a deliberate Juniper design choice (glassy chip effect). */
[data-mode="dark"] .eyebrow {
  background-color: rgba(255, 255, 255, 0.08);
  border-color: rgba(212, 212, 216, 0.08);
}
/* Avatar — round avatar with initials in a colored circle.

   Three sizes mirror the Juniper avatar scale (sm/md/lg = 48/64/88px) using
   tier-based typography (header-xs/sm/lg for the initials).

   Color variants come from the Juniper decorative palette (Tailwind-sourced).
   These are component-scoped colors — they're NOT brand tokens (which stay in
   primitives.css) and NOT semantic tokens (which stay in semantic.css). The
   per-client variance scope locks brand color seeds + icon library + fonts
   ONLY (see [[juniper-design-system-foundation]]); decorative atoms like
   these palette colors are component-private hardcodes.

   Markup is emitted by `templates/macros/avatar.html`. */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  overflow: clip;
  font-family: var(--font-family-primary);
  font-weight: 500;
  letter-spacing: -0.01em;
}

/* ─── Sizes ────────────────────────────────────────────────────────────── */

.avatar--sm {
  width: 48px;
  height: 48px;
  font-size: var(--font-size-header-xs);
  line-height: var(--line-height-header-xs);
}

.avatar--md {
  width: 64px;
  height: 64px;
  font-size: var(--font-size-header-sm);
  line-height: var(--line-height-header-sm);
}

.avatar--lg {
  width: 88px;
  height: 88px;
  font-size: var(--font-size-header-lg);
  line-height: var(--line-height-header-lg);
}

/* ─── Color variants — Juniper decorative palette (Tailwind hex literals) ── */

.avatar--rose {
  background-color: #fecdd3;
  color: #9f1239;
}
.avatar--pink {
  background-color: #fbcfe8;
  color: #9d174d;
}
.avatar--amber {
  background-color: #fde68a;
  color: #92400e;
}
.avatar--emerald {
  background-color: #a7f3d0;
  color: #065f46;
}
.avatar--teal {
  background-color: #99f6e4;
  color: #115e59;
}
.avatar--violet {
  background-color: #ddd6fe;
  color: #3730a3;
}
/* Testimonial Bubble — speech-bubble component for a single testimonial.

   Structure matches Figma's component shape:
     .testimonial-bubble = flex row container
       .testimonial-bubble__tail    = clip-path triangle pointing at avatar
       .testimonial-bubble__content = rounded rectangle with quote + stars + attribution

   The bubble's OUTER edge (which includes the tail) is what sits at the
   spacing-3 gap from the avatar — matching Figma's `--space/spacing-3` (6px)
   between avatar and bubble.

   Tail uses a clip-path triangle for a simple, scalable shape. Color matches
   the content box's background-color (both reference the same token).

   Markup is emitted by `templates/macros/testimonial-bubble.html`. */

.testimonial-bubble {
  position: relative;
  display: flex;
  align-items: flex-start;
}

/* ─── Tail — clip-path triangle, sized to align with avatar center ─────── */

.testimonial-bubble__tail {
  flex-shrink: 0;
  width: 16px;
  height: 24px;
  /* Align tail's vertical center with the avatar's center. Avatar is 88px
     tall; its center sits 44px below the slot top. Bubble's top is 24px
     above the slot top (per .reviews__bubble's `top: -24px`), so the tail
     needs to sit 44 + 24 = 68px below bubble top. Tail height 24, so tail
     top = 68 - 12 = 56px from bubble top. */
  margin-top: 56px;
  background-color: var(--color-primary-200);
}

.testimonial-bubble--tail-left .testimonial-bubble__tail {
  /* Tail comes FIRST in flex order (left of content), points LEFT toward
     the avatar that sits to the left of the bubble. Negative right margin
     overlaps the content by 1px to mask the sub-pixel anti-aliasing seam
     between the two same-colored boxes. */
  order: -1;
  clip-path: polygon(100% 0, 0 50%, 100% 100%);
  margin-right: -1px;
}

.testimonial-bubble--tail-right .testimonial-bubble__tail {
  /* Tail comes LAST in flex order (right of content), points RIGHT toward
     the avatar that sits to the right of the bubble. */
  order: 1;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
  margin-left: -1px;
}

/* ─── Content — the rounded rectangle that holds quote + stars + attribution ── */

.testimonial-bubble__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-12);
  /* Literal — bubble content width is a Juniper component spec (307px). */
  width: 307px;
  padding: var(--spacing-8);
  background-color: var(--color-primary-200);
  color: var(--color-primary-700);
  border-radius: var(--radius-2xl);
}

.testimonial-bubble__quote {
  font-family: var(--font-family-primary);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  font-weight: var(--font-weight-body);
  color: inherit;
  margin: 0;
}

.testimonial-bubble__bottom {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-4);
  width: 100%;
}

.testimonial-bubble__stars {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  color: inherit;
}

.testimonial-bubble__star {
  display: block;
  flex-shrink: 0;
}

.testimonial-bubble__attribution {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  color: inherit;
}

.testimonial-bubble__name {
  font-family: var(--font-family-primary);
  font-size: var(--font-size-body-md);
  line-height: var(--line-height-body-md);
  font-weight: var(--font-weight-body-semibold);
  margin: 0;
}

.testimonial-bubble__position {
  font-family: var(--font-family-primary);
  font-size: var(--font-size-body-sm);
  line-height: var(--line-height-body-sm);
  font-weight: var(--font-weight-body);
  margin: 0;
}
/* Reveal-on-view animations — page-entrance reveals triggered by an
   IntersectionObserver (js/lib/reveal-on-view.js) for in-scroll surfaces
   + a one-shot module (js/modules/hero-reveal.js) for the above-the-fold
   Hero. Elements declare `class="reveal-on-view reveal-on-view--<variant>"`;
   JS adds `.is-visible` when the element should reveal. CSS owns all
   timing + easing. Each variant has its own animation character so
   different element types feel distinct while same-type elements feel
   consistent across the site.

   The `html.js-reveal-ready` guard is set by a tiny synchronous inline
   script in <head> (templates/layouts/base.html) so the opacity:0 initial
   state only applies when JS will actually run. Without JS the reveal
   classes are no-ops and elements render normally — progressive
   enhancement, not a hard dependency.

   prefers-reduced-motion strips all transforms but keeps the opacity
   fade so the cascading reveal sequence is preserved without movement. */

.js-reveal-ready .reveal-on-view {
  opacity: 0;
  transition: opacity 400ms var(--ease-out-strong), transform 400ms var(--ease-out-strong);
  transition-delay: var(--reveal-delay, 0ms);
}

.js-reveal-ready .reveal-on-view.is-visible {
  opacity: 1;
  transform: none;
}

/* Eyebrow — pill character; quick "pop" via scale */

.js-reveal-ready .reveal-on-view--eyebrow {
  transform: scale(0.95);
  transition-duration: 400ms;
}

/* Headline — primary attention element; slower + larger movement */

.js-reveal-ready .reveal-on-view--headline {
  transform: translateY(16px);
  transition-duration: 700ms;
}

/* Subhead / body copy — slide up from below with a slower fade so the
   movement reads as deliberate rather than incidental. */

.js-reveal-ready .reveal-on-view--body {
  transform: translateY(24px);
  transition-duration: 800ms;
}

/* CTA — opacity + translateY + subtle scale so it feels like it "lands" */

.js-reveal-ready .reveal-on-view--cta {
  transform: translateY(8px) scale(0.96);
  transition-duration: 600ms;
}

/* List item / card / row — bigger initial translateY than body so each row's
   entrance reads clearly when staggered; meant to be staggered internally via
   per-item --reveal-delay set inline or via JS */

.js-reveal-ready .reveal-on-view--list-item {
  transform: translateY(32px);
  transition-duration: 600ms;
}

/* Decorative bubble — pop-in with subtle scale + mild overshoot.
   Mass/presence matters more than reading flow here. */

.js-reveal-ready .reveal-on-view--decorative-bubble {
  transform: scale(0.85);
  transition: opacity 500ms var(--ease-out-strong), transform 500ms cubic-bezier(0.34, 1.56, 0.64, 1);
  transition-delay: var(--reveal-delay, 0ms);
}

/* Hero word-by-word — words pre-render as <span class="hero__word">
   via js/modules/hero-reveal.js; each word gets its own --reveal-delay
   set inline. Opacity + blur (8px → 0) for a soft "coming into focus"
   reading flow. filter: blur() is GPU-accelerated; 8px is well under
   Emil's 20px ceiling for Safari perf.

   The headline parent hides itself until JS sets data-words-split, then
   re-appears with its word children handling opacity individually. Kills
   the FOUC where the full <h1> would otherwise flash visible at first
   paint (before JS wraps words in spans). */

.js-reveal-ready .hero__headline:not([data-words-split]) {
  opacity: 0;
}

.js-reveal-ready .hero__word {
  opacity: 0;
  filter: blur(8px);
  transition: opacity 600ms var(--ease-out-strong), filter 600ms var(--ease-out-strong);
  transition-delay: var(--reveal-delay, 0ms);
}

.js-reveal-ready .hero__word.is-visible {
  opacity: 1;
  filter: blur(0);
}

/* Interactive-state hand-off — once a reveal-on-view button finishes its
   entrance, restore the standard button transition so press-scale feedback
   uses the snappy ease-out feel (not the reveal's 500ms spring-out curve).
   Higher specificity (4 classes + 1 element) beats the reveal's resting
   .is-visible rule above. */

.js-reveal-ready button.reveal-on-view.is-visible,
.js-reveal-ready .button.reveal-on-view.is-visible {
  transition: background-color var(--motion-base) ease-in-out, color var(--motion-base) ease-in-out,
    border-color var(--motion-base) ease-in-out, opacity var(--motion-base) ease-in-out,
    transform var(--motion-base) ease-out;
}

.js-reveal-ready button.reveal-on-view.is-visible:active,
.js-reveal-ready .button.reveal-on-view.is-visible:active {
  transform: scale(0.97);
}

/* Reduced motion — drop transforms + filter (motion sickness mitigation)
   but keep opacity fades so reveal sequencing still reads as intentional. */

@media (prefers-reduced-motion: reduce) {
  .js-reveal-ready .reveal-on-view,
  .js-reveal-ready .reveal-on-view--eyebrow,
  .js-reveal-ready .reveal-on-view--headline,
  .js-reveal-ready .reveal-on-view--body,
  .js-reveal-ready .reveal-on-view--cta,
  .js-reveal-ready .reveal-on-view--list-item,
  .js-reveal-ready .reveal-on-view--decorative-bubble {
    transform: none;
    transition-property: opacity;
    transition-duration: 300ms;
  }

  .js-reveal-ready .hero__word {
    filter: none;
    transition-property: opacity;
    transition-duration: 300ms;
  }

  .js-reveal-ready button.reveal-on-view.is-visible:active,
  .js-reveal-ready .button.reveal-on-view.is-visible:active {
    transform: none;
  }
}
/* Shared module patterns — token-driven styling for module classes that
   are HubSpot-injected (blog listing + pagination). Migrated from
   theme-overrides.css "Modules" section. */

/* Blog listing — HubSpot-injected classes on /blog index pages.
   Title links render with theme h2 typography. */

.hs-blog-post-listing__post-title-link,
.hs-blog-post-listing__post-title-link:hover,
.hs-blog-post-listing__post-title-link:focus,
.hs-blog-post-listing__post-title-link:active {
  font-family: var(--font-family-primary);
  font-weight: var(--font-weight-display);
}

.hs-blog-post-listing__post-title-link {
  color: var(--color-fg-primary);
}

.hs-blog-post-listing__post-title-link:hover,
.hs-blog-post-listing__post-title-link:focus {
  color: var(--color-fg-primary);
}

.hs-blog-post-listing__post-title-link:active {
  color: var(--color-fg-primary);
}

.hs-blog-post-listing__post-tag,
.hs-blog-post-listing__post-author-name {
  color: var(--color-fg-primary);
}

/* Blog pagination — HubSpot-injected classes */

.hs-pagination__link-text,
.hs-pagination__link--number {
  color: var(--color-fg-primary);
}

.hs-pagination__link:hover .hs-pagination__link-text,
.hs-pagination__link:focus .hs-pagination__link-text,
.hs-pagination__link--number:hover,
.hs-pagination__link--number:focus {
  color: var(--color-fg-primary);
}

.hs-pagination__link:active .hs-pagination__link-text,
.hs-pagination__link--number:active {
  color: var(--color-fg-primary);
}

.hs-pagination__link-icon svg {
  fill: var(--color-fg-primary);
}

/* ─── Shared section utilities ──────────────────────────────────────────────
   Generic section-heading + section-description + .on-dark text utilities used
   across templates (service.html, contact.html, etc.). Migrated from
   theme-overrides.css "Modules & Sections" — these are content-typography
   primitives, not module-scoped, and live here alongside other shared
   patterns. */

.section-heading {
  max-width: 768px;
  color: var(--color-fg-primary);
}

.section-description {
  max-width: 635px;
  color: var(--color-fg-secondary);
}

.section-heading__centered {
  margin: 0 auto;
  text-align: center;
}

.section-description__centered {
  margin: 0 auto;
  text-align: center;
}

.on-dark {
  color: var(--color-bg-primary);
}

/* ─── HubSpot content-wrapper images ────────────────────────────────────────
   HubSpot injects .hs_cos_wrapper_type_* wrappers around CTAs, logos, and
   rich-text fields wherever they appear in templates and modules. This rule
   keeps the contained images responsive. Salvaged from the deleted
   _default-modules.css (TICKET-DEV-006); the rest of that file styled
   HubSpot's .hs-menu-wrapper output, which our BEM-emitting navigation macro
   never produces, so the .hs-menu-wrapper rules were retired as dead code. */

.hs_cos_wrapper_type_cta img,
.hs_cos_wrapper_type_logo img,
.hs_cos_wrapper_type_rich_text img {
  height: auto;
  max-width: 100%;
}

/* 6. Utilities — final layer, helper classes that win the cascade */

/* For content that needs to be visually hidden but stay visible for screenreaders */

.show-for-sr {
  border: 0 !important;
  clip: rect(0, 0, 0, 0) !important;
  height: 1px !important;
  overflow: hidden !important;
  padding: 0 !important;
  position: absolute !important;
  white-space: nowrap !important;
  width: 1px !important;
}

@media (max-width: 991px) {
  .show-for-sr--mobile {
    border: 0 !important;
    clip: rect(0, 0, 0, 0) !important;
    height: 1px !important;
    overflow: hidden !important;
    padding: 0 !important;
    position: absolute !important;
    white-space: nowrap !important;
    width: 1px !important;
  }
}