/* ==========================================================================
   components.css — PromptingPress Component Styles

   RULES:
   - Only CSS variables from base.css. No raw hex values, ever.
   - BEM naming: .block__element--modifier
   - Each component section clearly labeled.
   - To retheme: edit base.css tokens only. Do not add hex here.
   ========================================================================== */

/* ==========================================================================
   SHARED: Button
   .btn is the shared base; variants .btn--secondary / .btn--outline / .btn--ghost
   (primary = bare .btn) are selected per-instance via the `button_variant` prop.
   The --btn-* tokens default to the historical hard-coded values, so an unset
   button renders byte-identically. They are NOT exposed as component style_slots:
   per-instance button color stays on component-scoped rules (e.g. .cta .btn), so
   a section's __pp_style cannot leak into nested buttons.
   ========================================================================== */

.btn {
  display: inline-block;
  padding: var(--btn-padding-y) var(--btn-padding-x);
  background-color: var(--btn-bg, var(--color-accent));
  color: var(--btn-text, var(--color-bg));
  font-weight: 600;
  font-size: 1rem;
  text-decoration: none;
  border: 2px solid var(--btn-border-color, var(--color-accent));
  border-radius: var(--btn-radius, var(--radius));
  box-shadow: var(--btn-shadow, none);
  cursor: pointer;
  transition: background-color var(--transition), border-color var(--transition), color var(--transition);
  min-height: 44px; /* accessibility: 44px min touch target */
  line-height: 1.4;
}

.btn:hover {
  background-color: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
  color: var(--color-bg);
}

.btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

.btn--outline {
  background-color: transparent;
  color: var(--color-accent);
}

.btn--outline:hover {
  background-color: var(--color-accent);
  color: var(--color-bg);
}

/* Secondary: muted surface fill, used for lower-emphasis actions. */
.btn--secondary {
  background-color: var(--color-surface);
  color: var(--color-text);
  border-color: var(--color-border);
}

.btn--secondary:hover {
  background-color: var(--color-border);
  border-color: var(--color-border);
  color: var(--color-text);
}

/* Ghost: borderless text-style button for tertiary actions. */
.btn--ghost {
  background-color: transparent;
  color: var(--color-accent);
  border-color: transparent;
}

.btn--ghost:hover {
  background-color: var(--color-surface);
  border-color: transparent;
  color: var(--color-accent);
}

/* ==========================================================================
   SHARED: border-trigger cascade immunity (issue 332)

   WordPress core's global stylesheet ships attribute-SUBSTRING selectors:

     html :where([style*="border-width"]) { border-style: solid }
     html :where([style*="border-color"]) { border-style: solid }

   Core's intent is the block editor's `style="border-width:2px"` shorthand. But
   our style slots render as inline CUSTOM PROPERTIES on the component root
   (style="--grid-card-border-width:0px"), and the substring lives in the property
   NAME — so the selector matches the root even when the value is 0 and the border
   the slot controls actually lives on a DESCENDANT (the card). A root that
   declared no border of its own then computed core's injected `solid` at the
   initial `medium` width: a visible 3px border nobody asked for. The 1.0-H dogfood
   hit exactly this and had to abandon two documented slots.

   The fix is immunity, not renaming: the slot names are public AI-facing surface.
   Specificity, precisely: `:where()` contributes zero, but the leading `html` type
   selector still counts, so core's rule weighs (0,0,1) — low, NOT zero. A class or
   attribute selector weighs (0,1,0) and outranks it outright. So declaring the border
   baseline on every element that can carry inline slot custom properties (the 12
   component roots, the per-card .grid__item of issue 306, and the per-row
   .section__panel-row of issue 334) makes core's rule a no-op
   for us — and it stays a no-op for slots that do not exist yet. (Note the corollary:
   a BARE ELEMENT selector of ours would only tie core at (0,0,1) and could lose on
   source order. The baseline must keep a class/attribute selector to hold its rank.)

   Both longhands are declared on purpose: `border-style: none` alone would defeat
   today's core rule, and `border-width: 0` keeps it defeated if core ever injects a
   width instead. The declared values ARE the CSS initial rendering (a style of
   `none` computes to a 0 width), so an unset component renders byte-identically.

   Placement is load-bearing: an attribute selector and a class selector both weigh
   (0,1,0), so this must stay ABOVE the component blocks — every component rule that
   really wants a border ( .hero, .cta, .section, .grid__item, .grid--dark, … ) then
   wins on source order. Do not move it below them. Nor inside an @media/@layer block:
   a breakpoint-scoped baseline leaves every other breakpoint exposed.

   Known consequence: at (0,1,0) this also outranks a BARE ELEMENT rule a site owner or
   plugin might put on a component root (e.g. `section { border-top: 1px solid }` in
   Customizer → Additional CSS, which loads after this file but weighs only (0,0,1)).
   That is the same specificity lever the fix uses against core, pointed outward. Site
   CSS that targets a class/attribute (`.section`, `[data-pp-component]`) still wins
   normally; only bare-element borders on the roots are suppressed.
   ========================================================================== */

[data-pp-component],
.grid__item,
.section__panel-row {
  border-style: none;
  border-width: 0;
}

/* ==========================================================================
   COMPONENT: nav
   Site header, logo, nav links, hamburger toggle.
   ========================================================================== */

:root {
  /* Static fallback for the sticky header's real height (issue 63) — JS
     (main.js) measures the actual rendered .site-header height on load and
     resize and overrides this, since header height varies with content,
     breakpoint, and font loading. Without this, an anchor jump to a
     component's id (hero, section, cta, etc. all support one) lands with
     the sticky header covering the heading, worst on mobile where the
     header is proportionally taller. Not a design token: computed, not
     AI-editable via update_design_token.
  */
  --header-height: 65px;
}

/* Header chrome slots (issue 333). The header is template-owned (issue 223) and
   declares no style_slots, so these are consumed from the pp_header_* site options
   that base.php maps onto nav's props; the issue-305 slot guard does not cover them
   and HeaderChromeTest pins the consume-plus-fallback contract instead.

   `background`, NOT `background-color`: --header-bg is a gradient-typed option
   (color OR gradient), and a gradient is a CSS <image> — assigning one to
   background-color is invalid, so the browser drops the declaration and the header
   paints nothing. The shorthand accepts both a plain color and a gradient. Its
   reset of the other background-* longhands is a no-op here: this rule is the only
   place .site-header touches background at all. Unset, it resolves to the same
   --color-bg as before, so default rendering is unchanged. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--header-bg, var(--color-bg));
  border-bottom: 1px solid var(--color-border);
}

.nav__container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 64px;
  gap: var(--space-md);
}

.nav__logo {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--header-text, var(--color-text));
  text-decoration: none;
  flex-shrink: 0;
  min-height: 44px;
  display: flex;
  align-items: center;
}

.nav__logo:hover {
  color: var(--color-accent);
}

.nav__logo-image {
  display: block;
  max-height: 2.5rem;
  width: auto;
  object-fit: contain;
}

/* Hamburger toggle button */
.nav__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  border-radius: var(--radius);
  color: var(--header-text, var(--color-text));
  cursor: pointer;
  transition: color var(--transition);
}

.nav__toggle:hover {
  color: var(--color-accent);
}

/* Nav menu — visible without JS (progressive enhancement); JS adds/removes hidden. */
.nav__menu {
  width: 100%;
  padding: var(--space-sm) 0 var(--space-md);
}

/* WP menu list */
.nav__menu ul {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  list-style: none;
}

/* Nav links route through --header-link-color; the logo and toggle route through
   --header-text. Two slots, mirroring the footer's text-vs-link split (issue 300),
   so a header can carry a muted wordmark with brighter links (or the reverse).
   Hover keeps --color-accent (a global design token). The active/current link COLOR
   also routes through --header-link-color (issue 355), falling back to --color-accent
   when the operator hasn't set a header link color — so styling the header no longer
   leaves the active link stuck on the global accent (the common one-page anchor-nav
   case marks every link current). The active EMPHASIS (font-weight:700) is unchanged. */
.nav__menu ul li a {
  display: block;
  padding: var(--space-sm) var(--space-sm);
  color: var(--header-link-color, var(--color-text));
  text-decoration: none;
  border-radius: var(--radius);
  transition: color var(--transition);
}

.nav__menu ul li a:hover {
  color: var(--color-accent);
  text-decoration: underline;
}

/* Active link: WP sets current-menu-item on <li> — this is the primary active style.
   Color follows --header-link-color so an operator's pp_header_link_color reaches the
   active link too (issue 355); --color-accent stays the fallback, so an unset header
   renders byte-identically to before. The bold weight marks the current item either way. */
.nav__menu ul li.current-menu-item > a,
.nav__menu ul li.current_page_item > a {
  font-weight: 700;
  color: var(--header-link-color, var(--color-accent));
}

/* aria-current="page" is a secondary enhancement (e.g. set by other means). */
.nav__menu ul li a[aria-current="page"] {
  font-weight: 700;
  color: var(--header-link-color, var(--color-accent));
}

/* Desktop: hamburger hidden, menu always visible */
@media (min-width: 768px) {
  .nav__toggle {
    display: none;
  }

  .nav__container {
    flex-wrap: nowrap;
  }

  /* When JS sets hidden on mobile, override it on desktop so menu is always shown. */
  .nav__menu[hidden] {
    display: flex;
  }

  .nav__menu {
    width: auto;
    padding: 0;
    display: flex;
    align-items: center;
  }

  .nav__menu ul {
    flex-direction: row;
    gap: var(--space-xs);
    align-items: center;
  }

  .nav__menu ul li a {
    padding: var(--space-xs) var(--space-sm);
  }
}

/* ==========================================================================
   SHARED: Anchor scroll offset (issue 63)
   Every content component supports an `id` prop, rendering it as a direct-
   link/deep-link anchor target. Without this, jumping to #section-id lands
   the target at the very top of the viewport, under the sticky header
   (worst on mobile, where the header is proportionally taller) — covering
   the heading instead of revealing it.
   ========================================================================== */

.hero,
.section,
.cta,
.grid,
.faq,
.stats,
.logos,
.embed,
.testimonials,
.table-section {
  scroll-margin-top: var(--header-height);
}

/* ==========================================================================
   COMPONENT: hero
   Full-width hero section. Variants: centered, left, split.
   ========================================================================== */

.hero {
  padding-top: var(--hero-padding-top, var(--space-xl));
  padding-bottom: var(--hero-padding-bottom, var(--space-xl));
  background: var(--hero-bg, var(--color-bg));
  border: var(--hero-border-width, 0) solid var(--hero-border-color, transparent);
  border-radius: var(--hero-radius, 0);
  box-shadow: var(--hero-shadow, none);
}

@media (min-width: 768px) {
  .hero {
    padding-top: var(--hero-padding-top, var(--space-2xl));
    padding-bottom: var(--hero-padding-bottom, var(--space-2xl));
  }
}

.hero__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

.hero__content {
  display: flex;
  flex-direction: column;
  gap: var(--hero-content-gap, var(--space-md));
  max-width: var(--hero-content-width, var(--measure-centered));
}

/* align-self keeps the pill sized to its text: .hero__content is a flex column,
   which blockifies inline-block and would otherwise stretch the eyebrow to the
   full content width. Centered/cover re-center it alongside their CTA overrides. */
.hero__eyebrow {
  display: inline-block;
  align-self: flex-start;
  padding: 0.35rem 0.85rem;
  margin-bottom: var(--space-sm);
  border: var(--hero-eyebrow-border-width, 0) solid var(--hero-eyebrow-border-color, transparent);
  border-radius: var(--hero-eyebrow-radius, 3px);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: var(--hero-eyebrow-text-transform, uppercase);
  color: var(--hero-eyebrow-color, var(--color-text));
  background: var(--hero-eyebrow-bg, var(--color-surface-accent));
}

.hero__title {
  font-size: var(--hero-title-size, clamp(2.5rem, 5vw, 4rem));
  font-weight: var(--hero-title-weight, var(--font-weight-heading));
  color: var(--hero-text, inherit);
  line-height: 1.03;
}

.hero__title-accent {
  color: var(--hero-title-accent-color, var(--color-accent));
}

/* Constrain heading width for left/split variants; centered uses text-align centering */
.hero--left .hero__title,
.hero--split .hero__title {
  max-width: 12ch;
}

@media (min-width: 768px) {
  .hero__title {
    font-size: var(--hero-title-size, clamp(3rem, 4.5vw, 4.5rem));
  }
}

.hero__subtitle {
  font-size: var(--hero-subtitle-size, 1.0625rem);
  color: var(--hero-subtitle-color, var(--color-muted));
  max-width: 40ch;
  line-height: 1.6;
}

/* align-self places the GROUP's box; justify-content packs the buttons INSIDE it. The
   two are independent, and only align-self was declared: while the box shrink-wraps its
   buttons the distinction is invisible, but as soon as the buttons WRAP the box fills the
   content column and the rows pack left again — centered hero included. Declare the
   packing explicitly rather than inheriting the flexbox initial value (issue 338). */
.hero__cta-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-self: flex-start;
  justify-content: flex-start;
}

.hero__cta {
  align-self: flex-start;
}

/* Centered variant */
.hero--centered .hero__inner {
  align-items: center;
  text-align: center;
}

.hero--centered .hero__subtitle {
  max-width: none;
}

.hero--centered .hero__eyebrow {
  align-self: center;
}

.hero--centered .hero__cta-group {
  align-self: center;
  justify-content: center;
}

.hero--centered .hero__cta {
  align-self: center;
}

/* The proof row is the one flex row .hero__content never shrink-wraps (it sets no
   align-items, so .hero__proof stretches). Its BOX is therefore centered already; its
   ITEMS are not, and text-align:center — which this layout does inherit — has no say in
   where a flex container puts its items. Centering follows the layout variant, so a
   centered hero needs no second instruction from the operator (issue 338). */
.hero--centered .hero__proof {
  justify-content: center;
}

/* Left variant — used by inner pages; compact vertical rhythm */
.hero--left {
  padding-top: var(--hero-padding-top, var(--space-xl));
  padding-bottom: var(--hero-padding-bottom, var(--space-xl));
}

.hero--left .hero__inner {
  align-items: flex-start;
  text-align: left;
}

/* Split variant: text left, image right at lg+ */
.hero--split .hero__inner {
  align-items: flex-start;
  text-align: left;
}

@media (min-width: 1024px) {
  .hero--split .hero__inner {
    display: grid;
    grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
    align-items: center;
    gap: var(--space-xl);
  }

  .hero--split .hero__content {
    max-width: var(--hero-content-width, 40rem);
  }
}

/* Cover variant: full-width background image with overlay */
/* A flex row whose single item is .container. The container's auto inline margins already
   absorb the free space, so this centering is what already happens — declared here only so
   the row states its intent instead of resting on the flexbox initial value (issue 338). */
.hero--cover {
  position: relative;
  background-size: cover;
  background-position: var(--hero-bg-position, center);
  background-repeat: no-repeat;
  min-height: 70vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hero__overlay {
  position: absolute;
  inset: 0;
  background: var(--hero-overlay-bg, var(--overlay-bg));
}

.hero--cover .container {
  position: relative;
  z-index: 1;
}

.hero--cover .hero__inner {
  align-items: center;
  text-align: center;
}

.hero--cover .hero__title {
  color: var(--hero-text, var(--color-bg));
}

.hero--cover .hero__subtitle {
  color: var(--hero-subtitle-color, var(--color-bg));
  opacity: 0.85;
  max-width: none;
}

.hero--cover .hero__eyebrow {
  align-self: center;
}

.hero--cover .hero__cta-group {
  align-self: center;
  justify-content: center;
}

.hero--cover .hero__cta {
  align-self: center;
}

/* Cover centers its content like .hero--centered, so its proof row packs the same way. */
.hero--cover .hero__proof {
  justify-content: center;
}

.hero--cover .btn--outline {
  border-color: var(--color-bg);
  color: var(--color-bg);
}

.hero--cover .btn--outline:hover {
  background-color: var(--color-bg);
  color: var(--color-text);
}

.hero__image-wrap {
  flex-shrink: 0;
}

.hero__surface {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  justify-content: center;
  min-height: 100%;
  /* Inner proof/artifact surface — per-instance style slots (#24). Each falls back to
     the original "soft SaaS" treatment so unset heroes render unchanged; the site-builder
     AI overrides these to reach a Brand Book target (e.g. a 2px brutalist artifact). */
  padding: var(--hero-surface-padding, var(--space-lg));
  border: var(--hero-surface-border-width, 1px) solid
    var(--hero-surface-border-color, var(--color-border));
  border-radius: var(--hero-surface-radius, calc(var(--radius) * 2));
  background: var(--hero-surface-bg,
    linear-gradient(180deg, var(--color-bg) 0%, var(--color-surface) 100%));
  box-shadow: var(--hero-surface-shadow, 0 20px 50px rgba(16, 24, 40, 0.08));
}

.hero__surface > * {
  margin: 0;
}

.hero__surface .hero__proof {
  margin-top: 0;
}

.hero__surface-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-muted);
}

.hero__surface-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.hero__surface-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--color-border);
}

.hero__surface-item:first-child {
  padding-top: 0;
  border-top: 0;
}

.hero__surface-key {
  font-size: 0.8125rem;
  color: var(--color-muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.hero__surface-value {
  font-size: 1rem;
  font-weight: 600;
  color: var(--color-text);
}

.hero__image {
  width: 100%;
  height: auto;
  aspect-ratio: var(--hero-image-aspect-ratio, auto);
  object-fit: cover;
  object-position: var(--hero-image-position, center);
  border-radius: var(--hero-image-radius, var(--radius));
}

/* Hero accent: scoped button colors via style slots.
   Only FILLED buttons take the accent as a background. Outline/ghost variants
   must keep their transparent fill so the secondary CTA stays visually distinct
   from the primary (a `.hero .btn` background here outranks `.btn--outline`'s
   transparent and would otherwise flatten both CTAs into identical filled buttons). */
.hero .btn:not(.btn--outline):not(.btn--ghost):not(.btn--secondary) {
  background-color: var(--hero-accent, var(--color-accent));
  border-color: var(--hero-accent, var(--color-accent));
}
.hero .btn:not(.btn--outline):not(.btn--ghost):not(.btn--secondary):hover {
  background-color: var(--hero-accent-hover, var(--color-accent-hover));
  border-color: var(--hero-accent-hover, var(--color-accent-hover));
}
/* Outline (secondary) hero CTA: foreground tracks --hero-text, which is defined
   to contrast --hero-bg, so the ghost button stays readable on any hero palette
   (the accent is not guaranteed to contrast the hero background). On hover it
   fills with the accent and flips text to the hero background color. */
.hero .btn--outline {
  color: var(--hero-text, var(--color-text));
  border-color: var(--hero-text, var(--color-text));
}
.hero .btn--outline:hover {
  background-color: var(--hero-accent, var(--color-accent));
  border-color: var(--hero-accent, var(--color-accent));
  color: var(--hero-bg, var(--color-bg));
}

/* Second CTA button (cta2): per-instance bg/border/color override, independent
   of the primary button (issue 111). Targeted via the dedicated .hero__cta--secondary
   class (hero.php) rather than a positional :nth-child selector — this
   codebase's CSS lint guard forbids nth-child/nth-of-type entirely, since a
   position-based selector would silently reattach to the wrong element if
   composition content were ever reordered. cta_text/cta2_text are fixed,
   named props (not a reorderable array), so a dedicated class is both safe
   and simpler than the positional alternative. Applies regardless of which
   variant cta2 renders as — the primary button could independently also be
   set to any of the 4 variants via cta_variant, and this must not leak onto
   it. One rule per variant, each falling back to that variant's own existing
   default, so an unset override renders byte-identically to today. */
.hero .hero__cta-group .hero__cta--secondary:not(.btn--outline):not(.btn--ghost):not(.btn--secondary) {
  background-color: var(--hero-cta2-bg, var(--hero-accent, var(--color-accent)));
  border-color: var(--hero-cta2-border, var(--hero-accent, var(--color-accent)));
  color: var(--hero-cta2-color, var(--btn-text, var(--color-bg)));
}
.hero .hero__cta-group .hero__cta--secondary:not(.btn--outline):not(.btn--ghost):not(.btn--secondary):hover {
  background-color: var(--hero-cta2-hover-bg, var(--hero-accent-hover, var(--color-accent-hover)));
  border-color: var(--hero-cta2-hover-border, var(--hero-accent-hover, var(--color-accent-hover)));
  color: var(--hero-cta2-hover-color, var(--btn-text, var(--color-bg)));
}

.hero .hero__cta-group .hero__cta--secondary.btn--outline {
  background-color: var(--hero-cta2-bg, transparent);
  color: var(--hero-cta2-color, var(--hero-text, var(--color-text)));
  border-color: var(--hero-cta2-border, var(--hero-text, var(--color-text)));
}
.hero .hero__cta-group .hero__cta--secondary.btn--outline:hover {
  background-color: var(--hero-cta2-hover-bg, var(--hero-accent, var(--color-accent)));
  border-color: var(--hero-cta2-hover-border, var(--hero-accent, var(--color-accent)));
  color: var(--hero-cta2-hover-color, var(--hero-bg, var(--color-bg)));
}

.hero .hero__cta-group .hero__cta--secondary.btn--secondary {
  background-color: var(--hero-cta2-bg, var(--color-surface));
  color: var(--hero-cta2-color, var(--color-text));
  border-color: var(--hero-cta2-border, var(--color-border));
}
.hero .hero__cta-group .hero__cta--secondary.btn--secondary:hover {
  background-color: var(--hero-cta2-hover-bg, var(--color-border));
  border-color: var(--hero-cta2-hover-border, var(--color-border));
  color: var(--hero-cta2-hover-color, var(--color-text));
}

.hero .hero__cta-group .hero__cta--secondary.btn--ghost {
  background-color: var(--hero-cta2-bg, transparent);
  color: var(--hero-cta2-color, var(--color-accent));
  border-color: var(--hero-cta2-border, transparent);
}
.hero .hero__cta-group .hero__cta--secondary.btn--ghost:hover {
  background-color: var(--hero-cta2-hover-bg, var(--color-surface));
  border-color: var(--hero-cta2-hover-border, transparent);
  color: var(--hero-cta2-hover-color, var(--color-accent));
}

/* Split ratio: grid column ratios at desktop */
@media (min-width: 1024px) {
  .hero--split[data-pp-split-ratio="60-40"] .hero__inner {
    grid-template-columns: 3fr 2fr;
  }
  .hero--split[data-pp-split-ratio="40-60"] .hero__inner {
    grid-template-columns: 2fr 3fr;
  }
}

@media (max-width: 767px) {
  .hero__content {
    max-width: var(--hero-content-width, none);
  }

  .hero__surface {
    padding: var(--hero-surface-padding, var(--space-md));
  }
}

/* Vertical align: cover + split variants (desktop only) */
@media (min-width: 1024px) {
  .hero--cover[data-pp-vertical-align="top"] {
    align-items: flex-start;
  }

  .hero--cover[data-pp-vertical-align="bottom"] {
    align-items: flex-end;
  }

  .hero--split[data-pp-vertical-align="top"] .hero__inner {
    align-items: start;
  }
  .hero--split[data-pp-vertical-align="bottom"] .hero__inner {
    align-items: end;
  }
}

/* Proof slot: trust signals after CTA.
   justify-content is the row's packing, and it is NOT inherited from the layout: the
   left/split heroes want it at flex-start, and leaving it at the flexbox initial value
   (normal -> flex-start) got them that for free — which is exactly why the centered hero
   silently shipped a left-packed proof line. The variant rules above win on specificity with
   or without this declaration; it is here so the row STATES its packing, and a reader does
   not have to recall the flexbox initial value to know what a left hero does (issue 338). */
.hero__proof {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: center;
  justify-content: flex-start;
  margin-top: var(--space-md);
  font-size: 0.875rem;
  /* Route the proof color through a per-instance slot so a dark hero can lift it
     off the dark background; unset resolves to --color-muted, byte-identical to
     the prior hardcoded value (issue 296). */
  color: var(--hero-proof-color, var(--color-muted));
}

/* ==========================================================================
   COMPONENT: section
   Generic text + optional image section.
   ========================================================================== */

.section {
  padding-top: var(--section-padding-top, var(--space-xl));
  padding-bottom: var(--section-padding-bottom, var(--space-xl));
  background: var(--section-bg, transparent);
  border-top: var(--section-border-width, 0) solid var(--section-border-color, transparent);
  border-bottom: var(--section-border-width, 0) solid var(--section-border-color, transparent);
  border-radius: var(--section-radius, 0);
  box-shadow: var(--section-shadow, none);
}

/* .section__body is the OUTER wrapper of .section__content (section.php). The inner
   .section__content already routes max-width through --section-body-width, but this
   outer 40rem literal capped it, so the slot silently no-opped (issue 302). Route the
   outer cap through the same slot with 40rem as the fallback (unset stays 40rem, the
   narrower of the two, so output is unchanged). The remaining headings keep 40rem. */
.section__body {
  max-width: var(--section-body-width, 40rem);
}

.table-section__heading,
.faq__heading,
.logos__heading,
.embed__heading,
.cta__title,
.stats__heading {
  max-width: var(--cta-content-width, 40rem);
}

.section__header--center {
  text-align: center;
}

.section__eyebrow {
  display: inline-block;
  padding: 0.35rem 0.85rem;
  margin-bottom: var(--space-sm);
  border: var(--section-eyebrow-border-width, 0) solid var(--section-eyebrow-border-color, transparent);
  border-radius: var(--section-eyebrow-radius, 3px);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: var(--section-eyebrow-text-transform, uppercase);
  color: var(--section-eyebrow-color, var(--color-text));
  background: var(--section-eyebrow-bg, var(--color-surface-accent));
}

.section__title {
  font-size: var(--section-title-size, inherit);
  color: var(--section-title-color, var(--section-title-theme-color, var(--color-text)));
  /* Top-side header rhythm (title -> subheading gap). issue 336 made the subheading's
     BOTTOM margin authorable; this routes the title's own bottom margin through a
     slot so the whole header rhythm is slot-driven (issue 343). No header-scoping
     needed: the title is a heading, not the header's last child, so base.css's
     `p:last-child` reset never reached it. Today's literal stays the fallback. */
  margin-bottom: var(--section-title-margin-bottom, var(--space-md));
}

.section__title-accent {
  color: var(--section-title-accent-color, var(--color-accent));
}

.section__subheading {
  color: var(--section-subheading-color, var(--color-muted));
  margin-top: 0;
}
/* base.css `p:last-child { margin-bottom: 0 }` (0,1,1) outranks a bare
   `.section__subheading` (0,1,0), and the subheading is always the header's last
   child, so the component's declared bottom rhythm never reached the page (issue 336).
   Own the spacing at header scope (0,2,0): it wins the cascade without weakening
   the global prose reset, and does not depend on the subheading staying last. */
.section__header > .section__subheading {
  margin-bottom: var(--section-subheading-margin-bottom, var(--space-md));
}


.section__content {
  color: var(--section-text, var(--section-text-theme-color, var(--color-text)));
  max-width: var(--section-body-width, 42rem);
  /* Body list-marker colour (issue 339): read by the shared marker rules only
     when the operator opts into a non-disc body_marker; inert otherwise. */
  --pp-list-marker-color: var(--section-body-marker-color, var(--color-accent));
}

/* Restore list markers + indent for lists authored in section.body rich text.
   The global reset (base.css *{padding:0} + ul,ol{list-style:none}) strips both,
   so any <ul>/<ol> in section.body rendered as flush-left, unmarked lines,
   indistinguishable from stacked paragraphs. Re-declare markers, indent, and
   rhythm scoped to .section__content (where wp_kses_post($body) lands) — the
   .section__content ul selector (0,1,1) outweighs the base ul,ol reset (0,0,1).
   No style slot governs list-style/padding here, so no slot routing. issue 295 */
.section__content ul,
.section__content ol {
  padding-left: var(--space-lg);
  margin: 0 0 var(--space-md);
}

/* The disc here is intentionally beaten, on SOURCE ORDER at equal specificity
   (0,1,1), by the shared issue-339 marker rules (.section__content--marker-* > ul)
   further down this file. Do NOT raise this rule's specificity or move it below
   that block, or non-disc body markers stop painting (the tests/e2e/style-render
   issue-339 pins guard this). */
.section__content ul {
  list-style: disc;
}

.section__content ol {
  list-style: decimal;
}

.section__content li {
  margin-bottom: var(--space-xs);
}

.section__content li:last-child {
  margin-bottom: 0;
}

/* Image variants: side-by-side grid at md+ */
.section__grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
}

@media (min-width: 768px) {
  .section__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: var(--space-xl);
  }
}

.section__image {
  width: 100%;
  height: auto;
  aspect-ratio: var(--section-image-aspect-ratio, auto);
  object-fit: cover;
  object-position: var(--section-image-position, center);
  border-radius: var(--section-image-radius, var(--radius));
}

/* text-panel layout: text column + styleable content panel (issue 104).
   Reuses .section__grid (two columns at >=768px, stacked text-then-panel on
   mobile) but top-aligns the columns instead of centering like the image
   variants. The panel box and text route through --section-panel-* slots. The
   panel deliberately uses its own classes (not .section__content/.section__title)
   so the desktop premium-typography cascade cannot clobber the panel text on a
   dark panel — --section-panel-text stays the authority. */
@media (min-width: 768px) {
  .section--text-panel .section__grid {
    align-items: start;
  }
}

.section__panel {
  padding: var(--section-panel-padding, var(--space-lg));
  background: var(--section-panel-bg, var(--color-surface));
  color: var(--section-panel-text, var(--color-text));
  /* Panel font (issue 334): inherit by default (unchanged), var(--font-mono) for
     a monospace spec/stat panel. Descendants inherit, so heading/body/list/rows
     all pick it up unless a nested rule sets its own family. */
  font-family: var(--section-panel-font, inherit);
  border: var(--section-panel-border-width, 0) solid var(--section-panel-border-color, transparent);
  border-radius: var(--section-panel-radius, var(--radius));
}

.section__panel-heading {
  color: var(--section-panel-text, var(--color-text));
  margin-bottom: var(--space-md);
}

.section__panel-body {
  margin-top: 0;
  margin-bottom: var(--space-md);
}

/* Restore list markers + indent stripped by the base reset (base.css
   ul,ol{list-style:none} + *{padding:0}), same approach as .section__content
   (issue 295). No style slot governs list-style/padding here. issue 104.
   This disc is intentionally beaten on SOURCE ORDER at equal specificity (0,1,0)
   by the shared issue-339 .pp-marker-list rules below when panel_items_marker opts
   in — do not raise its specificity or move it below that block. */
.section__panel-list {
  list-style: disc;
  padding-left: var(--space-lg);
  margin: 0 0 var(--space-md);
  /* Panel list-marker colour (issue 339): inert until panel_items_marker opts in. */
  --pp-list-marker-color: var(--section-panel-marker-color, var(--color-accent));
}

.section__panel-list li {
  margin-bottom: var(--space-xs);
}

.section__panel-list li:last-child {
  margin-bottom: 0;
}

/* Paired rows (issue 334): a { label, value } entry renders as a two-part row
   inside the same panel list, label left / value right. A row is NOT a bullet,
   so it carries its own list-style:none and its glyph marker is suppressed below.
   Row text colour routes through the SAME --section-panel-text slot as the rest
   of the panel, which is item_eligible so a per-row style map can recolour one
   row (emphasise/de-emphasise) via cascade proximity on the inline row style. */
.section__panel-row {
  list-style: none;
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  color: var(--section-panel-text, var(--color-text));
}

.section__panel-row-value {
  text-align: right;
}

/* Suppress the shared issue-339 marker glyph on paired rows only. The marker
   layer paints via `.pp-marker-list--{marker} > li::before` (specificity 0,1,2);
   this two-class selector (0,2,1) outranks it so a mixed list keeps markers on
   its string bullets while rows stay markerless. content:none also neutralizes
   the base .pp-marker-list `li::before` reserved box. */
.section__panel-list > .section__panel-row::before {
  content: none;
}

/* Panel CTA spacing. Color/variant come from the shared .btn primitive and the
   panel_cta_variant prop (primary/secondary/outline/ghost); the panel does not
   introduce its own button-color slots, so the premium main .btn cascade stays
   the single source of primary-button color. issue 104 */
.section__panel-cta {
  margin-top: var(--space-sm);
}

/* Section background variants */
.pp-section--dark {
  background-color: var(--color-surface);
  color: var(--color-text);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

/* --color-muted carries .section__subheading; --section-*-theme-color carry the
   elements the desktop typography block re-colors at higher specificity (issue 222). */
.pp-section--inverted {
  background-color: var(--color-bg-inverted);
  color: var(--color-bg);
  --color-muted: var(--color-bg);
  --section-title-theme-color: var(--color-bg);
  --section-text-theme-color: var(--color-bg);
}

.pp-section--inverted .section__title,
.pp-section--inverted h2,
.pp-section--inverted h3 {
  color: var(--section-title-color, var(--color-bg));
}

.pp-section--inverted .section__content {
  color: var(--section-text, var(--color-bg));
}

.pp-section--inverted a {
  color: var(--section-accent, var(--color-accent));
}

/* Section with background image. The dark overlay below makes this a dark surface,
   so it needs the same theme text defaults as the inverted variant: the desktop
   typography block outranks every rule here and would otherwise fall back to the
   light-theme token, painting dark text on the overlay (issue 248). */
.section--has-bg-image {
  position: relative;
  background-size: cover;
  background-position: var(--section-bg-position, center);
  background-repeat: no-repeat;
  --section-title-theme-color: var(--color-bg);
  --section-text-theme-color: var(--color-bg);
}

.section__overlay {
  position: absolute;
  inset: 0;
  background: var(--section-overlay-bg, var(--overlay-bg));
}

.section--has-bg-image > .container {
  position: relative;
  z-index: 1;
}

.section--has-bg-image .section__title,
.section--has-bg-image h2,
.section--has-bg-image h3 {
  color: var(--section-title-color, var(--color-bg));
}

.section--has-bg-image .section__content,
.section--has-bg-image .section__body {
  color: var(--section-text, var(--color-bg));
}

.section--has-bg-image a {
  color: var(--section-accent, var(--color-accent));
}

/* ==========================================================================
   COMPONENT: faq
   Native details/summary accordion. No JS required.
   ========================================================================== */

.faq {
  padding-top: var(--faq-padding-top, var(--space-xl));
  padding-bottom: var(--faq-padding-bottom, var(--space-xl));
  background: var(--faq-bg, var(--color-surface));
}

.faq__eyebrow {
  display: inline-block;
  padding: 0.35rem 0.85rem;
  margin-bottom: var(--space-sm);
  border: var(--faq-eyebrow-border-width, 0) solid var(--faq-eyebrow-border-color, transparent);
  border-radius: var(--faq-eyebrow-radius, 3px);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: var(--faq-eyebrow-text-transform, uppercase);
  color: var(--faq-eyebrow-color, var(--color-text));
  background: var(--faq-eyebrow-bg, var(--color-surface-accent));
}

.faq__heading {
  /* Route the heading size through the slot (issue 304). This base rule governs
     widths below the desktop premium typography breakpoint, where the heading
     renders at the h2 base size (base.css); the desktop clamp is routed through
     the same slot below. Fallback keeps unset output byte-identical. */
  font-size: var(--faq-heading-size, 1.875rem);
  margin-bottom: var(--space-lg);
  color: var(--faq-heading-color, var(--faq-heading-theme-color, var(--color-text)));
}

.faq__heading-accent {
  color: var(--faq-heading-accent-color, var(--color-accent));
}

.faq__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.faq__item {
  border: 1px solid var(--faq-border-color, var(--color-border));
  border-radius: var(--radius);
  background: var(--faq-item-bg, var(--color-bg));
  overflow: hidden;
}

.faq__question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-md) var(--space-lg);
  font-weight: 600;
  color: var(--faq-question-color, var(--color-text));
  cursor: pointer;
  list-style: none;
  gap: var(--space-md);
  min-height: 44px;
  transition: color var(--transition);
}

/* Remove default marker in WebKit */
.faq__question::-webkit-details-marker {
  display: none;
}

/* Custom chevron via CSS */
.faq__question::after {
  content: '';
  display: block;
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--transition);
}

.faq__item[open] > .faq__question {
  color: var(--faq-accent, var(--color-accent));
}

.faq__item[open] > .faq__question::after {
  transform: rotate(225deg);
}

.faq__answer {
  padding: 0 var(--space-lg) var(--space-md);
  color: var(--faq-answer-color, var(--color-muted));
  line-height: 1.6;
}

@keyframes faq-open {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.faq__item[open] > .faq__answer {
  animation: faq-open 150ms ease;
}

.faq__empty {
  padding: var(--space-md) 0;
}

/* FAQ background variants. .faq already carries a --color-surface band by
   default, so --dark only adds the framing borders (mirrors .grid--dark /
   .pp-section--dark). --inverted swaps the band for the inverted surface and
   supplies the theme-color DEFAULT the desktop typography rule resolves at
   use-time — the desktop `main > .faq .faq__heading` rule outranks any theme
   selector, so the theme cannot win by specificity and instead feeds the
   heading's own fallback chain (slot > theme > token), the issue 222 pattern.
   .faq__item keeps its light background, so question/answer text stays dark —
   no theming needed there (mirrors .grid--inverted keeping cards light). */
.faq--dark {
  background: var(--faq-bg, var(--color-surface));
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.faq--inverted {
  background: var(--faq-bg, var(--color-bg-inverted));
  --faq-heading-theme-color: var(--color-bg);
}

.faq--inverted .faq__heading {
  color: var(--faq-heading-color, var(--color-bg));
}

/* ==========================================================================
   COMPONENT: grid
   Responsive card grid. 1-col → 2-col md → 3-col lg.
   Cards are for real content objects, not decoration.
   ========================================================================== */

.grid {
  padding-top: var(--grid-padding-top, var(--space-xl));
  padding-bottom: var(--grid-padding-bottom, var(--space-xl));
  background: var(--grid-bg, transparent);
}

.grid__header--center {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}

.grid__eyebrow {
  display: inline-block;
  padding: 0.35rem 0.85rem;
  margin-bottom: var(--space-sm);
  border: var(--grid-eyebrow-border-width, 0) solid var(--grid-eyebrow-border-color, transparent);
  border-radius: var(--grid-eyebrow-radius, 3px);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: var(--grid-eyebrow-text-transform, uppercase);
  color: var(--grid-eyebrow-color, var(--color-text));
  background: var(--grid-eyebrow-bg, var(--color-surface-accent));
}

.grid__heading {
  color: var(--grid-heading-color, var(--grid-heading-theme-color, var(--color-text)));
  font-size: var(--grid-heading-size, inherit);
  max-width: var(--grid-heading-max-width, 40rem);
  /* Top-side header rhythm (heading -> subheading gap), mirror of the issue 336
     subheading BOTTOM slot on the adjacent property (issue 343). The heading is
     not the header's last child, so base.css's `p:last-child` reset never won
     here — a plain slot on the heading suffices. Today's literal is the fallback. */
  margin-bottom: var(--grid-heading-margin-bottom, var(--space-lg));
}

.grid__heading-accent {
  color: var(--grid-heading-accent-color, var(--color-accent));
}

.grid__header--center .grid__heading {
  max-width: var(--grid-heading-max-width, 40rem);
  margin-left: auto;
  margin-right: auto;
}

.grid__subheading {
  color: var(--grid-subheading-color, var(--grid-subheading-theme-color, var(--color-muted)));
  margin-top: 0;
  max-width: 40rem;
}
/* base.css `p:last-child { margin-bottom: 0 }` (0,1,1) outranks a bare
   `.grid__subheading` (0,1,0), and the subheading is always the header's last
   child, so the component's declared bottom rhythm never reached the page (issue 336).
   Own the spacing at header scope (0,2,0): it wins the cascade without weakening
   the global prose reset, and does not depend on the subheading staying last. */
.grid__header > .grid__subheading {
  margin-bottom: var(--grid-subheading-margin-bottom, var(--space-lg));
}


.grid__header--center .grid__subheading {
  margin-left: auto;
  margin-right: auto;
}

.grid__list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--grid-gap, var(--space-lg));
  list-style: none;
}

@media (min-width: 768px) {
  .grid__list {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Steps variant: 3-col at desktop (always 3 items) */
@media (min-width: 1024px) {
  .grid--steps .grid__list {
    grid-template-columns: repeat(3, 1fr);
  }
}

.grid__item {
  display: flex;
  flex-direction: column;
  background: var(--grid-card-bg, var(--color-surface));
  border: var(--grid-card-border-width, 1px) solid var(--grid-card-border, var(--color-border));
  border-radius: var(--grid-card-radius, var(--radius));
  box-shadow: var(--grid-card-shadow, none);
  overflow: hidden;
  transition: transform var(--transition);
}

.grid__item:hover {
  transform: translateY(-2px);
}

.grid__item-image-wrap {
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

.grid__item-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.grid__item-body {
  padding: var(--grid-card-padding, var(--space-md));
  display: flex;
  flex-direction: column;
  gap: var(--grid-card-gap, var(--space-sm));
  flex: 1;
  /* Authorable TEXT alignment (issue 357). The body's text-bearing children
     (title, text, bullets) are full-width flex items (align-items:stretch), so
     text-align inherits into them and governs their INLINE content. The
     .grid__item-link is a content-width flex item placed by align-self, so per
     the issue 338 flex trap text-align cannot move its box; it follows the SAME
     alignment through the derived --pp-grid-link-align companion instead (issue
     361, on .grid__item-link below), so a centered card centers its link too.
     Default left is byte-identical to today's rendering (LTR theme, no rtl.css);
     an unset slot emits no inline custom property, so the fallback keeps every
     existing card left-aligned. Consumed via the shared align-typed slot. */
  text-align: var(--grid-item-text-align, left);
}

@media (min-width: 768px) {
  .grid__item-body {
    padding: var(--grid-card-padding, var(--space-lg));
  }
}

/* When a card has no image, the body is the only child — add top padding for balance */
.grid__item-body:first-child {
  padding-top: var(--space-lg);
}

.grid__item-title {
  font-size: var(--grid-item-title-size, 1.25rem);
  font-weight: 700;
  color: var(--grid-item-title-color, var(--color-text));
}

.grid__item-text {
  color: var(--grid-item-text-color, var(--color-muted));
  font-size: 0.9375rem;
  line-height: 1.6;
  flex: 1;
}

/* Issue 349 — an explicit per-instance --grid-item-text-color must win over a
   text_role color preset at ALL breakpoints. text_role adds .text-meta / .text-kicker
   (utilities.css), each a (0,1,0) `color` rule enqueued AFTER components.css, so it
   defeats the (0,1,0) base rule on the source-order tie below the 768px premium
   breakpoint — the slot was honored on desktop and dead on mobile. These (0,2,0)
   companion rules out-specify the utility regardless of enqueue order (mirroring how
   the desktop `main > .grid .grid__item-text` premium rule already does at (0,2,1)),
   so the slot always wins when set. The fallback is the exact role token, so an UNSET
   slot still renders the role colour — byte-identical to today at both breakpoints
   (desktop stays governed by the premium rule, which is strictly higher at (0,2,1)).
   Only meta/kicker set a colour; mono/label do not, so they need no companion rule. */
.grid__item-text.text-meta {
  color: var(--grid-item-text-color, var(--text-meta-color));
}

.grid__item-text.text-kicker {
  color: var(--grid-item-text-color, var(--text-kicker-color));
}

.grid__item-bullets {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  color: var(--grid-item-text-color, var(--color-muted));
  font-size: 0.9375rem;
  line-height: 1.5;
  flex: 1;
  /* Map grid's authorable bullet colour onto the shared marker plumbing var.
     Unset resolves to --color-accent, byte-identical to the historical grid
     treatment (issue 339). */
  --pp-list-marker-color: var(--grid-bullet-color, var(--color-accent));
}

/* ── Shared list-marker treatment (issue 339) ───────────────────────────────
   Promotes the check-mark bullet — historically locked inside grid cards — to a
   reusable marker any list-rendering surface can opt into. A marker is a GLYPH
   plus a COLOUR, both authorable; nothing here encodes a use-case. `disc` is the
   untouched default and adds NO class, so a list that does not opt in renders
   byte-identically. check / dash / arrow are just marker values.

   The base .pp-marker-list is INERT on its own (indent + empty positioned ::before,
   no glyph); a glyph comes only from the paired --check/--dash/--arrow modifier.
   section.php always emits the base and a modifier together, so a bare base never
   ships. A future consumer must pair them too.

   Colour flows in through --pp-list-marker-color (internal plumbing, NOT a schema
   style_slot): each consuming component maps its own authorable colour slot onto
   it inside that component's own block (grid: --grid-bullet-color above; section:
   --section-panel-marker-color / --section-body-marker-color).

   One definition, three consumers:
   - grid cards        .grid__item-bullet         (always a check, unchanged)
   - text-panel list   .pp-marker-list on the <ul> the renderer emits
   - section.body list .section__content--marker-* on the container we control,
                       scoped to its DIRECT-CHILD <ul> so nested/plugin lists keep
                       their default disc.
   These rules sit at grid's historical bullet position — after the COMPONENT:
   section block — so the section body/panel selectors below beat the issue-295
   disc rules on source order at equal specificity. --------------------------- */
.pp-marker-list,
.section__content--marker-check > ul,
.section__content--marker-dash > ul,
.section__content--marker-arrow > ul {
  list-style: none;
  padding-left: 0;
}

.grid__item-bullet,
.pp-marker-list > li,
.section__content--marker-check > ul > li,
.section__content--marker-dash > ul > li,
.section__content--marker-arrow > ul > li {
  position: relative;
  padding-left: 1.5rem;
}

.grid__item-bullet::before,
.pp-marker-list > li::before,
.section__content--marker-check > ul > li::before,
.section__content--marker-dash > ul > li::before,
.section__content--marker-arrow > ul > li::before {
  position: absolute;
  left: 0;
  font-weight: 700;
  color: var(--pp-list-marker-color, var(--color-accent));
}

/* Glyph per marker value. */
.grid__item-bullet::before,
.pp-marker-list--check > li::before,
.section__content--marker-check > ul > li::before {
  content: "\2713"; /* check mark */
}

.pp-marker-list--dash > li::before,
.section__content--marker-dash > ul > li::before {
  content: "\2013"; /* en dash */
}

.pp-marker-list--arrow > li::before,
.section__content--marker-arrow > ul > li::before {
  content: "\2192"; /* rightwards arrow */
}

.grid__item-link {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--grid-link-color, var(--color-accent));
  text-decoration: none;
  /* Follows the card's --grid-item-text-align via the derived --pp-grid-link-align
     companion (issue 361): grid.php maps left/start/justify->flex-start,
     center->center, right/end->flex-end. UNSET emits no companion, so the
     flex-start fallback is byte-identical to the historical left-pinned link. */
  align-self: var(--pp-grid-link-align, flex-start);
  margin-top: auto;
}

.grid__item-link:hover {
  color: var(--color-accent-hover);
  text-decoration: underline;
}

.grid__empty {
  grid-column: 1 / -1;
  padding: var(--space-md) 0;
}

/* Steps variant: same card chrome as the default grid, plus a numbered badge. */
.grid--steps .grid__item {
  position: relative;
  padding: var(--space-lg) var(--space-md) var(--space-md);
}

.grid--steps .pp-step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-bottom: var(--space-md);
  border-radius: 50%;
  background: var(--grid-step-color, var(--color-accent));
  color: var(--color-bg);
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1;
}

@media (max-width: 767px) {
  .grid--steps .pp-step-number {
    width: 2.5rem;
    height: 2.5rem;
    font-size: 1.125rem;
  }
}

/* Steps connector: a thin line between badges at desktop, replacing the old
   triangle arrow (already invisible in practice, and flagged as noisy — #56). */
@media (min-width: 1024px) {
  .grid--steps .grid__item:not(:last-child)::after {
    content: "";
    position: absolute;
    top: calc(var(--space-lg) + 1.375rem);
    left: 100%;
    width: var(--grid-gap, var(--space-lg));
    height: 1px;
    background: var(--grid-card-border, var(--color-border));
  }
}

/* Grid theme variants */
.grid--dark {
  background: var(--grid-bg, var(--color-surface));
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

/* Theme text defaults. The desktop typography block re-declares `color` at higher
   specificity than any theme variant can reach (`main > .grid .grid__heading` is
   [0,2,1]; this rule is [0,2,0]), so a theme cannot win by selector. It supplies an
   inheritable DEFAULT instead, which the element's own fallback chain resolves at
   use-time regardless of which selector won: slot > theme > global token (issue 222).
   Names are component-scoped on purpose: a shared token would inherit into a
   non-inverted component nested in an inverted one and paint light-on-light.
   Deliberately NOT applied to .grid__item-* — inverted cards keep a light
   background below, so their text must stay dark. */
.grid--inverted {
  background: var(--grid-bg, var(--color-bg-inverted));
  --grid-heading-theme-color: var(--color-bg);
  --grid-subheading-theme-color: var(--color-bg);
}

.grid--inverted .grid__heading {
  color: var(--grid-heading-color, var(--color-bg));
}

.grid--inverted .grid__item {
  background: var(--grid-card-bg, var(--color-bg));
}

/* Card top bar + featured first-card treatment (issue 293). These composed-page
   rules live here (not the final cascade, where they used to sit) so the issue 293
   slots are consumed inside the COMPONENT: grid block per the slot-contract guard.
   Cascade-equivalent: no other rule anywhere styles .grid__item::before, and the
   :first-child rule outranks the final cascade's [0,3,1] base rules by specificity
   in either order; the premium later winners (border-color, base box-shadow) still
   win exactly as before. Unset output is byte-identical — every fallback below is
   the literal that used to be hardcoded. */
main > .grid:not(.grid--steps) .grid__item::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  /* The top bar routes through the issue 293 bar slots on EVERY card; the
     featured :first-child rule below re-consumes them with louder defaults.
     The slots are shared (not featured-only) because the slot-contract guard
     collapses pseudo-classes onto the base subject, so a featured-only slot
     would flag these base literals as unwaivable bypasses. Setting the slots
     pins one bar on every card; --grid-card-bar-height: 0 removes it. */
  height: var(--grid-card-bar-height, 2px);
  background: var(--grid-card-bar-color, var(--color-border));
}

/* The whole featured first-card treatment below (this block, its ::before bar,
   the larger title, the extra body padding-top, and the dark-theme lift) carries
   a :not(.grid--uniform) guard. The grid `card_emphasis: uniform` prop emits
   .grid--uniform, which drops every featured rule so the first card falls through
   to the shared all-cards rules and renders identically to its siblings — a
   symmetric/peer card row (issue 226). The DEFAULT (featured) emits no class, so
   the guard never matches an existing page and output stays byte-identical. */
main > .grid:not(.grid--steps):not(.grid--uniform) .grid__item:first-child {
  /* Featured-card accent border DEFAULT tier; a per-instance --grid-card-border
     overrides it, mirroring --grid-card-bg below, so a declared style slot never
     silently no-ops on the first card (issue 226). Note: the premium later-cascade
     :first-child rule re-declares border-color with a --color-accent-strong
     fallback and currently wins when the slot is unset; this declaration is the
     base tier that takes over if that premium layer is ever removed. */
  border-color: var(--grid-card-border, var(--color-border-accent));
  /* Featured-card accent fill is the DEFAULT; a per-instance --grid-card-bg
     overrides it so the first card stays uniform with the rest when an author
     sets an explicit card color (e.g. uniform dark cards on a dark band). The
     texture-stripe line color routes through the issue 293 slot; transparent
     removes the stripe. */
  background:
    linear-gradient(90deg, var(--grid-featured-texture-color, rgba(37, 99, 235, 0.055)) 1px, transparent 1px) 0 0 / 2.75rem 100%,
    var(--grid-card-bg, linear-gradient(180deg, var(--color-surface-accent) 0%, var(--color-surface) 100%));
  /* Featured glow: the issue 293 featured-shadow slot wins first, then the shared
     --grid-card-shadow exactly as before, then the glow literal. The mobile
     featured rule in the final cascade re-declares this same chain. */
  box-shadow: var(--grid-featured-shadow, var(--grid-card-shadow,
    inset 0 0 0 1px rgba(37, 99, 235, 0.055),
    0 18px 42px rgba(37, 99, 235, 0.10)));
}

main > .grid:not(.grid--steps):not(.grid--uniform) .grid__item:first-child::before {
  height: var(--grid-card-bar-height, 4px);
  background: var(--grid-card-bar-color, linear-gradient(90deg, var(--color-accent), color-mix(in srgb, var(--color-accent) 18%, transparent)));
}

/* ==========================================================================
   COMPONENT: table
   Data table with horizontal scroll on mobile.
   ========================================================================== */

.table-section {
  padding-top: var(--space-xl);
  padding-bottom: var(--space-xl);
}

.table-section__heading {
  margin-bottom: var(--space-lg);
}

.table-wrap {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

.table {
  width: max-content;
  min-width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
  background-color: var(--color-bg);
}

.table__caption {
  caption-side: bottom;
  padding: var(--space-sm) var(--space-md);
  font-size: 0.875rem;
  color: var(--color-muted);
  text-align: left;
}

.table__head {
  background-color: var(--color-surface);
}

.table__header {
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  font-weight: 700;
  color: var(--color-text);
  border-bottom: 2px solid var(--color-border);
  white-space: nowrap;
}

.table__row {
  border-bottom: 1px solid var(--color-border);
  transition: background-color var(--transition);
}

.table__row:last-child {
  border-bottom: none;
}

.table__row:hover {
  background-color: var(--color-surface);
}

.table__cell {
  padding: var(--space-sm) var(--space-md);
  color: var(--color-text);
  vertical-align: top;
  white-space: normal;
  overflow-wrap: anywhere;
}

.table-section__empty {
  padding: var(--space-md) 0;
}

/* ==========================================================================
   COMPONENT: cta
   Call-to-action block. Variants: full-width (centered), inline (flex row).
   ========================================================================== */

.cta {
  padding-top: var(--cta-padding-top, var(--space-xl));
  padding-bottom: var(--cta-padding-bottom, var(--space-xl));
  background: var(--cta-bg, transparent);
  border-top: var(--cta-border-width, 0) solid var(--cta-border-color, transparent);
  border-bottom: var(--cta-border-width, 0) solid var(--cta-border-color, transparent);
  border-radius: var(--cta-radius, 0);
  box-shadow: var(--cta-shadow, none);
}

.cta__inner {
  display: flex;
  flex-direction: column;
  gap: var(--cta-inner-gap, var(--space-lg));
}

.cta__eyebrow {
  display: inline-block;
  padding: 0.35rem 0.85rem;
  margin-bottom: var(--space-sm);
  border: var(--cta-eyebrow-border-width, 0) solid var(--cta-eyebrow-border-color, transparent);
  border-radius: var(--cta-eyebrow-radius, 3px);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: var(--cta-eyebrow-text-transform, uppercase);
  color: var(--cta-eyebrow-color, var(--color-text));
  background: var(--cta-eyebrow-bg, var(--color-surface-accent));
}

.cta__title {
  font-size: var(--cta-title-size, inherit);
  color: var(--cta-text, inherit);
  max-width: var(--cta-content-width, 40rem);
  margin-bottom: var(--space-xs);
}

.cta__title-accent {
  color: var(--cta-title-accent-color, var(--color-accent));
}

.cta__body {
  color: var(--cta-body-color, var(--cta-body-theme-color, var(--color-muted)));
  font-size: var(--cta-body-size, inherit);
  margin-bottom: 0;
}

/* CTA accent: scoped button colors via style slots. Excludes outline/ghost/
   secondary — .cta .btn (2 classes) has HIGHER specificity than .btn--outline/
   --ghost/--secondary (1 class each), so without this exclusion the accent
   fill unconditionally wins over those variants' own transparent/surface
   fill regardless of source order, producing invisible or wrong-colored
   buttons (issue 111 — confirmed empirically: outline and ghost render with
   button text the same color as the button background). Same root cause and
   fix pattern already applied to hero's equivalent rule above. */
.cta .btn:not(.btn--outline):not(.btn--ghost):not(.btn--secondary) {
  background-color: var(--cta-accent, var(--color-accent));
  border-color: var(--cta-accent, var(--color-accent));
}
.cta .btn:not(.btn--outline):not(.btn--ghost):not(.btn--secondary):hover {
  background-color: var(--cta-accent-hover, var(--color-accent-hover));
  border-color: var(--cta-accent-hover, var(--color-accent-hover));
}

/* CTA button (single button): per-instance bg/border/color override,
   independent of --cta-accent (issue 111). One rule per variant, each falling
   back to that variant's own existing default, so an unset override renders
   byte-identically to today. */
.cta__button:not(.btn--outline):not(.btn--ghost):not(.btn--secondary) {
  background-color: var(--cta-button-bg, var(--cta-accent, var(--color-accent)));
  border-color: var(--cta-button-border, var(--cta-accent, var(--color-accent)));
  color: var(--cta-button-color, var(--btn-text, var(--color-bg)));
}
.cta__button:not(.btn--outline):not(.btn--ghost):not(.btn--secondary):hover {
  background-color: var(--cta-button-hover-bg, var(--cta-accent-hover, var(--color-accent-hover)));
  border-color: var(--cta-button-hover-border, var(--cta-accent-hover, var(--color-accent-hover)));
  color: var(--cta-button-hover-color, var(--btn-text, var(--color-bg)));
}

.cta__button.btn--outline {
  background-color: var(--cta-button-bg, transparent);
  color: var(--cta-button-color, var(--color-accent));
  border-color: var(--cta-button-border, var(--color-accent));
}
.cta__button.btn--outline:hover {
  background-color: var(--cta-button-hover-bg, var(--color-accent));
  border-color: var(--cta-button-hover-border, var(--color-accent));
  color: var(--cta-button-hover-color, var(--color-bg));
}

.cta__button.btn--secondary {
  background-color: var(--cta-button-bg, var(--color-surface));
  color: var(--cta-button-color, var(--color-text));
  border-color: var(--cta-button-border, var(--color-border));
}
.cta__button.btn--secondary:hover {
  background-color: var(--cta-button-hover-bg, var(--color-border));
  border-color: var(--cta-button-hover-border, var(--color-border));
  color: var(--cta-button-hover-color, var(--color-text));
}

.cta__button.btn--ghost {
  background-color: var(--cta-button-bg, transparent);
  color: var(--cta-button-color, var(--color-accent));
  border-color: var(--cta-button-border, transparent);
}
.cta__button.btn--ghost:hover {
  background-color: var(--cta-button-hover-bg, var(--color-surface));
  border-color: var(--cta-button-hover-border, transparent);
  color: var(--cta-button-hover-color, var(--color-accent));
}

/* Full-width: centered block with surface background */
.cta--full-width {
  background: var(--cta-bg, var(--color-surface));
  border-top: var(--cta-border-width, 1px) solid var(--cta-border-color, var(--color-border));
  border-bottom: var(--cta-border-width, 1px) solid var(--cta-border-color, var(--color-border));
}

.cta--full-width .cta__inner {
  align-items: center;
  text-align: center;
}

/* Inline: flex row, text left, button right */
.cta--inline .cta__inner {
  align-items: flex-start;
  text-align: left;
}

@media (min-width: 768px) {
  .cta--inline .cta__inner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.cta__button {
  flex-shrink: 0;
}

/* CTA theme variants */
.cta--dark {
  background: var(--cta-bg, var(--color-surface));
  border-top: var(--cta-border-width, 1px) solid var(--cta-border-color, var(--color-border));
  border-bottom: var(--cta-border-width, 1px) solid var(--cta-border-color, var(--color-border));
}

.cta--inverted {
  background: var(--cta-bg, var(--color-bg-inverted));
  --cta-body-theme-color: var(--color-bg);
}

.cta--inverted .cta__title {
  color: var(--cta-text, var(--color-bg));
}

.cta--inverted .cta__body {
  color: var(--cta-body-color, var(--color-bg));
  opacity: 0.85;
}

/* CTA with background image */
/* Same dark-overlay surface as .section--has-bg-image — same theme text default
   (issue 248). */
.cta--has-bg-image {
  position: relative;
  background-size: cover;
  background-position: var(--cta-bg-position, center);
  background-repeat: no-repeat;
  border: none;
  --cta-body-theme-color: var(--color-bg);
}

.cta__overlay {
  position: absolute;
  inset: 0;
  background: var(--cta-overlay-bg, var(--overlay-bg));
}

.cta--has-bg-image .container {
  position: relative;
  z-index: 1;
}

.cta--has-bg-image .cta__title {
  color: var(--cta-text, var(--color-bg));
}

.cta--has-bg-image .cta__body {
  color: var(--cta-body-color, var(--color-bg));
  opacity: 0.85;
}

/* Closing CTA correction: wide premium close, not a narrow centered poem. */
/* Page-specific desktop CTA padding for the benchmark closers. These IDs
   [1,0,0] outrank the generic .cta slot rule [0,1,0], so a bare literal here
   defeats --cta-padding-* on exactly the pages that ship a CTA (issue 302).
   Route through the slot with 5.75rem as the fallback (unset unchanged). */
#home-cta,
#how-cta,
#agencies-cta,
#implementers-cta {
  padding-top: var(--cta-padding-top, 5.75rem);
  padding-bottom: var(--cta-padding-bottom, 5.75rem);
}

#home-cta .container,
#how-cta .container,
#agencies-cta .container,
#implementers-cta .container {
  max-width: 70rem;
}

#home-cta.cta--inline .cta__inner,
#how-cta.cta--inline .cta__inner,
#agencies-cta.cta--inline .cta__inner,
#implementers-cta.cta--inline .cta__inner {
  width: 100%;
  max-width: 68rem;
  margin-right: auto;
  margin-left: auto;
  gap: var(--cta-inner-gap, 1.75rem);
  align-items: flex-start;
  text-align: left;
}

#home-cta .cta__text,
#how-cta .cta__text,
#agencies-cta .cta__text,
#implementers-cta .cta__text {
  max-width: 38rem;
}

#home-cta .cta__title,
#how-cta .cta__title,
#agencies-cta .cta__title,
#implementers-cta .cta__title {
  max-width: var(--cta-content-width, 14em);
  margin-right: 0;
  margin-left: 0;
}

#home-cta .cta__body,
#how-cta .cta__body,
#agencies-cta .cta__body,
#implementers-cta .cta__body {
  max-width: 36rem;
  margin-right: 0;
  margin-left: 0;
}

#home-cta .cta__button,
#how-cta .cta__button,
#agencies-cta .cta__button,
#implementers-cta .cta__button {
  width: fit-content;
  min-width: 15.5rem;
  align-self: flex-start;
}

@media (min-width: 768px) {
  #home-cta.cta--inline .cta__inner,
  #how-cta.cta--inline .cta__inner,
  #agencies-cta.cta--inline .cta__inner,
  #implementers-cta.cta--inline .cta__inner {
    display: grid;
    /* Both tracks must be able to SHRINK. This grid turns on at 768px, but the content
       box it gets there is far narrower than the viewport (the container eats
       2 * var(--space-lg) and .cta__inner eats 2 * clamp(2rem, 4vw, 2.6rem) padding plus
       its border), leaving roughly 40rem. Fixed floors (32.5rem + 14rem + a >=3rem gap)
       could not fit, so #how-cta / #agencies-cta / #implementers-cta scrolled the page
       sideways from 768px through ~912px (issue 265 — the sibling of home-cta's 258 fix).
       Column 1 floors at 0 and is fractional so it absorbs the slack; column 2 floors at
       the button's own min-width (14.75rem, re-declared by the four-CTA button rule below)
       so the button can never overflow its track. #home-cta re-sets these same tracks in
       its own override further down and is unaffected. The 0 floor only works because
       base.css's overflow-wrap:break-word lets a long title word wrap mid-word rather than
       hold the track open. */
    grid-template-columns: minmax(0, 2fr) minmax(14.75rem, 1fr);
    column-gap: clamp(3rem, 7vw, 6rem);
    align-items: center;
  }

  #home-cta .cta__button,
  #how-cta .cta__button,
  #agencies-cta .cta__button,
  #implementers-cta .cta__button {
    justify-self: start;
    align-self: center;
  }
}

@media (max-width: 767px) {
  /* Route through --cta-padding-* too (issue 302). This earlier mobile CTA rule
     is currently shadowed by the later mobile #*-cta rule, but keeping a bare
     literal on a slot-controlled property here would re-introduce the dead slot
     if the later rule ever moves. Fallback stays 4.25rem, so nothing changes. */
  #home-cta,
  #how-cta,
  #agencies-cta,
  #implementers-cta {
    padding-top: var(--cta-padding-top, 4.25rem);
    padding-bottom: var(--cta-padding-bottom, 4.25rem);
  }

  #home-cta.cta--inline .cta__inner,
  #how-cta.cta--inline .cta__inner,
  #agencies-cta.cta--inline .cta__inner,
  #implementers-cta.cta--inline .cta__inner {
    max-width: 100%;
    gap: var(--cta-inner-gap, 1.35rem);
  }

  #home-cta .cta__title,
  #how-cta .cta__title,
  #agencies-cta .cta__title,
  #implementers-cta .cta__title {
    max-width: var(--cta-content-width, 12.5em);
  }

  #home-cta .cta__button,
  #how-cta .cta__button,
  #agencies-cta .cta__button,
  #implementers-cta .cta__button {
    width: 100%;
    max-width: 22rem;
  }
}

/* ==========================================================================
   COMPONENT: footer
   Site footer with nav and copyright.
   ========================================================================== */

/* Dark-marketing-footer chrome (issue 300). The footer is template-owned
   (issue 223) with no composition style_slots, so its background/text/link
   colors come from whitelisted site options (pp_footer_bg / pp_footer_text /
   pp_footer_link_color) that base.php emits as inline --footer-* custom
   properties. Each property routes through var(--footer-*, <existing-literal>)
   so an unset footer renders byte-identical to before. These are NOT schema
   style_slots (footer declares none), so they are outside the issue 305 slot
   guard; FooterChromeTest pins the consume-plus-fallback contract instead. */
/* `background`, NOT `background-color` (issue 333): --footer-bg is gradient-typed
   (color OR gradient), and a gradient is a CSS <image> — background-color would drop
   it as invalid and the footer would paint nothing. The shorthand takes both. Its
   longhand reset is a no-op: this rule is the only place .site-footer sets background. */
.site-footer {
  background: var(--footer-bg, var(--color-surface));
  color: var(--footer-text, inherit);
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-lg);
  padding-bottom: var(--space-lg);
}

.site-footer__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

@media (min-width: 768px) {
  .site-footer__inner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

/* Brand column (logo + optional blurb) and the optional contact block. Minimal,
   single-axis styling — the footer stays a flat marketing footer, not a builder
   (issue 300 scope). */
.site-footer__brand {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.site-footer__blurb {
  font-size: 0.9rem;
  max-width: 32ch;
  margin-bottom: 0;
}

.site-footer__contact {
  font-size: 0.9rem;
  line-height: 1.6;
}

/* Cap the footer logo the same way the nav caps it (.nav__logo-image). The
   footer is template-owned with zero style slots, so a real-world wordmark
   (e.g. 664x150) would otherwise render near intrinsic size and dominate the
   footer. Literal cap, consistent with the nav treatment. issue 299 */
.site-footer__logo-image {
  display: block;
  max-height: 2.5rem;
  width: auto;
  object-fit: contain;
}

.site-footer__nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  list-style: none;
}

.site-footer__nav ul li a {
  color: var(--footer-link-color, var(--color-muted));
  text-decoration: none;
  font-size: 0.9rem;
  transition: color var(--transition);
}

.site-footer__nav ul li a:hover {
  color: var(--color-accent);
  text-decoration: underline;
}

/* Copyright routes color through --footer-text (fallback --color-muted, so an
   unset footer is unchanged). The old markup carried a .text-muted utility that
   hardcoded --color-muted and would win over --footer-text on a dark footer;
   footer.php dropped that class so the copyright follows the footer text color.
   issue 300 */
.site-footer__copyright {
  font-size: 0.875rem;
  color: var(--footer-text, var(--color-muted));
  margin-bottom: 0;
}

/* Footer structure (issue 335). Optional column headings + a delimited bottom
   bar. Emitted only when the matching pp_footer_* option is set, so an unset
   footer renders exactly as issue 300 left it. Styling is deliberately NEUTRAL
   (no color/size opinion baked in): the heading inherits the footer text color
   through --footer-text (so it follows whatever the dark/light footer sets),
   and the bottom bar's divider reuses --color-border — the SAME token the
   footer's own top border uses. The capability is the structure and the slots
   that drive it, not a specific look. */
.site-footer__heading {
  font-size: 0.9rem;
  font-weight: 600;
  margin-bottom: var(--space-sm);
  color: var(--footer-text, inherit);
}

/* Bottom bar: a delimited band below the main footer flow. Full-width divider
   (via --color-border, matching .site-footer's own border-top), with the
   copyright and the optional secondary note on opposite ends on wider screens. */
.site-footer__bottom {
  border-top: 1px solid var(--color-border);
  margin-top: var(--space-md);
  padding-top: var(--space-md);
}

.site-footer__bottom-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

@media (min-width: 768px) {
  .site-footer__bottom-inner {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.site-footer__note {
  font-size: 0.875rem;
  color: var(--footer-text, var(--color-muted));
  margin-bottom: 0;
}

/* ==========================================================================
   COMPONENT: stats
   Horizontal row of large-number metrics with labels.
   ========================================================================== */

.stats {
  padding-top: var(--stats-padding-top, var(--space-xl));
  padding-bottom: var(--stats-padding-bottom, var(--space-xl));
  background: var(--stats-bg, transparent);
}

.stats__heading {
  /* Route the heading size through the slot (issue 304). stats has no premium
     typography rule, so the fallback is the h2 base size (base.css) — unset
     output is byte-identical, and an operator can now tune the scale. */
  font-size: var(--stats-title-size, 1.875rem);
  margin-bottom: var(--space-lg);
  /* Center the heading BOX, not just its text (issue 367 — same class as issue 354).
     The shared cap rule gives this h2 max-width: var(--cta-content-width, 40rem);
     a block h2 fills to that cap inside the wider .container, so without auto
     inline margins the 40rem box pins to the container's left edge (measured
     x 96-736 at 1280px) and text-align: center only centers WITHIN that
     left-pinned box — the heading sits left of page center for any title.
     Auto side-margins center the box under the already-centered stats list
     (.stats__list is justify-content: center, spanning the full container).
     Both sides auto so it centers regardless of writing direction; collapses to
     0 when the heading is as wide as the container (narrow viewports), so no
     band is introduced. Physical margins mirror the landed issue-354 fix
     (.section--centered .section__content) and the file's centering convention. */
  margin-left: auto;
  margin-right: auto;
  text-align: center;
  color: var(--stats-title-color, var(--color-text));
}

.stats__heading-accent {
  color: var(--stats-title-accent-color, var(--color-accent));
}

.stats__list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-lg) var(--space-xl);
  list-style: none;
  padding: 0;
  margin: 0;
}

.stats__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  min-width: 8rem;
}

.stats__number {
  font-size: var(--stats-number-size, 2.5rem);
  font-weight: 700;
  color: var(--stats-number-color, var(--color-accent));
  line-height: 1;
}

.stats__label {
  font-size: 0.875rem;
  color: var(--stats-label-color, var(--color-muted));
  text-align: center;
}

/* Stats variants */
.stats--dark {
  background: var(--stats-bg, var(--color-surface));
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.stats--inverted {
  background: var(--stats-bg, var(--color-bg-inverted));
}

.stats--inverted .stats__heading {
  color: var(--stats-title-color, var(--color-bg));
}

.stats--inverted .stats__number {
  color: var(--stats-number-color, var(--color-accent));
}

.stats--inverted .stats__label {
  color: var(--stats-label-color, var(--color-bg));
  opacity: 0.75;
}

/* Stats with background image */
.stats--has-bg-image {
  position: relative;
  background-size: cover;
  background-position: var(--stats-bg-position, center);
  background-repeat: no-repeat;
}

.stats__overlay {
  position: absolute;
  inset: 0;
  background: var(--overlay-bg);
}

.stats--has-bg-image > .container {
  position: relative;
  z-index: 1;
}

.stats--has-bg-image .stats__heading {
  color: var(--stats-title-color, var(--color-bg));
}

.stats--has-bg-image .stats__number {
  color: var(--stats-number-color, var(--color-accent));
}

.stats--has-bg-image .stats__label {
  color: var(--stats-label-color, var(--color-bg));
  opacity: 0.85;
}

/* ==========================================================================
   COMPONENT: logos
   Flex-wrap image grid. Works for logo strips (no labels) and icon-category
   tiles (with labels). Items are always image-based.
   ========================================================================== */

.logos {
  padding-top: var(--space-xl);
  padding-bottom: var(--space-xl);
}

.logos__heading {
  margin-bottom: var(--space-lg);
}

.logos__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg);
  list-style: none;
  padding: 0;
  margin: 0;
}

.logos__item {
  display: flex;
  align-items: center;
  justify-content: center;
}

.logos__item--labeled {
  flex-direction: column;
  gap: var(--space-sm);
  min-width: 6rem;
}

.logos__image {
  max-height: 3rem;
  width: auto;
  object-fit: contain;
}

.logos__item--labeled .logos__image {
  max-height: 2.5rem;
}

.logos__label {
  font-size: 0.8125rem;
  color: var(--color-muted);
  text-align: center;
}

/* Logos variants */
.logos--dark {
  background-color: var(--color-surface);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.logos--inverted {
  background-color: var(--color-bg-inverted);
}

.logos--inverted .logos__heading {
  color: var(--color-bg);
}

.logos--inverted .logos__label {
  color: var(--color-bg);
  opacity: 0.75;
}

/* ==========================================================================
   COMPONENT: embed
   Generic WP shortcode / plugin content wrapper.
   ========================================================================== */

.embed {
  padding-top: var(--space-xl);
  padding-bottom: var(--space-xl);
}

.embed__heading {
  margin-bottom: var(--space-lg);
}

.embed__content {
  max-width: 40rem;
}

/* Embed variants */
.embed--dark {
  background-color: var(--color-surface);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.embed--inverted {
  background-color: var(--color-bg-inverted);
}

.embed--inverted .embed__heading {
  color: var(--color-bg);
}

.embed--inverted .embed__content {
  color: var(--color-bg);
}

.embed--inverted a {
  color: var(--color-accent);
}

/* ==========================================================================
   COMPONENT: testimonials
   Customer quotes with attribution. 'grid' variant: 1-col → 2-col md →
   3-col lg card grid. 'stack' variant: single centered column, no card
   chrome, for one or two large pull-quotes.
   ========================================================================== */

.testimonials {
  padding-top: var(--testimonials-padding-top, var(--space-xl));
  padding-bottom: var(--testimonials-padding-bottom, var(--space-xl));
  background: var(--testimonials-bg, transparent);
}

.testimonials__header--center {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}

.testimonials__eyebrow {
  display: inline-block;
  padding: 0.35rem 0.85rem;
  margin-bottom: var(--space-sm);
  border: var(--testimonials-eyebrow-border-width, 0) solid var(--testimonials-eyebrow-border-color, transparent);
  border-radius: var(--testimonials-eyebrow-radius, 3px);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: var(--testimonials-eyebrow-text-transform, uppercase);
  color: var(--testimonials-eyebrow-color, var(--color-text));
  background: var(--testimonials-eyebrow-bg, var(--color-surface-accent));
}

.testimonials__heading {
  color: var(--testimonials-heading-color, var(--color-text));
  max-width: 40rem;
  /* Top-side header rhythm (heading -> subheading gap), mirror of the issue 336
     subheading BOTTOM slot on the adjacent property (issue 343). The heading is
     not the header's last child, so base.css's `p:last-child` reset never won
     here — a plain slot on the heading suffices. Today's literal is the fallback. */
  margin-bottom: var(--testimonials-heading-margin-bottom, var(--space-lg));
}

.testimonials__heading-accent {
  color: var(--testimonials-heading-accent-color, var(--color-accent));
}

.testimonials__header--center .testimonials__heading {
  margin-left: auto;
  margin-right: auto;
}

.testimonials__subheading {
  color: var(--testimonials-subheading-color, var(--color-muted));
  margin-top: 0;
  max-width: 40rem;
}
/* base.css `p:last-child { margin-bottom: 0 }` (0,1,1) outranks a bare
   `.testimonials__subheading` (0,1,0), and the subheading is always the header's last
   child, so the component's declared bottom rhythm never reached the page (issue 336).
   Own the spacing at header scope (0,2,0): it wins the cascade without weakening
   the global prose reset, and does not depend on the subheading staying last. */
.testimonials__header > .testimonials__subheading {
  margin-bottom: var(--testimonials-subheading-margin-bottom, var(--space-lg));
}


.testimonials__header--center .testimonials__subheading {
  margin-left: auto;
  margin-right: auto;
}

.testimonials__list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--testimonials-gap, var(--space-lg));
}

@media (min-width: 768px) {
  .testimonials__list {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .testimonials__list {
    grid-template-columns: repeat(3, 1fr);
  }
}

.testimonials__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin: 0;
  padding: var(--testimonials-card-padding, var(--space-lg));
  background: var(--testimonials-card-bg, var(--color-surface));
  border: var(--testimonials-card-border-width, 1px) solid var(--testimonials-card-border, var(--color-border));
  border-radius: var(--testimonials-card-radius, var(--radius));
  box-shadow: var(--testimonials-card-shadow, none);
}

.testimonials__quote {
  margin: 0;
  padding: 0;
  border-left: none;
  font-style: normal;
  color: var(--testimonials-quote-color, var(--color-text));
  flex: 1;
}

.testimonials__quote p {
  margin: 0;
  position: relative;
  padding-left: 1.75rem;
}

.testimonials__quote p::before {
  content: "\201C";
  position: absolute;
  left: 0;
  top: -0.1em;
  font-size: 1.75em;
  line-height: 1;
  font-weight: 700;
  color: var(--testimonials-quote-mark-color, var(--color-accent));
}

.testimonials__attribution {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.testimonials__avatar {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.testimonials__attribution-text {
  display: flex;
  flex-direction: column;
}

.testimonials__author {
  font-weight: 700;
  color: var(--testimonials-author-color, var(--color-text));
}

.testimonials__meta {
  font-size: 0.875rem;
  color: var(--testimonials-meta-color, var(--color-muted));
}

.testimonials__empty {
  grid-column: 1 / -1;
  padding: var(--space-md) 0;
}

/* Stack variant: single centered column, no card chrome, larger quotes */
.testimonials--stack .testimonials__list {
  grid-template-columns: 1fr;
  max-width: 42rem;
  margin: 0 auto;
}

.testimonials--stack .testimonials__item {
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  text-align: center;
  align-items: center;
}

.testimonials--stack .testimonials__quote {
  font-size: 1.375rem;
}

.testimonials--stack .testimonials__quote p {
  padding-left: 0;
}

.testimonials--stack .testimonials__quote p::before {
  display: block;
  position: static;
  margin-bottom: var(--space-sm);
}

/* Theme variants */
.testimonials--dark {
  background: var(--testimonials-bg, var(--color-surface));
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.testimonials--inverted {
  background: var(--testimonials-bg, var(--color-bg-inverted));
}

.testimonials--inverted .testimonials__heading {
  color: var(--testimonials-heading-color, var(--color-bg));
}

.testimonials--inverted .testimonials__item {
  background: var(--testimonials-card-bg, var(--color-bg));
}

.testimonials--inverted.testimonials--stack .testimonials__item {
  background: transparent;
}

.testimonials--inverted.testimonials--stack .testimonials__quote {
  color: var(--testimonials-quote-color, var(--color-bg));
}

.testimonials--inverted.testimonials--stack .testimonials__author {
  color: var(--testimonials-author-color, var(--color-bg));
}

/* ==========================================================================
   SHARED: Adjacent-Sibling Rhythm
   Tightens vertical padding between consecutive section-level components
   inside <main>. First component keeps its own padding naturally (the `+`
   combinator skips it). Desktop only — mobile spacing stays uniform.
   ========================================================================== */

@media (min-width: 768px) {
  main > [data-pp-component] + [data-pp-component] {
    padding-top: var(--space-lg);
  }
}

/* ==========================================================================
   SHARED: Text-Only Layout
   Text-only: body fills the container (no max-width) so titles and headings
   match the visual width of adjacent full-width components (grid, table, CTA).
   Only the prose content block is constrained for readable line length.
   Centered: body is constrained to --measure-centered with center-aligned text.
   ========================================================================== */

.section--text-only .section__title {
  font-size: var(--section-title-size, 2.25rem);
}

.section--text-only .section__content {
  max-width: var(--section-body-width, var(--measure-centered));
}

/* ==========================================================================
   SHARED: Spacing + Width overrides (data attributes)
   These selectors are generic ([data-pp-component][data-pp-spacing=...]) but
   only hero.php emits the data-pp-spacing and data-pp-width attributes.
   ========================================================================== */

[data-pp-component][data-pp-spacing="compact"] {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-lg);
}

[data-pp-component][data-pp-spacing="spacious"] {
  padding-top: var(--space-2xl);
  padding-bottom: var(--space-2xl);
}

@media (min-width: 768px) {
  main > [data-pp-component][data-pp-spacing="compact"] {
    padding-top: var(--space-lg);
    padding-bottom: var(--space-lg);
  }
  main > [data-pp-component][data-pp-spacing="spacious"] {
    padding-top: var(--space-3xl);
    padding-bottom: var(--space-3xl);
  }
}

[data-pp-width="narrow"] .container {
  max-width: 56rem;
}

[data-pp-width="full"] .container {
  max-width: 100%;
}

/* ==========================================================================
   SHARED: Section Centered Variant
   Centered text layout with constrained width, no image.
   ========================================================================== */

.section--centered .section__body {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
  max-width: var(--section-body-width, var(--measure-centered));
}

.section--centered .section__title {
  text-align: center;
}

/* The centered layout centers the OUTER .section__body (max-width --measure-centered,
   56rem) but its inner .section__content carries a NARROWER cap (--section-body-width,
   42rem) with no auto margins, so the body copy left-pinned inside the wider centered
   wrapper while the title sat centered — a visible ~112px asymmetry (issue 354). Give
   the inner wrapper auto side-margins so the measure centers under the heading. Scoped
   to .section--centered only: text-only / image-left / image-right / text-panel keep
   .section__content left-pinned (byte-identical). Text is already centered here via the
   inherited text-align:center on .section--centered .section__body. */
.section--centered .section__content {
  margin-left: auto;
  margin-right: auto;
}

/* ==========================================================================
   SHARED: Pagination
   Renders under the post grid on the blog index / archives (issue 126).
   ========================================================================== */

.pp-pagination {
  padding: var(--space-lg) 0;
}

.pp-pagination__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  list-style: none;
}

.pp-pagination__list a.page-numbers,
.pp-pagination__list span.page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.5rem;
  height: 2.5rem;
  padding: 0 var(--space-sm);
  border-radius: var(--radius);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--color-text);
  text-decoration: none;
}

.pp-pagination__list a.page-numbers:hover {
  background: var(--color-surface);
}

.pp-pagination__list span.page-numbers.current {
  background: var(--color-accent);
  color: var(--color-bg);
}

.pp-pagination__list a.page-numbers.dots {
  color: var(--color-muted);
}

/* Homepage closing CTA headline fix: title-left, action copy-right on desktop. */
@media (min-width: 768px) {
  /* Both tracks must be able to SHRINK, because this grid turns on at 768px but the
     content box it gets there is far narrower than the viewport: the container eats
     2 * var(--space-lg) (its padding switches to --space-lg at this same 768px
     breakpoint, utilities.css), and .cta__inner itself eats 2 * clamp(2rem, 4vw, 2.6rem)
     of padding plus its 1px border (see the .cta__inner rule further down). At 768px that
     leaves roughly 40rem, so the old fixed floors (36rem + 18rem + a 3rem gap = 57rem)
     could not fit and the section scrolled the page sideways from 768px through ~912px
     (issue 258). The E2E cases pin the rendered page width across that band.

     Column 2's floor is the button's own min-width (14.75rem — the four-CTA button rule
     below re-declares it and wins over the earlier 15.5rem), so the button can never
     overflow its track. Keep the two in step. Column 1 is 0-floored and fractional, so it
     absorbs whatever is left. The desktop composition is unchanged in practice because
     .cta__title and .cta__body already cap themselves (max-width 40rem / 22rem) long
     before the tracks stop growing.

     Two separate mechanisms, easy to conflate: the `0` min sizing function is what lets
     the TRACK size below the title's min-content (a `minmax(0, ...)` track's minimum is a
     fixed 0, so a grid item's `min-width: auto` cannot hold it open). That alone would
     still leave a long unbreakable headline word PAINTING outside the narrowed column and
     scrolling the page. What keeps the word inside the column is base.css's
     `overflow-wrap: break-word` on `p, h1..h6`, which lets it wrap mid-word. Both are
     load-bearing: at 768px, forcing `overflow-wrap: normal` on the title puts ~97px back
     outside the viewport. The E2E long-word case guards that cross-file dependency. */
  #home-cta.cta--inline .cta__inner {
    grid-template-columns: minmax(0, 2fr) minmax(14.75rem, 1fr);
    column-gap: clamp(3rem, 6vw, 5.5rem);
    row-gap: 0;
    align-items: center;
  }

  #home-cta .cta__text {
    display: contents;
    max-width: none;
  }

  /* display:contents above dissolves .cta__text, so the eyebrow becomes a grid item
     of .cta__inner. Grid blockifies its inline-block and, with no placement of its
     own, auto-flows it past every explicitly-placed sibling into the first free cell
     — row 3, column 1 — and stretches it there: a band the full width of the TITLE
     COLUMN, rendered below the button. Placing it in row 1 of the title column
     restores the pill above the title. Row 1 collapses when no eyebrow is set
     (row-gap is 0), so a CTA without one renders exactly as it did before.

     Only .cta--inline carries this placement, because align-self is the one
     declaration here that is NOT inert outside the grid: grid-column, grid-row and
     justify-self are all ignored on a flex item, but .cta__inner stays a flex COLUMN
     in the full-width layout, where the cross axis is horizontal and align-self:end
     would shove the pill to the right edge. align-self:end is itself inert today —
     row 1 is exactly the eyebrow's height, so there is no free space to align within
     — and is kept to state the intent for a row that ever grows taller.

     The bare #home-cta siblings below keep their grid-column/grid-row/justify-self
     bare: those three are safely inert on a flex item, so they describe the inline
     grid without disturbing the full-width flex column. Their align-self, which is
     NOT inert on a flex item, is scoped to .cta--inline for the same reason this
     selector is (issue 257) — a bare align-self:end/start leaked into the full-width
     column and shoved the body and button to opposite edges. */
  #home-cta.cta--inline .cta__eyebrow {
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    align-self: end;
  }

  #home-cta .cta__title {
    grid-column: 1;
    grid-row: 2 / span 2;
    max-width: var(--cta-content-width, 40rem);
    margin: 0;
    font-size: var(--cta-title-size, clamp(2.35rem, 3.05vw, 2.65rem));
    line-height: 1.1;
  }

  #home-cta .cta__body {
    grid-column: 2;
    grid-row: 2;
    max-width: 22rem;
    margin: 0 0 1.15rem;
  }

  #home-cta .cta__button {
    grid-column: 2;
    grid-row: 3;
    justify-self: start;
  }

  /* align-self is the one placement declaration that is NOT inert on a flex item,
     so it is scoped to the inline grid. In the default full-width layout .cta__inner
     stays a flex COLUMN whose cross axis is horizontal; a bare align-self:end/start
     here shoved the body to the right edge and the button to the left while the
     eyebrow and title stayed centered (issue 257). Left bare, they leak; scoped, the
     full-width body falls back to .cta--full-width .cta__inner's align-items:center and
     the button to the shared four-CTA rule's align-self:center just above. */
  #home-cta.cta--inline .cta__body {
    align-self: end;
  }

  #home-cta.cta--inline .cta__button {
    align-self: start;
  }
}

@media (max-width: 767px) {
  #home-cta.cta--inline .cta__inner {
    align-items: center;
    text-align: center;
  }

  #home-cta .cta__text {
    max-width: 100%;
  }

  #home-cta .cta__title {
    max-width: var(--cta-content-width, 21rem);
    margin-right: auto;
    margin-left: auto;
    font-size: var(--cta-title-size, clamp(1.55rem, 6.5vw, 1.75rem));
    line-height: 1.12;
  }

  #home-cta .cta__body {
    max-width: 22rem;
    margin-right: auto;
    margin-left: auto;
  }

  #home-cta .cta__button {
    align-self: center;
  }
}
/* Owned technical workflow surface motif. */
.hero .pp-workflow-surface {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border: 1px solid var(--color-border);
  background:
    linear-gradient(90deg, rgba(49, 87, 244, 0.04) 1px, transparent 1px) 0 0 / 3.25rem 100%,
    linear-gradient(180deg, rgba(49, 87, 244, 0.05) 1px, transparent 1px) 0 0 / 100% 3.25rem,
    linear-gradient(180deg, var(--color-surface) 0%, var(--color-surface) 100%);
  box-shadow:
    0 24px 60px rgba(16, 24, 40, 0.11),
    inset 0 1px 0 rgba(255, 255, 255, 0.72);
}

.hero .pp-workflow-surface::before {
  content: "";
  position: absolute;
  z-index: 2;
  top: 0;
  right: 0;
  left: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent 0%, var(--color-accent) 16%, var(--color-accent) 52%, transparent 100%);
}

.hero .pp-workflow-surface::after {
  content: "";
  position: absolute;
  z-index: 2;
  top: 0.82rem;
  left: 1rem;
  width: 0.58rem;
  height: 0.58rem;
  border: 1px solid var(--color-accent);
  border-radius: 2px;
  background: var(--color-bg);
  box-shadow:
    1.05rem 0 0 var(--color-surface-accent),
    2.1rem 0 0 var(--color-surface);
}

.hero .pp-workflow-step {
  position: relative;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.68) 0%, rgba(255, 255, 255, 0.22) 100%);
  border-color: var(--color-border);
}

.hero .pp-workflow-step::after {
  content: "";
  position: absolute;
  top: 1.15rem;
  right: 1.25rem;
  width: 1.65rem;
  height: 0.42rem;
  border-top: 1px solid var(--color-border-accent);
  border-bottom: 1px solid var(--color-border);
  opacity: 0.72;
}

.hero .pp-workflow-step + .pp-workflow-step {
  border-left-color: var(--color-border);
}

.hero .pp-workflow-step + .pp-workflow-step::before {
  background: var(--color-bg);
  border-color: var(--color-border-accent);
  box-shadow: 0 0 0 5px rgba(49, 87, 244, 0.08);
}

.hero .pp-workflow-step--active {
  background:
    linear-gradient(180deg, var(--color-surface-accent) 0%, var(--color-surface) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(49, 87, 244, 0.30),
    0 16px 32px rgba(37, 99, 235, 0.09);
}

.hero .pp-workflow-step--active::after {
  border-color: var(--color-accent);
  opacity: 1;
}

.hero .pp-workflow-kicker {
  color: var(--color-accent);
  letter-spacing: 0.09em;
}

.hero .pp-workflow-title {
  color: var(--color-text);
}

.hero .pp-workflow-code {
  border-top-color: var(--color-border);
  background:
    linear-gradient(90deg, rgba(49, 87, 244, 0.07) 1px, transparent 1px) 0 0 / 1.35rem 100%,
    var(--color-bg);
  color: var(--color-muted);
}

@media (max-width: 767px) {
  .hero .pp-workflow-surface {
    background:
      linear-gradient(90deg, rgba(49, 87, 244, 0.04) 1px, transparent 1px) 0 0 / 2.5rem 100%,
      linear-gradient(180deg, var(--color-surface) 0%, var(--color-surface) 100%);
  }

  .hero .pp-workflow-surface::after {
    display: none;
  }

  .hero .pp-workflow-step::after {
    top: 1rem;
    right: 1rem;
  }
}
/* Explicit hero surface motif layer properties. */
.hero .pp-workflow-surface {
  background-color: var(--color-surface-accent);
  background-image:
    linear-gradient(90deg, rgba(37, 99, 235, 0.055) 1px, transparent 1px),
    linear-gradient(180deg, rgba(37, 99, 235, 0.045) 1px, transparent 1px),
    linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(37, 99, 235, 0.045));
  background-size:
    3.25rem 100%,
    100% 3.25rem,
    100% 100%;
  background-position:
    0 0,
    0 0,
    0 0;
  background-repeat:
    repeat,
    repeat,
    no-repeat;
}

.hero .pp-workflow-step + .pp-workflow-step::before {
  box-shadow: 0 0 0 5px rgba(37, 99, 235, 0.08);
}

.hero .pp-workflow-code {
  background:
    linear-gradient(90deg, rgba(37, 99, 235, 0.07) 1px, transparent 1px) 0 0 / 1.35rem 100%,
    var(--color-bg);
}

@media (max-width: 767px) {
  .hero .pp-workflow-surface {
    background-image:
      linear-gradient(90deg, rgba(37, 99, 235, 0.05) 1px, transparent 1px),
      linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(37, 99, 235, 0.045));
    background-size:
      2.5rem 100%,
      100% 100%;
    background-repeat:
      repeat,
      no-repeat;
  }
}
/* Page-specific surface motif image wins over prior shorthands. */
#home-hero .pp-workflow-surface,
#how-hero .pp-workflow-surface,
#agencies-hero .pp-workflow-surface,
#implementers-hero .pp-workflow-surface {
  background-color: var(--color-surface-accent);
  background-image:
    linear-gradient(90deg, rgba(37, 99, 235, .055) 1px, transparent 1px),
    linear-gradient(180deg, rgba(37, 99, 235, .045) 1px, transparent 1px),
    linear-gradient(180deg, rgba(255, 255, 255, .9), rgba(37, 99, 235, .045));
  background-size:
    3.25rem 100%,
    100% 3.25rem,
    100% 100%;
  background-position:
    0 0,
    0 0,
    0 0;
  background-repeat:
    repeat,
    repeat,
    no-repeat;
}

@media (max-width: 767px) {
  #home-hero .pp-workflow-surface,
  #how-hero .pp-workflow-surface,
  #agencies-hero .pp-workflow-surface,
  #implementers-hero .pp-workflow-surface {
    background-image:
      linear-gradient(90deg, rgba(37, 99, 235, .05) 1px, transparent 1px),
      linear-gradient(180deg, rgba(255, 255, 255, .92), rgba(37, 99, 235, .045));
    background-size:
      2.5rem 100%,
      100% 100%;
    background-repeat:
      repeat,
      no-repeat;
  }
}
/* Premium mechanism strip weight. */
#home-hero .hero__proof,
#how-hero .hero__proof,
#agencies-hero .hero__proof,
#implementers-hero .hero__proof {
  margin-top: clamp(2.4rem, 3.3vw, 3.25rem);
}

#home-hero .pp-workflow-surface,
#how-hero .pp-workflow-surface,
#agencies-hero .pp-workflow-surface,
#implementers-hero .pp-workflow-surface {
  max-width: 62rem;
  border: 1px solid var(--color-border-accent);
  border-radius: 10px;
  box-shadow:
    0 26px 64px rgba(15, 23, 42, 0.14),
    0 0 0 1px rgba(37, 99, 235, 0.055),
    inset 0 1px 0 rgba(255, 255, 255, 0.86);
}

#home-hero .pp-workflow-surface::before,
#how-hero .pp-workflow-surface::before,
#agencies-hero .pp-workflow-surface::before,
#implementers-hero .pp-workflow-surface::before {
  height: 4px;
  background: linear-gradient(90deg, rgba(37, 99, 235, 0.08) 0%, rgba(37, 99, 235, 0.72) 18%, var(--color-accent) 50%, rgba(37, 99, 235, 0.72) 82%, rgba(37, 99, 235, 0.08) 100%);
}

#home-hero .pp-workflow-step,
#how-hero .pp-workflow-step,
#agencies-hero .pp-workflow-step,
#implementers-hero .pp-workflow-step {
  min-height: 12.9rem;
  padding: 2.05rem 1.7rem 1.55rem;
  border-color: var(--color-border);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.82) 0%, rgba(248, 251, 255, 0.62) 100%);
}

#home-hero .pp-workflow-step + .pp-workflow-step,
#how-hero .pp-workflow-step + .pp-workflow-step,
#agencies-hero .pp-workflow-step + .pp-workflow-step,
#implementers-hero .pp-workflow-step + .pp-workflow-step {
  border-left-color: var(--color-border-accent);
}

#home-hero .pp-workflow-step + .pp-workflow-step::before,
#how-hero .pp-workflow-step + .pp-workflow-step::before,
#agencies-hero .pp-workflow-step + .pp-workflow-step::before,
#implementers-hero .pp-workflow-step + .pp-workflow-step::before {
  width: 0.82rem;
  height: 0.82rem;
  border-width: 2px;
  border-color: var(--color-accent);
  background: var(--color-surface-accent);
  box-shadow:
    0 0 0 5px rgba(37, 99, 235, 0.08),
    0 0 0 1px rgba(255, 255, 255, 0.92);
}

#home-hero .pp-workflow-step--active,
#how-hero .pp-workflow-step--active,
#agencies-hero .pp-workflow-step--active,
#implementers-hero .pp-workflow-step--active {
  background:
    linear-gradient(180deg, var(--color-surface-accent) 0%, var(--color-surface) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(49, 87, 244, 0.42),
    inset 0 4px 0 rgba(37, 99, 235, 0.28),
    0 18px 40px rgba(37, 99, 235, 0.13);
}

#home-hero .pp-workflow-step--active .pp-workflow-kicker,
#how-hero .pp-workflow-step--active .pp-workflow-kicker,
#agencies-hero .pp-workflow-step--active .pp-workflow-kicker,
#implementers-hero .pp-workflow-step--active .pp-workflow-kicker {
  color: var(--color-accent);
}

#home-hero .pp-workflow-kicker,
#how-hero .pp-workflow-kicker,
#agencies-hero .pp-workflow-kicker,
#implementers-hero .pp-workflow-kicker {
  display: block;
  margin-bottom: 0.72rem;
  color: var(--color-accent);
  font-size: 0.72rem;
  font-weight: 760;
  letter-spacing: 0.105em;
}

#home-hero .pp-workflow-title,
#how-hero .pp-workflow-title,
#agencies-hero .pp-workflow-title,
#implementers-hero .pp-workflow-title {
  display: block;
  max-width: 15rem;
  color: var(--color-text);
  font-size: 1.02rem;
  font-weight: 680;
  line-height: 1.24;
}

#home-hero .pp-workflow-code,
#how-hero .pp-workflow-code,
#agencies-hero .pp-workflow-code,
#implementers-hero .pp-workflow-code {
  display: block;
  margin-top: 1.18rem;
  padding: 0.92rem 0.95rem 0.82rem;
  border: 1px solid var(--color-border);
  border-top: 1px solid var(--color-border-accent);
  border-radius: 5px;
  background-color: rgba(255, 255, 255, 0.62);
  background-image:
    linear-gradient(90deg, rgba(37, 99, 235, .085) 1px, transparent 1px),
    linear-gradient(180deg, rgba(255, 255, 255, .88), rgba(37, 99, 235, .04));
  background-size:
    1.25rem 100%,
    100% 100%;
  color: var(--color-muted);
  font-size: 0.77rem;
  font-weight: 560;
  line-height: 1.55;
  letter-spacing: 0.01em;
}

@media (min-width: 768px) {
  #home-hero .hero__proof {
    max-width: 62rem;
  }

  #how-hero .hero__proof,
  #agencies-hero .hero__proof,
  #implementers-hero .hero__proof {
    max-width: 62rem;
  }
}

@media (max-width: 767px) {
  #home-hero .hero__proof,
  #how-hero .hero__proof,
  #agencies-hero .hero__proof,
  #implementers-hero .hero__proof {
    margin-top: 2rem;
  }

  #home-hero .pp-workflow-surface,
  #how-hero .pp-workflow-surface,
  #agencies-hero .pp-workflow-surface,
  #implementers-hero .pp-workflow-surface {
    border-radius: 8px;
  }

  #home-hero .pp-workflow-step,
  #how-hero .pp-workflow-step,
  #agencies-hero .pp-workflow-step,
  #implementers-hero .pp-workflow-step {
    min-height: auto;
    padding: 1.35rem 1.15rem 1.25rem;
  }

  #home-hero .pp-workflow-title,
  #how-hero .pp-workflow-title,
  #agencies-hero .pp-workflow-title,
  #implementers-hero .pp-workflow-title {
    max-width: 18rem;
    font-size: 0.98rem;
  }

  #home-hero .pp-workflow-code,
  #how-hero .pp-workflow-code,
  #agencies-hero .pp-workflow-code,
  #implementers-hero .pp-workflow-code {
    margin-top: 0.95rem;
    padding: 0.78rem 0.8rem;
    font-size: 0.72rem;
  }
}
/* Premium body-section typography. */
@media (min-width: 768px) {
  main > .section .section__title,
  main > .grid .grid__heading,
  main > .faq .faq__heading {
    font-weight: 560;
    line-height: 1.12;
  }

  /* Per-instance heading-color slots must survive this desktop typography rule (#86).
     Color is split per selector so each component's slot wins instead of a shared
     hardcoded var(--color-text). The theme-color var sits between the slot and the
     global token so a theme variant (e.g. .grid--inverted) still supplies the right
     default here — this rule outranks every theme selector, so without it the theme
     lost and inverted text rendered dark-on-dark above 768px (issue 222).

     font-size joins the same per-selector split (issue 302): the premium scale used
     to live in the shared rule above as a bare clamp(), outranking the base
     .section__title / .grid__heading rules ([0,1,0]) and silently ignoring a
     declared --section-title-size / --grid-heading-size. Route each through its
     size slot with the clamp() as the fallback so unset output is unchanged. faq
     now routes through --faq-heading-size too (issue 304).

     margin-bottom joins the split for section/grid (issue 343): this premium rule
     hardcoded the title -> subheading gap (1.65rem here, 1.25rem in the max-width
     rule below) at [0,2,1], outranking the base [0,1,0] rules and silently ignoring
     a declared --section-title-margin-bottom / --grid-heading-margin-bottom. Route
     each through its slot with 1.65rem as the fallback so unset output is unchanged.
     faq stays a bare literal (no faq heading-margin slot in issue 343's scope). */
  main > .grid .grid__heading {
    color: var(--grid-heading-color, var(--grid-heading-theme-color, var(--color-text)));
    font-size: var(--grid-heading-size, clamp(2.25rem, 3vw, 2.62rem));
    margin-bottom: var(--grid-heading-margin-bottom, 1.65rem);
  }

  main > .section .section__title {
    color: var(--section-title-color, var(--section-title-theme-color, var(--color-text)));
    font-size: var(--section-title-size, clamp(2.25rem, 3vw, 2.62rem));
    margin-bottom: var(--section-title-margin-bottom, 1.65rem);
  }

  main > .faq .faq__heading {
    color: var(--faq-heading-color, var(--faq-heading-theme-color, var(--color-text)));
    font-size: var(--faq-heading-size, clamp(2.25rem, 3vw, 2.62rem));
    margin-bottom: 1.65rem;
  }

  main > .section .section__content,
  main > .section .section__content p {
    color: var(--section-text, var(--section-text-theme-color, var(--color-text-secondary)));
    font-size: 1.065rem;
    font-weight: 430;
    line-height: 1.76;
  }

  main > .section--text-only .section__content {
    max-width: var(--section-body-width, 49rem);
  }

  main > .section .section__content p + p {
    margin-top: 1.05rem;
  }

  main > .grid .grid__item-title {
    color: var(--grid-item-title-color, var(--color-text));
    font-size: var(--grid-item-title-size, 1.13rem);
    font-weight: 670;
    line-height: 1.28;
  }

  main > .grid:not(.grid--steps) .grid__item-title {
    font-size: var(--grid-item-title-size, 1.14rem);
  }

  main > .grid .grid__item-text {
    color: var(--grid-item-text-color, var(--color-text-secondary));
    font-size: 1.005rem;
    font-weight: 430;
    line-height: 1.68;
  }

  main > .grid .grid__item-body,
  main > .grid .grid__item-body:first-child {
    padding: var(--grid-card-padding, 2rem);
  }

  main > .grid--steps .grid__item-title {
    max-width: 17rem;
    font-size: var(--grid-item-title-size, 1.04rem);
    font-weight: 680;
  }

  main > .grid--steps .grid__item-text {
    font-size: 0.995rem;
    line-height: 1.66;
  }

  main > .faq .faq__question {
    color: var(--faq-question-color, var(--color-text));
    font-size: 1rem;
    font-weight: 560;
    line-height: 1.45;
  }

  main > .faq .faq__answer {
    color: var(--faq-answer-color, var(--color-text-secondary));
    font-size: 1rem;
    font-weight: 430;
    line-height: 1.68;
  }

  main > .cta .cta__body {
    color: var(--cta-body-color, var(--cta-body-theme-color, var(--color-text-secondary)));
    font-size: var(--cta-body-size, 1.04rem);
    font-weight: 430;
    line-height: 1.66;
  }
}

@media (max-width: 767px) {
  main > .section .section__title,
  main > .grid .grid__heading,
  main > .faq .faq__heading {
    font-weight: 560;
    line-height: 1.15;
  }

  /* margin-bottom split per-selector so each component's heading-margin slot
     reaches the title at this breakpoint too (issue 343); fallbacks preserve
     today's 1.25rem. faq keeps the bare literal (out of issue 343's scope). */
  main > .grid .grid__heading {
    margin-bottom: var(--grid-heading-margin-bottom, 1.25rem);
  }

  main > .section .section__title {
    margin-bottom: var(--section-title-margin-bottom, 1.25rem);
  }

  main > .faq .faq__heading {
    margin-bottom: 1.25rem;
  }

  main > .section .section__content,
  main > .section .section__content p,
  main > .grid .grid__item-text,
  main > .faq .faq__answer,
  main > .cta .cta__body {
    font-size: var(--cta-body-size, 1rem);
    font-weight: 430;
    line-height: 1.65;
  }

  main > .grid .grid__item-title {
    font-size: var(--grid-item-title-size, 1.06rem);
    font-weight: 660;
    line-height: 1.28;
  }

  main > .grid .grid__item-body,
  main > .grid .grid__item-body:first-child {
    padding: var(--grid-card-padding, 1.55rem);
  }

  main > .faq .faq__question {
    font-size: 0.98rem;
    font-weight: 560;
    line-height: 1.42;
  }
}

/* True final cascade: composed card-grid hierarchy. */
main > .grid .grid__list {
  gap: var(--grid-gap, 1rem);
}

main > .grid .grid__item {
  position: relative;
  /* Route the all-cards border through the slot so a per-instance
     --grid-card-border is honored on cards 2..N, not just the featured
     first card (issue 292 — regression of issue 226). Unset keeps the
     neutral --color-border default. */
  border-color: var(--grid-card-border, var(--color-border));
  box-shadow: var(--grid-card-shadow, 0 12px 28px rgba(15, 23, 42, 0.045));
}

main > .grid:not(.grid--steps) .grid__item {
  /* The decorative 1px rule stays; the fill layer routes through the
     per-instance card-bg slot so a set --grid-card-bg (e.g. a dark-band card
     color) is honored instead of being clobbered by this late cascade rule.
     Unset keeps the original bg→surface gradient. */
  background:
    linear-gradient(90deg, rgba(37, 99, 235, 0.028) 1px, transparent 1px) 0 0 / 3rem 100%,
    var(--grid-card-bg, linear-gradient(180deg, var(--color-bg) 0%, var(--color-surface) 100%));
}

/* The card top-bar and featured first-card rules moved into the COMPONENT: grid
   block (issue 293); the cascade-equivalence argument lives with the rules there. */

main > .grid:not(.grid--steps):not(.grid--uniform) .grid__item:first-child .grid__item-title {
  color: var(--grid-item-title-color, var(--color-text));
}

main > .grid .grid__item-link {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding-top: 0.2rem;
}

main > .grid .grid__item-link::after {
  content: "->";
  color: currentColor;
  font-weight: 700;
}

@media (min-width: 768px) {
  main > .grid:not(.grid--steps) .grid__list[data-pp-count="1"] {
    /* A lone card must never sit in a two-column track (issue 297): the base
       768px rule sets repeat(2, 1fr), stranding a single item in the left
       column with dead space on the right. One full-width track spans the
       container so the card's left edge aligns with the section rail, mirroring
       the count-2/count-3 "span the container" treatment (issues 303/224). This
       sits at 768px, not 1024px like the other count rules, because the
       stranding exists from the first two-column breakpoint, and no count-1
       override in the 1024px block re-declares it, so it holds through desktop. */
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (min-width: 1024px) {
  main > .grid .grid__list {
    gap: var(--grid-gap, 1.05rem);
  }

  main > .grid:not(.grid--steps) .grid__list[data-pp-count="4"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: 58rem;
    margin-right: auto;
    margin-left: auto;
  }

  main > .grid--steps .grid__list[data-pp-count="4"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: 56rem;
    margin-right: auto;
    margin-left: auto;
  }

  main > .grid:not(.grid--steps) .grid__list[data-pp-count="3"] {
    /* Three-across is the common marketing feature row. No max-width: the row
       spans the container like grid--steps does, instead of being centered and
       narrowed the way the 2- and 4-card rules are. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  main > .grid:not(.grid--steps) .grid__list[data-pp-count="2"] {
    /* Two-across rows span the container like the 3-card rule, instead of being
       capped at 52rem and centered. The centered cap left the pair on a narrower
       rail than the intro copy above it (measured at desktop: pair left-edge 304px
       vs section rail 176px), so "intro text + two feature cards" could never read
       as one aligned section. Removing the max-width + auto margins aligns the pair
       with the section rail, mirroring the count-3 treatment (issue 303, sibling of
       issue 224). */
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  main > .grid:not(.grid--steps):not(.grid--uniform) .grid__item:first-child .grid__item-body {
    padding-top: 2.25rem;
  }

  main > .grid:not(.grid--steps):not(.grid--uniform) .grid__item:first-child .grid__item-title {
    font-size: var(--grid-item-title-size, 1.22rem);
    line-height: 1.22;
  }

  main > .grid--dark:not(.grid--steps):not(.grid--uniform) .grid__item:first-child {
    transform: translateY(-0.18rem);
  }

  main > .grid--dark:not(.grid--steps):not(.grid--uniform) .grid__item:first-child:hover {
    transform: translateY(-0.34rem);
  }
}

@media (max-width: 767px) {
  main > .grid .grid__list {
    gap: var(--grid-gap, 0.85rem);
  }

  main > .grid:not(.grid--steps):not(.grid--uniform) .grid__item:first-child {
    /* Same featured-shadow chain as the component-block rule (issue 293), so the
       slot does not silently no-op below 768px; only the glow literal differs. */
    box-shadow: var(--grid-featured-shadow, var(--grid-card-shadow, 0 14px 32px rgba(37, 99, 235, 0.09)));
  }
}

/* True final cascade: premium CTA treatment.
   Layout/typography apply to every button. The FILLED treatment (gradient
   background-image, accent border, light text) applies to primary buttons only:
   outline/ghost/secondary variants are transparent by design, and a gradient
   background-IMAGE here would paint over their transparent background-COLOR
   (a different layer that `background-color: transparent` cannot clear),
   flattening them into low-contrast filled buttons. */
main .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 13.25rem;
  min-height: 3rem;
  padding: 0.82rem 1.18rem;
  border-radius: 6px;
  font-size: 0.96rem;
  font-weight: 640;
  line-height: 1.2;
  text-align: center;
  transition:
    background-color var(--transition),
    border-color var(--transition),
    box-shadow var(--transition),
    color var(--transition),
    transform var(--transition);
}

main .btn:not(.btn--outline):not(.btn--ghost):not(.btn--secondary) {
  border: 1px solid var(--color-accent-strong);
  background:
    linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-strong) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 10px 24px color-mix(in srgb, var(--color-accent-strong) 16%, transparent);
  color: var(--color-bg);
}

main .btn:not(.btn--outline):not(.btn--ghost):not(.btn--secondary):hover {
  border-color: var(--color-accent);
  background:
    linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-hover) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 14px 30px color-mix(in srgb, var(--color-accent-strong) 22%, transparent);
  color: var(--color-bg);
  transform: translateY(-1px);
  text-decoration: none;
}

main .btn:hover {
  transform: translateY(-1px);
  text-decoration: none;
}

main .btn:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 4px;
}

/* Outline/ghost keep a transparent fill inside the premium cascade — this clears
   the gradient background-IMAGE layer the filled rule would otherwise leave on
   them, so the variant's own foreground/border treatment stays readable. */
main .btn--outline,
main .btn--ghost {
  background: transparent;
}

#home-hero .hero__cta-group .btn,
#how-hero .hero__cta-group .btn,
#agencies-hero .hero__cta-group .btn,
#implementers-hero .hero__cta-group .btn {
  width: auto;
  min-width: 13.75rem;
  max-width: 18rem;
}

#home-cta .cta__button,
#how-cta .cta__button,
#agencies-cta .cta__button,
#implementers-cta .cta__button {
  position: relative;
  width: fit-content;
  min-width: 14.75rem;
  max-width: 20rem;
  padding-right: 1.35rem;
  padding-left: 1.35rem;
  /* Last-resort break for an unbreakable author-supplied button_text label
     (issue 266): `anywhere` (unlike the global `break-word`) feeds soft-wrap
     opportunities into the intrinsic min-content size, so `fit-content`
     collapses to the available track and the label wraps inside the button
     instead of widening the column past its grid track and scrolling the
     page. Short and spaced labels are unaffected (they still hit min-width). */
  overflow-wrap: anywhere;
}

#home-cta .cta__button::before,
#how-cta .cta__button::before,
#agencies-cta .cta__button::before,
#implementers-cta .cta__button::before {
  content: "Next step";
  position: absolute;
  top: -1.72rem;
  left: 0;
  color: var(--color-accent);
  font-size: 0.72rem;
  font-weight: 720;
  letter-spacing: 0.105em;
  line-height: 1;
  text-transform: uppercase;
}

#home-cta .cta__button::after,
#how-cta .cta__button::after,
#agencies-cta .cta__button::after,
#implementers-cta .cta__button::after {
  content: "";
  width: 0.42rem;
  height: 0.42rem;
  margin-left: 0.68rem;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
  transform: rotate(45deg);
}

@media (min-width: 768px) {
  #home-cta .cta__button,
  #how-cta .cta__button,
  #agencies-cta .cta__button,
  #implementers-cta .cta__button {
    justify-self: start;
    margin-top: 1.35rem;
  }
}

@media (max-width: 767px) {
  main .btn {
    width: 100%;
    min-width: 0;
    max-width: 20rem;
    min-height: 2.95rem;
    padding-right: 1rem;
    padding-left: 1rem;
  }

  #home-hero .hero__cta-group,
  #how-hero .hero__cta-group,
  #agencies-hero .hero__cta-group,
  #implementers-hero .hero__cta-group {
    width: 100%;
    align-items: center;
  }

  #home-hero .hero__cta-group .btn,
  #how-hero .hero__cta-group .btn,
  #agencies-hero .hero__cta-group .btn,
  #implementers-hero .hero__cta-group .btn,
  #home-cta .cta__button,
  #how-cta .cta__button,
  #agencies-cta .cta__button,
  #implementers-cta .cta__button {
    width: 100%;
    max-width: 20rem;
  }

  #home-cta .cta__button,
  #how-cta .cta__button,
  #agencies-cta .cta__button,
  #implementers-cta .cta__button {
    margin-top: 1.3rem;
  }

  #home-cta .cta__button::before,
  #how-cta .cta__button::before,
  #agencies-cta .cta__button::before,
  #implementers-cta .cta__button::before {
    right: 0;
    text-align: center;
  }
}

/* True final cascade: CTA color and elevation correction.
   Scoped away from outline/ghost/secondary: this block re-applies the gradient
   fill and has the SAME specificity as `main .btn--outline` (both 0,1,1, since
   `main` is a type selector), so without the :not() it would win by source order
   and re-fill the transparent variants — the secondary-CTA contrast bug. */
main .btn:not(.btn--outline):not(.btn--ghost):not(.btn--secondary),
#home-hero .hero__cta-group .btn,
#how-hero .hero__cta-group .btn,
#agencies-hero .hero__cta-group .btn,
#implementers-hero .hero__cta-group .btn,
#home-cta .cta__button,
#how-cta .cta__button,
#agencies-cta .cta__button,
#implementers-cta .cta__button {
  border-color: var(--cta-button-border, var(--cta-accent, var(--color-accent-strong)));
  background:
    linear-gradient(180deg, var(--color-accent-strong) 0%, var(--color-accent-hover) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 10px 22px color-mix(in srgb, var(--color-accent-strong) 14%, transparent);
}

main .btn:not(.btn--outline):not(.btn--ghost):not(.btn--secondary):hover,
#home-hero .hero__cta-group .btn:hover,
#how-hero .hero__cta-group .btn:hover,
#agencies-hero .hero__cta-group .btn:hover,
#implementers-hero .hero__cta-group .btn:hover,
#home-cta .cta__button:hover,
#how-cta .cta__button:hover,
#agencies-cta .cta__button:hover,
#implementers-cta .cta__button:hover {
  border-color: var(--cta-button-hover-border, var(--cta-accent-hover, var(--color-accent)));
  background:
    linear-gradient(180deg, var(--color-accent) 0%, var(--color-accent-strong) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 14px 30px color-mix(in srgb, var(--color-accent-strong) 20%, transparent);
}

main .btn:focus,
main .btn:focus-visible,
#home-hero .hero__cta-group .btn:focus,
#home-hero .hero__cta-group .btn:focus-visible,
#how-hero .hero__cta-group .btn:focus,
#how-hero .hero__cta-group .btn:focus-visible,
#agencies-hero .hero__cta-group .btn:focus,
#agencies-hero .hero__cta-group .btn:focus-visible,
#implementers-hero .hero__cta-group .btn:focus,
#implementers-hero .hero__cta-group .btn:focus-visible,
#home-cta .cta__button:focus,
#home-cta .cta__button:focus-visible,
#how-cta .cta__button:focus,
#how-cta .cta__button:focus-visible,
#agencies-cta .cta__button:focus,
#agencies-cta .cta__button:focus-visible,
#implementers-cta .cta__button:focus,
#implementers-cta .cta__button:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 4px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 0 0 4px color-mix(in srgb, var(--color-accent) 14%, transparent),
    0 14px 30px color-mix(in srgb, var(--color-accent-strong) 20%, transparent);
}

/* True final cascade: technical workflow brand and compact mobile closers. */

/* Adjacent-sibling rhythm, restored (issue 302). This premium rule used to flatten
   the vertical rhythm to a uniform clamp() at ALL widths, outranking the base
   two-tier rule (SHARED: Adjacent-Sibling Rhythm, ~line 2124: 32px = var(--space-lg)
   between stacked components on desktop) AND defeating every --*-padding-top slot on
   an adjacent component. Drop the flat override; the base rule restores the 32px
   two-tier default for component types with no padding slot (hero/...).
   For the slot-bearing components, route the adjacent top-padding through the slot
   with var(--space-lg) as the fallback so the slot controls adjacent spacing too.
   Desktop-scoped and placed last so it wins over the base rule; mobile keeps the
   uniform rhythm below (mobile spacing is intentionally uniform). */
@media (min-width: 768px) {
  main > [data-pp-component] + .section {
    padding-top: var(--section-padding-top, var(--space-lg));
  }
  main > [data-pp-component] + .grid {
    padding-top: var(--grid-padding-top, var(--space-lg));
  }
  main > [data-pp-component] + .cta {
    padding-top: var(--cta-padding-top, var(--space-lg));
  }
  main > [data-pp-component] + .stats {
    padding-top: var(--stats-padding-top, var(--space-lg));
  }
  main > [data-pp-component] + .faq {
    padding-top: var(--faq-padding-top, var(--space-lg));
  }
}

/* Own section padding, routed through each component's padding slots (issue 302).
   This premium rule re-declared padding-top/bottom as bare clamp() literals,
   outranking the base .section/.grid/.cta rules ([0,1,0]) and silently ignoring a
   declared --*-padding-top/bottom. Split per component and route through the slot
   with the clamp() as the fallback (unset output unchanged). faq now routes
   through --faq-padding-top/bottom too (issue 304). */
.section {
  padding-top: var(--section-padding-top, clamp(4.25rem, 6vw, 5rem));
  padding-bottom: var(--section-padding-bottom, clamp(4.25rem, 6vw, 5rem));
}

.grid {
  padding-top: var(--grid-padding-top, clamp(4.25rem, 6vw, 5rem));
  padding-bottom: var(--grid-padding-bottom, clamp(4.25rem, 6vw, 5rem));
}

.cta {
  padding-top: var(--cta-padding-top, clamp(4.25rem, 6vw, 5rem));
  padding-bottom: var(--cta-padding-bottom, clamp(4.25rem, 6vw, 5rem));
}

.faq {
  padding-top: var(--faq-padding-top, clamp(4.25rem, 6vw, 5rem));
  padding-bottom: var(--faq-padding-bottom, clamp(4.25rem, 6vw, 5rem));
}

/* Page-specific texture flourish (4 benchmark pages) layered ON TOP of the
   general slot-driven background (issue 102) — an operator setting
   --hero-eyebrow-bg for one of these pages still takes effect as the base
   layer; only the decorative line pattern is page-specific. */
#home-hero .hero__eyebrow,
#how-hero .hero__eyebrow,
#agencies-hero .hero__eyebrow,
#implementers-hero .hero__eyebrow {
  border-radius: var(--hero-eyebrow-radius, 3px);
  border-color: var(--hero-eyebrow-border-color, var(--color-border));
  background:
    linear-gradient(90deg, rgba(37, 99, 235, 0.06) 1px, transparent 1px) 0 0 / 1.4rem 100%,
    var(--hero-eyebrow-bg, var(--color-surface-accent));
  color: var(--hero-eyebrow-color, var(--color-text));
}

.hero .pp-workflow-surface,
#home-hero .pp-workflow-surface,
#how-hero .pp-workflow-surface,
#agencies-hero .pp-workflow-surface,
#implementers-hero .pp-workflow-surface,
main > .grid .grid__item,
main > .faq .faq__item,
#home-cta.cta--inline .cta__inner,
#how-cta.cta--inline .cta__inner,
#agencies-cta.cta--inline .cta__inner,
#implementers-cta.cta--inline .cta__inner {
  border-radius: var(--grid-card-radius, 4px);
}

.hero .pp-workflow-surface,
#home-hero .pp-workflow-surface,
#how-hero .pp-workflow-surface,
#agencies-hero .pp-workflow-surface,
#implementers-hero .pp-workflow-surface {
  border-color: var(--color-accent-strong);
  box-shadow:
    inset 0 0 0 1px rgba(15, 23, 42, 0.04),
    0 18px 42px rgba(15, 23, 42, 0.09);
}

main > .grid .grid__item {
  /* Later-cascade winner for cards 2..N: route it through --grid-card-border
     too (matching the earlier all-cards rule and the :first-child rule below)
     or the slot stays ignored on every non-featured card (issue 292). */
  border-color: var(--grid-card-border, var(--color-border));
  box-shadow: var(--grid-card-shadow, 0 10px 24px rgba(15, 23, 42, 0.055));
}

main > .grid:not(.grid--steps):not(.grid--uniform) .grid__item:first-child {
  /* Later-cascade winner: this is the border-color that actually applies. Route it
     through --grid-card-border too (matching the earlier rule) or the slot stays
     ignored on the first card (issue 226). */
  border-color: var(--grid-card-border, var(--color-accent-strong));
}

main .btn,
#home-hero .hero__cta-group .btn,
#how-hero .hero__cta-group .btn,
#agencies-hero .hero__cta-group .btn,
#implementers-hero .hero__cta-group .btn,
#home-cta .cta__button,
#how-cta .cta__button,
#agencies-cta .cta__button,
#implementers-cta .cta__button {
  /* This premium-CTA block is the later-cascade winner for composed buttons
     (it overrides the base `.btn { border-radius: var(--btn-radius, var(--radius)) }`
     at equal specificity + source order), so the button radius must be routed
     through --btn-radius HERE or the token stays inert on every rendered button
     (issue 369). --btn-radius is registered defaulting to 4px, so unset output
     is byte-identical to the previous hardcoded 4px; an override (e.g. 100px)
     pills the button without touching the global --radius (or anything that
     reads it), so the button radius is no longer chained to the card radius. */
  border-radius: var(--btn-radius, 4px);
}

#home-cta.cta--inline .cta__inner,
#how-cta.cta--inline .cta__inner,
#agencies-cta.cta--inline .cta__inner,
#implementers-cta.cta--inline .cta__inner {
  position: relative;
  border: 1px solid var(--color-border);
  background:
    linear-gradient(90deg, rgba(37, 99, 235, 0.04) 1px, transparent 1px) 0 0 / 2.25rem 100%,
    linear-gradient(180deg, var(--color-surface) 0%, var(--color-surface) 100%);
  padding: clamp(2rem, 4vw, 2.6rem);
}

#home-cta.cta--inline .cta__inner::before,
#how-cta.cta--inline .cta__inner::before,
#agencies-cta.cta--inline .cta__inner::before,
#implementers-cta.cta--inline .cta__inner::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3px;
  background: linear-gradient(180deg, var(--color-accent), color-mix(in srgb, var(--color-accent) 18%, transparent));
}

#home-cta .cta__body,
#how-cta .cta__body,
#agencies-cta .cta__body,
#implementers-cta .cta__body {
  color: var(--cta-body-color, var(--color-text-secondary));
}

@media (max-width: 767px) {
  /* Mobile rhythm defaults to a uniform 3.35rem (SHARED: Adjacent-Sibling Rhythm
     is desktop-only, so there is no two-tier default here). This generic rule
     [0,2,1] beats the per-component .section/.grid/.cta rules below [0,1,0], so
     without the per-component adjacent rules a declared --*-padding-top would
     silently no-op on an adjacent mobile component's TOP edge (same dead-slot
     class as issue 302). Route the slot per component with 3.35rem as the
     fallback; unset output stays a uniform 3.35rem. */
  main > [data-pp-component] + [data-pp-component] {
    padding-top: 3.35rem;
  }
  main > [data-pp-component] + .section {
    padding-top: var(--section-padding-top, 3.35rem);
  }
  main > [data-pp-component] + .grid {
    padding-top: var(--grid-padding-top, 3.35rem);
  }
  main > [data-pp-component] + .cta {
    padding-top: var(--cta-padding-top, 3.35rem);
  }
  main > [data-pp-component] + .stats {
    padding-top: var(--stats-padding-top, 3.35rem);
  }
  main > [data-pp-component] + .faq {
    padding-top: var(--faq-padding-top, 3.35rem);
  }

  /* Own mobile padding, routed through the padding slots so a declared
     --*-padding-top/bottom takes effect at mobile too (issue 302). faq now routes
     through --faq-padding-top/bottom too (issue 304). Fallbacks equal the prior
     literals. */
  .section {
    padding-top: var(--section-padding-top, 3.35rem);
    padding-bottom: var(--section-padding-bottom, 3.35rem);
  }

  .grid {
    padding-top: var(--grid-padding-top, 3.35rem);
    padding-bottom: var(--grid-padding-bottom, 3.35rem);
  }

  .faq {
    padding-top: var(--faq-padding-top, 3.35rem);
    padding-bottom: var(--faq-padding-bottom, 3.35rem);
  }

  #home-cta,
  #how-cta,
  #agencies-cta,
  #implementers-cta {
    padding-top: var(--cta-padding-top, 2.85rem);
    padding-bottom: var(--cta-padding-bottom, 2.85rem);
  }

  #home-cta.cta--inline .cta__inner,
  #how-cta.cta--inline .cta__inner,
  #agencies-cta.cta--inline .cta__inner,
  #implementers-cta.cta--inline .cta__inner {
    gap: var(--cta-inner-gap, 1rem);
    padding: 1.45rem 1.2rem 1.25rem;
    text-align: left;
  }

  #home-cta .cta__text,
  #how-cta .cta__text,
  #agencies-cta .cta__text,
  #implementers-cta .cta__text {
    max-width: none;
  }

  #home-cta .cta__title,
  #how-cta .cta__title,
  #agencies-cta .cta__title,
  #implementers-cta .cta__title {
    max-width: var(--cta-content-width, none);
    margin-bottom: 0.75rem;
    font-size: var(--cta-title-size, clamp(1.48rem, 6.45vw, 1.72rem));
    font-weight: 620;
    line-height: 1.08;
  }

  #home-cta .cta__body,
  #how-cta .cta__body,
  #agencies-cta .cta__body,
  #implementers-cta .cta__body {
    max-width: 19.75rem;
    color: var(--cta-body-color, var(--color-text-secondary));
    font-size: var(--cta-body-size, 1.015rem);
    font-weight: 450;
    line-height: 1.52;
  }

  #home-cta .cta__text::after,
  #how-cta .cta__text::after,
  #agencies-cta .cta__text::after,
  #implementers-cta .cta__text::after {
    content: "Rules -> composition -> WordPress output";
    display: block;
    width: 100%;
    margin-top: 1rem;
    padding: 0.72rem 0.8rem;
    border: 1px solid var(--color-border);
    border-left: 3px solid var(--color-accent);
    border-radius: 3px;
    background:
      linear-gradient(90deg, rgba(37, 99, 235, 0.06) 1px, transparent 1px) 0 0 / 1.45rem 100%,
      var(--color-surface-accent);
    color: var(--color-text);
    font-size: 0.73rem;
    font-weight: 720;
    letter-spacing: 0.04em;
    line-height: 1.35;
    text-transform: uppercase;
  }

  #home-cta .cta__button,
  #how-cta .cta__button,
  #agencies-cta .cta__button,
  #implementers-cta .cta__button {
    align-self: flex-start;
    width: 100%;
    max-width: 19.75rem;
    margin-top: 1.15rem;
  }

  #home-cta .cta__button::before,
  #how-cta .cta__button::before,
  #agencies-cta .cta__button::before,
  #implementers-cta .cta__button::before {
    right: auto;
    text-align: left;
  }
}
