/* Radix base stylesheet (ADR-0263 extension, PR THEME.2, refined against the
   real firstflight-portal mock-up — docs/plans/0263-radix-visual-theming-plan.md).

   One shared, brand-neutral file for every Radix-served site — the same
   "N sites, one shared component set" shape the block library already uses
   (ADR-0263 §3). Per-site color/font come in as CSS custom properties
   (--radix-primary/--radix-secondary/--radix-font) set by render.js's
   per-request inline <style> block (PR THEME.3) from `branding_json`; this
   file never hardcodes a color or font for any specific site. Every other
   token below (--radix-ink, --radix-canvas, --radix-border, ...) is
   *derived* from those two brand colors via color-mix() — the mock
   (`Atrium/frontends/firstflight-portal/src/brand.css`) hand-picks a full
   ink/deep/canvas/border palette per brand; Radix only ever has two base
   colors to work with, so derivation is the only way to get the same
   "dark gradient hero, tinted canvas, subtle borders" visual language for
   an arbitrary site without adding new branding_json fields.

   No build step (no Sass/Tailwind/PostCSS) — plain CSS, consistent with
   Radix carrying no bundler/JSX-transform for its JS either
   (blocks/index.js's own docstring explains why).

   ============================================================================
   DO NOT edit this file to make ONE site match a mock/reference design.
   ============================================================================
   This file is served, byte-identical, to every site Radix hosts
   (`express.static('/assets', ...)`, server.js) — there is no per-tenant
   variant. A rule added here to chase one client's specific look changes
   every other tenant's site too, silently, the moment it's deployed. This
   is not a hypothetical: a hardcoded fallback color taken from First
   Flight's own brand palette (`.portal-signin__tagline`'s
   `var(--radix-secondary, #cfa622)`) sat in this exact file for exactly
   this reason — any other site's portal sign-in page would have rendered
   First Flight's gold, not its own branding, the moment it hit that
   fallback.

   If you're reaching for this file because a site needs to look like a
   design you're matching it against, stop and use the actual per-site
   mechanism instead (ADR-0306 Part B):
     - `site_stylesheets` — a per-site, sanitized, ordered set of custom
       stylesheets, authored in Atrium Studio's "Stylesheets" panel and
       injected as their own `<style>` block after this file and the
       branding variables (render.js's `safeStylesheetBlocks`) — cascade
       order means a site's own rule already wins over anything here with
       equal specificity, no `!important` needed.
     - `Atrium/scripts/push_site_stylesheet.py <site_id> <file.css>` — the
       fast path for iterating: sanitizes, writes the row, and re-publishes
       the bundle in one command, so there's no reason editing this file is
       ever the quicker option.

   A change belongs in *this* file only when it's a decision about the
   shared base template itself — a new `--radix-*` token, a new block
   type's default styling, or a real bug fixed for every tenant at once —
   never a value chosen to satisfy one site's brief. If a fallback needs a
   literal color, it must be a neutral (see the plain grays at
   `var(--radix-primary, #666)` etc. below) — never a specific client's
   brand hex, even as "just a fallback": see the incident above. */

/* Self-hosted Inter (SIL OFL, same file the firstflight-portal mock bundles)
   — ADR-0288. Never set as the global --radix-font default (every other
   site keeps the system-font fallback below); a site opts in per its own
   branding_json.font_family, e.g. first-flight-demo's portal site sets
   "InterVariable, -apple-system, sans-serif". */
@font-face {
  font-family: 'InterVariable';
  src: url('/assets/fonts/InterVariable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --radix-primary: #1f2937;
  --radix-secondary: #6b7280;
  --radix-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, system-ui, sans-serif;

  /* Derived from --radix-primary — see file docstring. */
  --radix-ink: color-mix(in srgb, var(--radix-primary) 55%, black);
  --radix-deep: color-mix(in srgb, var(--radix-primary) 80%, black);
  --radix-bright: color-mix(in srgb, var(--radix-primary) 55%, white);
  --radix-canvas: color-mix(in srgb, var(--radix-primary) 3%, white);
  --radix-border: color-mix(in srgb, var(--radix-ink) 10%, transparent);

  /* Derived from --radix-secondary (ADR-0288) — the one role slot reserved
     for "premium/scored" moments: Client Portal match-score rings/factor
     bars, membership badges, active-nav accents. A site sets its own
     --radix-secondary via branding_json to put its own color here (e.g.
     first-flight-demo's gold, #cfa622) — never hardcode a literal color for
     this role in shared block/CSS code. */
  --radix-accent: color-mix(in srgb, var(--radix-secondary) 92%, black);

  --radix-bg: #ffffff;
  --radix-text: #14232a;
  --radix-text-muted: #5f7076;
  /* A slightly-off-white nested surface (ADR-0288) — document rows, filter
     panels, footer strips inside an already-white card. */
  --radix-surface-2: color-mix(in srgb, var(--radix-primary) 1.5%, white);
  /* ADR-0379 §D3 — one multiplier, not five separate overrides. A site's
     `spacing_density` preset sets only `--radix-space-scale` (0.85 compact,
     1 default/unset, 1.15 spacious); every step scales off the same base
     ramp below, so the ratios between steps never invert or collide
     regardless of which preset is picked. */
  --radix-space-scale: 1;
  --radix-space-1: calc(0.5rem * var(--radix-space-scale));
  --radix-space-2: calc(1rem * var(--radix-space-scale));
  --radix-space-3: calc(1.5rem * var(--radix-space-scale));
  --radix-space-4: calc(2.5rem * var(--radix-space-scale));
  --radix-space-5: calc(4.5rem * var(--radix-space-scale));
  --radix-radius: 16px;
  /* Mock parity follow-up — small chip-like elements (tag/badge pills) use
     the mock's own Mantine `Badge` radius (a measured 4px), not the card/
     button radius above scaled down — at chip height (~24px) `--radix-
     radius`'s 16px reads almost as round as the 999px pill it replaced on
     `.cta`; a dedicated, smaller token keeps chips looking like chips. */
  --radix-radius-sm: 4px;
  --radix-max-width: 1180px;
  /* ADR-0379 §D3 — `shadow_style` swaps both variables together (a card's
     shadow and its hover/lift shadow have to stay a matched pair), unset
     keeps today's values exactly. `none` clears both to `none`; `pronounced`
     is a stronger version of the same two-layer shape, not a new one. */
  --radix-shadow-card: 0 1px 2px rgba(7, 31, 35, 0.04), 0 6px 20px -12px rgba(7, 31, 35, 0.22);
  --radix-shadow-lift: 0 2px 4px rgba(7, 31, 35, 0.05), 0 18px 40px -18px rgba(7, 31, 35, 0.32);
}

/* ---------------------------------------------------------------- reset */
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  font-family: var(--radix-font);
  color: var(--radix-text);
  background: var(--radix-bg);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
h1,
h2,
h3,
h4 {
  margin: 0 0 var(--radix-space-2);
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: -0.02em;
  /* ADR-0379 §D1 — a distinct heading face is opt-in; unset falls all the
     way back to the body font, so a site that never sets it is byte-for-
     byte unchanged. */
  font-family: var(--radix-heading-font, var(--radix-font));
}
h1 {
  /* ADR-0379 §D2 — `--radix-h1-size` (unset by default) lets a site's
     `type_scale` preset replace this exact clamp() with a flat,
     ratio-derived value; "Default" never sets the variable, so the
     original fluid clamp() stays exactly as it was for every existing
     site. */
  font-size: var(--radix-h1-size, clamp(2.4rem, 5vw, 4rem));
  letter-spacing: -0.032em;
  line-height: 1.06;
}
h2 {
  font-size: var(--radix-h2-size, clamp(1.5rem, 3vw, 2.1rem));
}
h3 {
  font-size: var(--radix-h3-size, 1.15rem);
}
p {
  margin: 0 0 var(--radix-space-2);
  color: var(--radix-text-muted);
  /* ADR-0379 §D2 — same opt-in pattern as the headings above; unset keeps
     the browser/body default body copy size exactly as before. */
  font-size: var(--radix-body-size, inherit);
}
a {
  color: var(--radix-primary);
}
img {
  max-width: 100%;
  display: block;
}

/* G00103 — every style-override-eligible block spreads `styleOverrideAttrs`
   (`blocks/index.js`), which sets `--radix-bg`/`--radix-text` as inline
   custom properties on the block's own root element via `computeBlockStyle`
   (`themeTokens.js`) — but a custom property only *does* anything where a
   real declaration reads it with `var(...)`. Before this rule, that was true
   for exactly two block types (`.block-hero`/`.block-cta-band`, both via the
   hardcoded `.radix-dark-band` gradient, which paints from the site's brand
   primary color, not from this override at all) — so authoring a background/
   text-color override on any other block type rendered a real inline style
   attribute in the DOM with nothing downstream to consume it: accepted by
   the API, stored, republished, silently invisible. `.block-hero`/
   `.block-cta-band` are deliberately excluded here — they already have
   their own established dark-gradient look via `.radix-dark-band` and this
   rule must not fight that cascade. */
.block-stat-strip,
.block-process-steps,
.block-feature-grid,
.block-icon-card-grid,
.block-faq,
.block-form,
.block-logo-wall,
.block-prose,
.block-custom-html,
.block-custom-code,
.block-collection-grid,
.block-testimonial-set,
.block-pricing-table,
.block-image,
.block-container,
/* Caught while adding `.block-image` above (2026-09-08) — `embed`
   (ADR-0383) was added after this rule and never joined it, so its own
   documented "background/text color + corner radius" style-override
   surface (`STYLE_OVERRIDE_BLOCK_TYPES`'s own comment) has been silently
   inert since it shipped, the exact bug this rule exists to prevent.
   `.block-container` (ADR-0387) is added straight into this list from day
   one specifically to not repeat that. */
.block-embed {
  background: var(--radix-bg, transparent);
  color: var(--radix-text, inherit);
}

/* Every block/chrome section shares one centered, padded container so
   authors never have to think about layout width.
   Mock parity follow-up — `4.5rem 2rem` matches the mock's own `.ff-band`
   exactly (`firstflight-portal/src/brand.css`), not a reused generic
   spacing token: the mock's per-section rhythm is a deliberate, specific
   value, and reusing `--radix-space-4` (2.5rem, meant for smaller in-block
   gaps elsewhere) under-spaced every section boundary on the page by 32px
   top+bottom — the single biggest contributor to the two sites' differing
   overall page height/rhythm. */
.radix-site-header,
.radix-site-footer,
main > section {
  padding: 4.5rem 2rem;
}
main > section > *,
main > section > .grid {
  max-width: var(--radix-max-width);
  margin-left: auto;
  margin-right: auto;
}

/* Small uppercase label above a block's heading (PR THEME.7) — matches the
   mock's `SectionHead` eyebrow/title pairing. On a dark gradient band the
   eyebrow switches to the secondary/accent color (mirrors the mock's gold
   eyebrow-on-ink treatment) instead of the primary color used everywhere
   else, since primary is already the band's own background hue. */
.eyebrow-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--radix-primary);
  /* `margin: 0 auto 0.5rem` — not `0 0 0.5rem` — deliberately. Every block
     using this label sits inside `main > section > *`'s
     `margin-left/right: auto` centering (theme.css above), but that rule's
     specificity (a two-type-selector combinator) is lower than this class
     selector's, so an explicit `margin-left: 0`/`margin-right: 0` here
     would win the cascade and pin the eyebrow flush-left while its sibling
     `<h2>` (styled only by the lower-specificity `h1,h2,h3,h4` rule) centers
     normally — visibly misaligned on any viewport wider than
     --radix-max-width plus this section's own padding. `auto` matches what
     the heading already resolves to instead of fighting it. */
  margin: 0 auto 0.5rem;
}
.radix-dark-band .eyebrow-label {
  color: var(--radix-secondary);
}

/* Checklist mode (PR THEME.7) — a plain vertical list with a small
   circular check icon, used by `icon_card_grid` when every item is
   title-only (see blocks/index.js). Deliberately has none of the card
   grid's border/shadow/hover chrome. */
.block-icon-card-grid.checklist .checklist-list {
  list-style: none;
  margin: var(--radix-space-2) 0 0;
  padding: 0;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-1);
}
.checklist-item {
  display: flex;
  align-items: flex-start;
  gap: var(--radix-space-1);
}
.checklist-item .icon {
  width: 22px;
  height: 22px;
  padding: 4px;
  margin-bottom: 0;
  margin-top: 1px;
  flex: none;
  border-radius: 50%;
}
.checklist-item span:last-child {
  line-height: 1.6;
}

/* `icon_list` layout — a plain multi-column grid of icon+title+description
   rows, no card border/shadow/background. Distinct from `.checklist-list`
   (single narrow column, title-only) and from `.card` (stacked icon-above-
   text inside a bordered box) — see the layout's own comment in
   blocks/index.js for why neither existing shape fit. */
.icon-list-grid {
  display: grid;
  /* `--radix-grid-cols` — same var `.grid` (cards/collection_grid) reads,
     set inline from the block's own `grid_columns` field; unset keeps the
     auto-fit behavior this shipped with. */
  grid-template-columns: repeat(var(--radix-grid-cols, auto-fit), minmax(240px, 1fr));
  gap: var(--radix-space-2) var(--radix-space-3);
  margin-top: var(--radix-space-2);
}
.icon-list-item {
  display: flex;
  align-items: flex-start;
  gap: var(--radix-space-1);
}
.icon-list-item-icon {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  flex: none;
  display: grid;
  place-items: center;
  margin-top: 2px;
  background: color-mix(in srgb, var(--item-accent, var(--radix-primary)) 12%, transparent);
  color: var(--item-accent, var(--radix-primary));
}
.icon-list-item-icon .icon {
  width: 14px;
  height: 14px;
  padding: 0;
  margin: 0;
  background: none;
}
.icon-list-item-text {
  min-width: 0;
}
.icon-list-item-title {
  font-size: 0.875rem;
  font-weight: 650;
  margin: 0;
  line-height: 1.3;
}
.icon-list-item-description {
  font-size: 0.8rem;
  color: var(--radix-text-muted);
  margin: 0.2rem 0 0;
  line-height: 1.5;
}

/* ------------------------------------------------------------- CTA links */
.cta {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.7em 1.5em;
  /* Mock parity — the mock's buttons (`SiteHeader`/hero/footer, all Mantine
     `Button`) are consistently 16px rounded rects, never a full pill;
     `--radix-radius` (already the shared card/blockquote/tier radius
     everywhere else in this file) is the right token, not a bespoke 999px
     pill that only `.cta` ever used. */
  border-radius: var(--radix-radius);
  text-decoration: none;
  font-weight: 600;
  transition:
    transform 170ms cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 170ms ease;
}
.cta:hover {
  transform: translateY(-2px);
}
/* Mock parity follow-up — the mock trails most primary CTAs with an arrow
   (`rightSection={<ArrowRight/>}`); `cta_icon` (opt-in field) reuses this
   for whichever icon key an author names, not just an arrow. */
.cta .icon {
  width: 15px;
  height: 15px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}
.cta.primary,
.cta:not(.secondary) {
  background: var(--radix-primary);
  color: white;
  box-shadow: var(--radix-shadow-card);
}
.cta.primary:hover,
.cta:not(.secondary):hover {
  background: color-mix(in srgb, var(--radix-primary) 88%, black);
  box-shadow: var(--radix-shadow-lift);
}
.cta.secondary {
  background: color-mix(in srgb, var(--radix-primary) 10%, white);
  color: var(--radix-primary);
  border: 1px solid color-mix(in srgb, var(--radix-primary) 30%, white);
}
.cta.secondary:hover {
  background: color-mix(in srgb, var(--radix-primary) 18%, white);
}
/* ADR-0376 D12 — a third tone, opt-in per CTA field, that actually reads
   the brand's secondary/accent color (nothing else in this file's `.cta`
   rules ever did). Excluded from the two `:not(.secondary)` dark-band
   overrides below so a page that opts into it keeps its own accent color
   even inside a hero/cta-band, rather than being forced back to white. */
.cta.accent {
  background: var(--radix-secondary);
  color: white;
  box-shadow: var(--radix-shadow-card);
}
.cta.accent:hover {
  background: color-mix(in srgb, var(--radix-secondary) 88%, black);
  box-shadow: var(--radix-shadow-lift);
}
/* On a dark section (hero's default theme, or cta-band's default
   `full_bleed_dark` variant — ADR-0376 D3 made the dark treatment opt-out
   via `.radix-dark-band`, no longer cta-band's only rendering), an
   inverted-to-white primary CTA and a translucent secondary CTA read
   correctly against the gradient. Both scope on the class (not the block
   name) — ADR-0395 follow-up made hero's own `.radix-dark-band` conditional
   too (`fields.theme === 'light'` omits it), so what used to be true
   unconditionally for every hero ("it always carries `.radix-dark-band`")
   no longer is; scoping here the same way cta-band's `inline_light` variant
   already did is what makes a light-theme hero fall through to the plain
   `.cta`/`.cta.secondary` rules above instead. */
.block-hero.radix-dark-band .cta:not(.secondary):not(.accent),
.block-cta-band.radix-dark-band .cta:not(.secondary):not(.accent) {
  background: white;
  color: var(--radix-ink);
}
.block-hero.radix-dark-band .cta:not(.secondary):not(.accent):hover,
.block-cta-band.radix-dark-band .cta:not(.secondary):not(.accent):hover {
  background: color-mix(in srgb, white 90%, var(--radix-ink));
}
.block-hero.radix-dark-band .cta.secondary,
.block-cta-band.radix-dark-band .cta.secondary {
  background: rgba(255, 255, 255, 0.12);
  color: white;
  border-color: rgba(255, 255, 255, 0.35);
}
.block-hero.radix-dark-band .cta.secondary:hover,
.block-cta-band.radix-dark-band .cta.secondary:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* A shared "grid paper" texture used by every dark gradient section
   (hero, cta-band) — mirrors brand.css's ::after treatment on
   .ff-sitehero/.ff-band--ink, built from plain rgba(white) lines so it
   needs no brand-specific color. */
.radix-dark-band {
  position: relative;
  overflow: hidden;
  color: white;
  background:
    radial-gradient(
      95% 130% at 82% 0%,
      color-mix(in srgb, var(--radix-bright) 55%, transparent) 0%,
      transparent 56%
    ),
    linear-gradient(150deg, var(--radix-ink) 0%, var(--radix-deep) 56%, var(--radix-primary) 100%);
}
.radix-dark-band::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
  background-size: 56px 56px;
  mask-image: radial-gradient(85% 110% at 18% 0%, #000 0%, transparent 72%);
}
.radix-dark-band > * {
  position: relative;
  z-index: 1;
}

/* ------------------------------------------------------------------ header */
.radix-site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--radix-space-2);
  padding-top: var(--radix-space-1);
  padding-bottom: var(--radix-space-1);
  border-bottom: 1px solid var(--radix-border);
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--radix-bg) 86%, transparent);
  backdrop-filter: saturate(170%) blur(14px);
}
.radix-brand {
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.radix-wordmark {
  color: var(--radix-ink);
}
.radix-logo {
  max-height: 32px;
}
.radix-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem;
}
.radix-nav a {
  text-decoration: none;
  color: var(--radix-text-muted);
  font-weight: 550;
  font-size: 0.86rem;
  padding: 0.42rem 0.72rem;
  border-radius: 8px;
  transition: color 130ms ease;
}
.radix-nav a:hover {
  color: var(--radix-primary);
}
/* ADR-0376 D8 — the public nav never marked its own current page. */
.radix-nav a[data-active='true'] {
  color: var(--radix-primary);
  background: color-mix(in srgb, var(--radix-primary) 10%, transparent);
}
.radix-header-cta {
  display: flex;
  gap: var(--radix-space-1);
}
.radix-header-cta .cta {
  padding: 0.55em 1.2em;
  font-size: 0.9rem;
}

/* Mock parity — `data-transparent-until-scroll` (`chrome.js`'s `Header`,
   set by `render.js` when the page's leading block is `hero`) floats the
   header, transparent, directly over the hero, then solidifies to the
   normal look once `header-scroll.js` marks `data-scrolled` past the
   threshold. `position: fixed` the whole time (never switches to
   `sticky`) so there is no layout-mode change, and therefore no content
   jump, at the scroll threshold — only the background/text colors toggle.
   Reserves no space in the document flow in either state, same as
   `position: absolute` would, which is exactly right pre-scroll (floats
   over the hero's own already-generous top padding) and still correct
   post-scroll (a `position: fixed` bar doesn't need flow space to stay
   pinned). Every page without the attribute keeps today's unaffected
   `position: sticky` header.

   The attribute's VALUE is the leading hero's own `theme` ('dark'/'light')
   — matched here so the position/transparency rule below applies over
   EITHER hero theme, while the two theme-specific blocks that follow only
   set the text-contrast rules each theme actually needs (G00133 5.1). */
.radix-site-header[data-transparent-until-scroll] {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  width: 100%;
  background: transparent;
  border-bottom-color: transparent;
  backdrop-filter: none;
}
/* Dark hero (the pre-existing, and still default, case) — light text over
   the hero's own dark gradient band. */
.radix-site-header[data-transparent-until-scroll='dark'] .radix-wordmark {
  color: rgba(255, 255, 255, 0.92);
}
.radix-site-header[data-transparent-until-scroll='dark'] .radix-nav a {
  color: rgba(255, 255, 255, 0.78);
}
.radix-site-header[data-transparent-until-scroll='dark'] .radix-nav a:hover,
.radix-site-header[data-transparent-until-scroll='dark'] .radix-nav a[data-active='true'] {
  color: #fff;
  background: rgba(255, 255, 255, 0.14);
}
/* G00133 5.1 — light hero: the hero itself already sits on the page's own
   plain background with dark text, so the floating pre-scroll header uses
   the exact same ink/muted/primary values the SCROLLED (solid) header
   below already uses — a light hero's background and the scrolled header's
   background are both "the page's own light surface," so one set of values
   correctly serves both. Without this, every page whose leading hero uses
   `theme: "light"` rendered a fully invisible white-on-white header
   (logo, nav, and secondary CTA all present but unreadable) until scroll. */
.radix-site-header[data-transparent-until-scroll='light'] .radix-wordmark {
  color: var(--radix-ink);
}
.radix-site-header[data-transparent-until-scroll='light'] .radix-nav a {
  color: var(--radix-text-muted);
}
.radix-site-header[data-transparent-until-scroll='light'] .radix-nav a:hover,
.radix-site-header[data-transparent-until-scroll='light'] .radix-nav a[data-active='true'] {
  color: var(--radix-primary);
  background: color-mix(in srgb, var(--radix-primary) 10%, transparent);
}
.radix-site-header[data-transparent-until-scroll][data-scrolled] {
  background: color-mix(in srgb, var(--radix-bg) 86%, transparent);
  border-bottom-color: var(--radix-border);
  backdrop-filter: saturate(170%) blur(14px);
}
.radix-site-header[data-transparent-until-scroll][data-scrolled] .radix-wordmark {
  color: var(--radix-ink);
}
.radix-site-header[data-transparent-until-scroll][data-scrolled] .radix-nav a {
  color: var(--radix-text-muted);
}
.radix-site-header[data-transparent-until-scroll][data-scrolled] .radix-nav a:hover,
.radix-site-header[data-transparent-until-scroll][data-scrolled] .radix-nav a[data-active='true'] {
  color: var(--radix-primary);
  background: color-mix(in srgb, var(--radix-primary) 10%, transparent);
}
/* Mock parity follow-up — mirrors `SiteChrome.js`'s own
   `color={overHero ? 'ff-gold' : 'ff-teal'}` on its primary header button:
   the accent color reads as an emphatic "pop" against the dark hero, then
   steps back to the site's ordinary primary-color CTA once the header
   solidifies. Scoped to the un-scrolled state only — once `[data-scrolled]`
   is present these selectors stop matching and the plain `.cta`/
   `.cta.secondary` rules (unchanged) take back over.

   G00133 5.1 — also now scoped to `='dark'` specifically, not just
   attribute-presence: the translucent-white secondary-button treatment
   below (`rgba(255, 255, 255, 0.12)` background, `rgba(255, 255, 255, 0.35)`
   border) is exactly the "present but unreadable" element G00133 found on a
   LIGHT leading hero — a near-white button on a near-white background. A
   light hero needs no override here at all; the plain `.cta`/`.cta.secondary`
   rules it already falls through to were designed for a solid light
   background and read correctly there unmodified. */
.radix-site-header[data-transparent-until-scroll='dark']:not([data-scrolled])
  .radix-header-cta
  .cta:not(.secondary):not(.accent) {
  background: var(--radix-secondary);
  color: color-mix(in srgb, var(--radix-secondary) 25%, black);
}
.radix-site-header[data-transparent-until-scroll='dark']:not([data-scrolled])
  .radix-header-cta
  .cta:not(.secondary):not(.accent):hover {
  background: color-mix(in srgb, var(--radix-secondary) 88%, white);
}
.radix-site-header[data-transparent-until-scroll='dark']:not([data-scrolled])
  .radix-header-cta
  .cta.secondary {
  background: rgba(255, 255, 255, 0.12);
  color: white;
  border-color: rgba(255, 255, 255, 0.35);
}
.radix-site-header[data-transparent-until-scroll='dark']:not([data-scrolled])
  .radix-header-cta
  .cta.secondary:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* ------------------------------------------------------------------ footer */
.radix-site-footer {
  background: var(--radix-ink);
  color: rgba(255, 255, 255, 0.62);
  margin-top: var(--radix-space-5);
  padding-top: var(--radix-space-4);
  padding-bottom: var(--radix-space-3);
}
.radix-footer-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--radix-space-3);
  max-width: var(--radix-max-width);
  margin: 0 auto var(--radix-space-3);
}
.radix-footer-group h4 {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
  margin-bottom: 0.7rem;
}
.radix-footer-group ul {
  list-style: none;
  margin: 0;
  padding: 0;
}
.radix-footer-group li {
  padding: 0.22rem 0;
}
.radix-footer-group a {
  color: rgba(255, 255, 255, 0.62);
  text-decoration: none;
  font-size: 0.855rem;
  transition: color 130ms ease;
}
.radix-footer-group a:hover {
  color: white;
}
.radix-legal {
  max-width: var(--radix-max-width);
  margin: var(--radix-space-2) auto 0;
  font-size: 0.8rem;
  opacity: 0.55;
}
.radix-legal p {
  margin: 0 0 0.25rem;
}
.radix-legal p:last-child {
  margin-bottom: 0;
}
/* Mock parity follow-up — `legal_links` (blocks/index.js's `footerLegalEl`)
   is an optional row of small inline links (Privacy/Terms/Cookies, say)
   shown beside `legal_text`; wraps rather than force-fitting on narrow
   screens, same posture as `.section-head`. */
.radix-legal-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--radix-space-1);
}
.radix-legal-links {
  display: flex;
  gap: var(--radix-space-2);
  flex-wrap: wrap;
}
.radix-legal-links a {
  color: inherit;
  text-decoration: none;
}
.radix-legal-links a:hover {
  text-decoration: underline;
}
/* ADR-0376 D4 — the one footer group `type` that isn't a plain link list:
   a logo, a description paragraph, and a single link. */
.radix-footer-group-brand {
  max-width: 280px;
}
.radix-footer-brand-logo {
  height: 28px;
  margin-bottom: 0.7rem;
}
.radix-footer-group-brand p {
  font-size: 0.855rem;
  color: rgba(255, 255, 255, 0.62);
  margin: 0 0 0.7rem;
}
/* Mock parity follow-up — the brand group's link now renders through
   `ctaLinkEl` (a real `.cta` button, blocks/index.js), not a bare `<a>`.
   `.cta`'s own default (`background: var(--radix-primary); color: white`)
   assumes a light page background; the footer is always `--radix-ink`
   (dark), so this mirrors the same "white button on a dark section"
   override `.block-hero`/`.block-cta-band.radix-dark-band` already use,
   just scoped to this one corner instead of the whole `.cta` class. */
.radix-footer-group-brand .cta {
  padding: 0.5em 1.1em;
  font-size: 0.85rem;
  background: white;
  color: var(--radix-ink);
  box-shadow: none;
}
.radix-footer-group-brand .cta:hover {
  background: color-mix(in srgb, white 90%, var(--radix-ink));
}

/* -------------------------------------------------------------- generic grid */
.grid {
  display: grid;
  /* Mock parity follow-up — `--radix-grid-cols` (set inline per instance
     from `collection_grid`'s optional `grid_columns` field) pins an exact
     column count; unset falls through to the original auto-fit behavior,
     byte-identical to before this existed. */
  grid-template-columns: repeat(var(--radix-grid-cols, auto-fit), minmax(240px, 1fr));
  gap: var(--radix-space-3);
  margin-top: var(--radix-space-3);
}

/* ADR-0387 — `container`'s own grid-mode track sizing, read from the
   `--radix-container-columns`/`-rows` custom properties `Container` sets
   inline (`layoutTokens.js`) rather than a raw `grid-template-columns`
   value: an inline style attribute always wins over a stylesheet rule
   regardless of specificity, which would make the mobile collapse below
   impossible to express at all. */
.container-layout {
  grid-template-columns: repeat(var(--radix-container-columns, 1), 1fr);
  grid-template-rows: var(--radix-container-rows, auto);
}
/* ADR-0387 breakpoint extension — `--radix-container-columns-sm` (not the
   base `--radix-container-columns`) so an author's `layout.sm.columns`
   override can widen this collapse instead of being silently beaten by it:
   without a dedicated variable, `Container`'s per-breakpoint `<style>` rule
   would redeclare the SAME custom property this block's own literal
   `grid-template-columns: 1fr` already wins the cascade over (this rule is
   later in source and targets the property directly, not through a
   variable). Defaults to 1 — a page that sets no `sm` override collapses to
   one column exactly as before this extension. */
@media (max-width: 700px) {
  .container-layout {
    grid-template-columns: repeat(var(--radix-container-columns-sm, 1), 1fr);
  }
}

/* ------------------------------------------------------------------- hero */
.block-hero {
  text-align: center;
  /* Unlike .ff-sitehero, this does NOT need a negative margin to sit under
     the header — the header is `position: fixed` whenever
     `data-transparent-until-scroll` applies (see that rule below), which
     already reserves zero space in the flow, so the hero box starts at the
     true viewport top on its own. The mock's -68px margin only exists to
     cancel out ITS header's `position: sticky` (in-flow) footprint; nothing
     here needs cancelling. Padding is asymmetric (much taller top than
     bottom) to match the mock's `.ff-sitehero` visual rhythm now that both
     approaches land the hero flush with the viewport top. */
  padding: 11rem var(--radix-space-2) 6.5rem;
}
/* Mock parity follow-up — matches `.radix-dark-band .eyebrow-label` (above)
   reaching for the site's own accent color on a dark band, rather than a
   fixed translucent-white pill that ignores the site's branding entirely.
   The mock's own hero badge (`Badge variant="light" color="ff-gold"`) is
   exactly this shape: a low-opacity accent-tinted fill with solid accent
   text, never plain white-on-glass. */
.block-hero .eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.3em 0.9em;
  border-radius: 999px;
  background: color-mix(in srgb, var(--radix-secondary) 22%, transparent);
  color: var(--radix-secondary);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: var(--radix-space-2);
}
.block-hero .eyebrow .icon {
  width: 14px;
  height: 14px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}
/* G00143 — `.hero-content` (the one wrapper `Hero` now always renders around
   eyebrow/h1/subheading/hero-ctas, non-gallery case) is the single box that
   matches `main > section > *`'s generic centered-max-width rule, exactly
   like every other block's own `.grid`/wrapper child already does. Centering
   or left-aligning that ONE box (via `align-items`) replaces the old
   per-child `margin: 0` overrides below, which used to strip the outer
   centering along with the inner one because each child was itself a direct
   `main > section > *` match with no shared positioned ancestor. Scoped off
   `.hero-has-gallery` — that layout already puts `.hero-content` in a CSS
   grid with its own complete, independent alignment rules (below) that this
   must not fight. */
.block-hero:not(.hero-has-gallery) .hero-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.block-hero.hero-align-left .hero-content {
  align-items: flex-start;
}
.block-hero h1 {
  max-width: 17ch;
}
.block-hero .subheading {
  font-size: 1.15rem;
  max-width: 640px;
}
/* ADR-0395 follow-up — the subheading's own color used to be hardcoded
   white, correct only because `.radix-dark-band` was unconditional on
   every hero. Split by theme now that it isn't: dark keeps the original
   translucent-white value, light dims the site's own ink color the same
   `opacity: 0.78` amount `.block-cta-band:not(.radix-dark-band) p` already
   uses for its own light-theme body text, rather than inventing a second
   dimming convention. */
.block-hero.radix-dark-band .subheading {
  color: rgba(255, 255, 255, 0.82);
}
.block-hero:not(.radix-dark-band) .subheading {
  color: var(--radix-ink);
  opacity: 0.78;
}
.block-hero .hero-ctas,
.block-cta-band .hero-ctas {
  display: flex;
  gap: var(--radix-space-1);
  justify-content: center;
  flex-wrap: wrap;
  margin-top: var(--radix-space-3);
}
/* `text_align: "left"` (`Hero`'s `hero-align-left` class) un-centers the
   `.hero-content` box itself (`align-items: flex-start`, above) — h1/
   subheading no longer need their own margin override to follow along, since
   they're no longer individually centered boxes. The CTA row's *own*
   children (the buttons) still need this: `justify-content` governs how the
   two buttons distribute within their own row, independent of where that
   row sits inside `.hero-content`. */
.block-hero.hero-align-left .hero-ctas {
  justify-content: flex-start;
}

/* ------------------------------------------------------- hero photo gallery */
/* ADR-0383 — `.hero-has-gallery` only applies when `fields.images` is
   non-empty (`Hero`'s own `hasGallery` check), so a hero with no gallery
   images gets byte-identical markup/CSS to before this existed. Splits the
   hero into a two-column layout (text left, rotating photos right) on
   desktop, stacking back to the single-column centered layout at the
   existing 700px mobile breakpoint. */
.block-hero.hero-has-gallery {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--radix-space-4);
  align-items: center;
  text-align: left;
}
/* `main > section > *` (above) centers a section's direct children with
   `max-width` + `margin: auto`, sized for the old one-content-block-per-
   section case — it doesn't know these two are grid items meant to STRETCH
   to fill their own column. Harmless for `.hero-content` (real in-flow text
   still drives its width), but `.hero-gallery`'s only children are
   `position: absolute`, so it has no in-flow content to shrink-to-fit
   around: `margin: auto` collapses it to 0 width, and `aspect-ratio` then
   resolves its height from that same 0 — a 0×0 box with real images inside
   it, invisible with no broken-image icon (found live via DevTools
   computed-size check, not visible from curl/HTML alone). Higher specificity
   (two classes) than `main > section > *`'s (two type selectors), so this
   wins outright rather than needing `!important`. */
.block-hero.hero-has-gallery > * {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
}
.block-hero.hero-has-gallery h1,
.block-hero.hero-has-gallery .subheading {
  margin-left: 0;
  margin-right: 0;
  max-width: none;
}
.block-hero.hero-has-gallery .hero-ctas {
  justify-content: flex-start;
}
@media (max-width: 700px) {
  .block-hero.hero-has-gallery {
    grid-template-columns: 1fr;
    text-align: center;
  }
  .block-hero.hero-has-gallery h1,
  .block-hero.hero-has-gallery .subheading {
    margin-left: auto;
    margin-right: auto;
  }
  .block-hero.hero-has-gallery .hero-ctas {
    justify-content: center;
  }
}

/* The rotating photo strip itself. `.hero-gallery-slide[data-active]`
   crossfades via opacity (no layout shift, works with any image's own
   aspect ratio) — the first slide is server-rendered with
   `data-active="true"` so a JS-disabled page still shows one real photo
   rather than an empty box. `carousel.js` (`Radix/src/assets/widgets/`)
   owns rotation/dot-click for 2+ images only — a single image renders here
   with no dots and no widget script at all (`heroGalleryEl`,
   `blocks/index.js`). */
.hero-gallery {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--radix-radius);
  overflow: hidden;
  background: rgba(255, 255, 255, 0.06);
}
.hero-gallery-track {
  position: absolute;
  inset: 0;
}
.hero-gallery-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 600ms ease;
}
.hero-gallery-slide[data-active='true'] {
  opacity: 1;
}
.hero-gallery-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.hero-gallery-dots {
  position: absolute;
  bottom: var(--radix-space-2);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 0.4rem;
}
.hero-gallery-dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.45);
  cursor: pointer;
  transition:
    background 170ms ease,
    transform 170ms ease;
}
.hero-gallery-dot[data-active='true'] {
  background: #fff;
  transform: scale(1.25);
}
@media (prefers-reduced-motion: reduce) {
  .hero-gallery-slide {
    transition: none;
  }
}

/* ------------------------------------------------------------------ embed */
/* ADR-0383 — `embed`: heading + an allow-listed iframe, centered, capped at
   a readable width so a wide scheduling widget/map doesn't stretch full
   page width on a desktop-size viewport. */
.block-embed {
  text-align: center;
}
.block-embed h2 {
  margin-bottom: var(--radix-space-2);
}
.block-embed .embed-frame {
  width: 100%;
  max-width: 900px;
  border: none;
  border-radius: var(--radix-radius);
  box-shadow: var(--radix-shadow-card);
}

/* ----------------------------------------------------------------- image */
/* G00109 — a standalone image/illustration. `text-align: center` centers
   the image itself (a block-level element sized narrower than the column
   via its own natural width) the same way `.block-embed` above centers its
   iframe; `img`'s own global `display: block` (file top) means it isn't
   textual content this would otherwise misalign. */
.block-image {
  text-align: center;
}
.block-image img {
  margin: 0 auto;
  border-radius: var(--radix-radius);
}
.image-caption {
  margin: var(--radix-space-1) 0 0;
  font-size: 0.85rem;
  color: var(--radix-text-muted);
}

/* --------------------------------------------------------- feature/icon cards */
/* Mock parity follow-up — the mock varies card padding per-instance via
   Mantine's `p="md"/"lg"/"xl"` prop (`OpportunitiesPage.js`, `HomePage.js`),
   never one fixed value everywhere. `--radix-card-padding`, when a block
   instance sets `card_density` (`blocks/index.js`'s `itemsGridCore`), pins
   an explicit value the same way `--radix-grid-cols` already does for
   column count; unset keeps this exact padding, unchanged. */
.block-feature-grid .feature,
.block-icon-card-grid .card {
  position: relative;
  overflow: hidden;
  padding: var(--radix-card-padding, var(--radix-space-3) var(--radix-space-2));
  border: 1px solid var(--radix-border);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
  box-shadow: var(--radix-shadow-card);
  transition:
    transform 170ms cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 170ms ease,
    border-color 170ms ease;
}
/* Accent stripe across the top — mirrors .ff-audience's colored top edge.
   ADR-0376 D2 — `--item-accent` (set inline per-card from the item's own
   `tag_color` field, when authored) lets each card carry its own accent
   instead of every card in a grid sharing one block-wide color; falls back
   to the existing shared primary color when no per-item color is set. */
.block-icon-card-grid .card::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 3px;
  background: var(--item-accent, var(--radix-primary));
  opacity: 0.85;
}
/* ADR-0376 D2 — card metadata fields: a tag/badge pill, a metadata line
   (e.g. "Sector · Stage · Location"), a lock-icon-and-caption pattern for
   gated content, and a per-item CTA link — added to `items_grid`'s card
   `item_shape` (and mirrored for `collection_grid`'s field-mapped cards
   below) since neither block carried them before. */
.item-tag {
  display: inline-block;
  padding: 0.15em 0.75em;
  border-radius: var(--radix-radius-sm);
  font-size: 0.75rem;
  font-weight: 600;
  margin-bottom: 0.6rem;
  background: color-mix(in srgb, var(--item-accent, var(--radix-primary)) 14%, transparent);
  color: var(--item-accent, var(--radix-primary));
}
/* Mock parity follow-up — `subtitle` (items_grid's item shape): a second
   line directly under the title, e.g. a role/organization name — sits
   between the title and the description, same position the mock's own
   cards use it in. */
/* G00133 finding 2 — the other 4 accent-driven elements on this same card
   (`::before`'s top stripe, `.item-tag`, `.item-tags-chip`, `.item-index`)
   already fall back through `--item-accent`; this one was hardcoded to
   `--radix-text-muted` with no fallback chain at all, so a card with its own
   `tag_color` set correctly recolored 4 of its 5 accent-driven elements and
   silently left this one uniformly muted-gray. Same fallback shape as every
   sibling rule: a card with no `tag_color` set is unaffected. */
.item-subtitle {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--item-accent, var(--radix-text-muted));
  margin-top: -0.4rem;
  margin-bottom: 0.5rem;
}
/* Mock parity follow-up — `tags` (items_grid's item shape): a repeatable
   chip row, distinct from the single `tag_label`/`.item-tag` badge above —
   for cards needing several small labels (the mock's practice cards show
   3-4 capability chips each). `--item-chip-accent` is set inline per chip
   from that chip's own `color`; unset falls back to the shared per-item
   `--item-accent` (itself falling back to `--radix-primary`), so an
   un-colored chip still tints consistently with its card's other accents
   rather than defaulting to plain gray. */
.item-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.6rem;
}
.item-tags-chip {
  display: inline-block;
  padding: 0.15em 0.65em;
  border-radius: var(--radix-radius-sm);
  font-size: 0.72rem;
  font-weight: 600;
  background: color-mix(
    in srgb,
    var(--item-chip-accent, var(--item-accent, var(--radix-primary))) 12%,
    transparent
  );
  color: var(--item-chip-accent, var(--item-accent, var(--radix-primary)));
}
.item-metadata {
  font-size: 0.85rem;
  color: var(--radix-text-muted);
  margin-top: 0.3rem;
}
.item-locked-caption {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-size: 0.85rem;
  color: var(--radix-text-muted);
  margin-top: 0.5rem;
}
.item-locked-caption .icon {
  width: 16px;
  height: 16px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  /* Mock parity follow-up — the caption text stays muted (matches the
     mock's dimmed "Members only" label), but the lock glyph itself is
     always the accent color there, never the same muted gray as its text. */
  color: var(--radix-secondary);
}
.item-cta {
  display: inline-block;
  margin-top: 0.6rem;
  font-weight: 600;
  color: var(--radix-primary);
  text-decoration: none;
}
.item-cta:hover {
  text-decoration: underline;
}
.block-icon-card-grid a.card-link {
  text-decoration: none;
  color: inherit;
  display: block;
}
.block-feature-grid .feature:hover,
.block-icon-card-grid a.card-link:hover .card {
  transform: translateY(-4px);
  box-shadow: var(--radix-shadow-lift);
  border-color: color-mix(in srgb, var(--radix-primary) 42%, transparent);
}
/* ADR-0397 D1 — `items_grid.show_index`: a large, low-opacity ornamental
   numeral, positioned so it sits in the card's own top-right corner rather
   than behind the title/description (both flow from the top-left) — the
   card's own `overflow: hidden` (above) crops anything that spills past the
   card edge. `pointer-events: none` — purely decorative, never a click
   target. */
.item-index {
  position: absolute;
  top: -0.1em;
  right: 0.5em;
  font-size: 2.75rem;
  font-weight: 800;
  line-height: 1;
  color: var(--item-accent, var(--radix-primary));
  opacity: 0.14;
  pointer-events: none;
}
/* G00133 finding 4 — `numeral_position: "top-left-inline"`, opt-in
   alternative to the default above. In-flow, directly above the title,
   rather than absolutely positioned in the corner — closer to a
   conventional numbered feature list, at the cost of the default's own
   collision-proofing for long titles/descriptions (ADR-0397 D1's own
   reasoning still applies to the default; this is a deliberate per-block
   opt-out of it, not a replacement). Default (unset, or explicit
   `"top-right"`) is completely unaffected — this only applies to the one
   opt-in value. */
.item-index--inline {
  position: static;
  display: block;
  font-size: 2rem;
  opacity: 0.28;
  margin-bottom: 0.2rem;
}
/* ADR-0397 D2 — a real, structured criteria list on a card (`bullets`),
   additive alongside `.item description`'s own plain paragraph. */
/* A small caption directly above THIS card's own bullet list — not the
   full-row divider `.item-joiner` (below) sits between two cards. */
.item-bullets-caption {
  margin: 0.6rem 0 0.3rem;
  font-size: 0.68rem;
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--radix-text-muted);
}
.item-bullets {
  margin: 0.6rem 0 0;
  padding-left: 1.1em;
  font-size: 0.9rem;
}
.item-bullets li {
  margin-bottom: 0.3rem;
}
.item-bullets li:last-child {
  margin-bottom: 0;
}
/* A small divider label ("OR"/"AND") between two cards, spanning the full
   grid row so it reads as a connector rather than a third card. */
.item-joiner {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--radix-text-muted);
  margin: -0.2rem 0;
}
.icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 9px;
  border-radius: 11px;
  margin-bottom: 0.9rem;
  background: color-mix(in srgb, var(--radix-primary) 13%, transparent);
  color: var(--radix-primary);
}
.icon svg {
  width: 100%;
  height: 100%;
}

/* ------------------------------------------------------------- testimonials */
.block-testimonial-set .testimonials {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--radix-space-3);
  margin-top: var(--radix-space-3);
}
.block-testimonial-set blockquote {
  margin: 0;
  padding: var(--radix-space-3) var(--radix-space-3) 1.4rem;
  border: 1px solid var(--radix-border);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
  box-shadow: var(--radix-shadow-card);
  height: 100%;
}
.block-testimonial-set blockquote::before {
  content: '\201C';
  display: block;
  font-size: 2.6rem;
  line-height: 0.6;
  font-weight: 700;
  color: var(--radix-secondary);
  margin-bottom: 0.6rem;
}
.block-testimonial-set p {
  color: var(--radix-text);
}
.block-testimonial-set footer {
  margin-top: var(--radix-space-1);
}
/* Mock parity — author/role render as two lines now (blocks/index.js), not
   one comma-joined string; author reads as the "name", role as a smaller,
   muted line under it, matching the mock's own two-line footer. Scoped
   past `.block-testimonial-set p` (color: var(--radix-text)) on purpose —
   an unqualified `.testimonial-author`/`.testimonial-role` would tie on
   specificity and lose to declaration order. */
.block-testimonial-set .testimonial-author {
  margin-bottom: 0.2rem;
  font-size: 0.875rem;
  font-weight: 650;
  color: var(--radix-ink);
}
.block-testimonial-set .testimonial-role {
  margin-bottom: 0;
  font-size: 0.75rem;
  color: var(--radix-text-muted);
}

/* ------------------------------------------------------------------ pricing */
.block-pricing-table .tiers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--radix-space-3);
  margin-top: var(--radix-space-3);
}
.block-pricing-table .tier {
  padding: var(--radix-space-3);
  border: 1px solid var(--radix-border);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
  box-shadow: var(--radix-shadow-card);
  text-align: center;
}
.block-pricing-table .price {
  font-size: 1.8rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--radix-primary);
}
.block-pricing-table ul {
  list-style: none;
  padding: 0;
  margin: var(--radix-space-2) 0;
  text-align: left;
}

/* ----------------------------------------------------------------------- faq */
.block-faq details {
  max-width: var(--radix-max-width);
  margin: 0 auto var(--radix-space-1);
  padding: var(--radix-space-2);
  border: 1px solid var(--radix-border);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
}
.block-faq summary {
  cursor: pointer;
  font-weight: 600;
}

/* -------------------------------------------------------------------- cta band */
.block-cta-band {
  text-align: center;
  border-radius: 18px;
  margin: var(--radix-space-4) var(--radix-space-2);
  padding: var(--radix-space-4) var(--radix-space-3);
  box-shadow: var(--radix-shadow-lift);
}
.block-cta-band.radix-dark-band p {
  color: rgba(255, 255, 255, 0.78);
}
/* ADR-0376 D3 — `inline_light` variant: `radix-dark-band` is simply omitted
   by the component in this case, so this is everything the default (dark)
   treatment applies that a light, low-emphasis strip needs to override. */
.block-cta-band:not(.radix-dark-band) {
  background: color-mix(in srgb, var(--radix-primary) 6%, white);
  color: var(--radix-ink);
  box-shadow: none;
}
.block-cta-band:not(.radix-dark-band) p {
  color: var(--radix-ink);
  opacity: 0.78;
}
/* Mock parity follow-up — `full_bleed_centered` variant (opt-in via
   `variant`, blocks/index.js's `CtaBand`): edge-to-edge, no inset margin or
   rounded corners, with a narrower centered text column — matching the
   mock's own closing CTA band, which neither pre-existing variant produces
   (both keep the inset rounded-card treatment despite one being named
   "full_bleed_dark"). `h2`/`p` get the same narrower-max-width-plus-auto-
   margin override `.block-hero h1`/`.subheading` already use to opt out of
   `main > section > *`'s wider 1180px content box. */
.block-cta-band--full-bleed {
  margin: 0;
  border-radius: 0;
  box-shadow: none;
}
.block-cta-band--full-bleed h2 {
  max-width: 18ch;
  margin-left: auto;
  margin-right: auto;
}
.block-cta-band--full-bleed p {
  max-width: 32rem;
  margin-left: auto;
  margin-right: auto;
}
/* G00133 finding 5.2 — `inline_row` variant: a single horizontal row,
   heading+body on the left, button(s) on the right, vertically centered as
   a pair — the mock pattern neither `full_bleed_dark` nor `inline_light`
   produce (both are centered/stacked columns). Reuses `inline_light`'s own
   light, low-emphasis card background (no `radix-dark-band` class on this
   variant) rather than inventing a third color treatment. */
.block-cta-band--row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--radix-space-3);
  text-align: left;
  background: color-mix(in srgb, var(--radix-primary) 6%, white);
  color: var(--radix-ink);
  box-shadow: none;
}
.cta-band-row-text {
  flex: 1 1 320px;
}
.cta-band-row-text h2 {
  margin-bottom: 0;
}
.cta-band-row-text p {
  color: var(--radix-ink);
  opacity: 0.78;
  margin-bottom: 0;
}
.cta-band-row-actions {
  display: flex;
  gap: var(--radix-space-1);
  flex: none;
}
/* `band_icon` — a decorative icon above the heading, distinct from
   `cta_icon` (which decorates the button). Reuses the shared `.icon` tile
   look everywhere else on the site uses, just re-tinted translucent-white
   to read against a dark band instead of `--radix-primary`. */
.block-cta-band-icon {
  display: flex;
  justify-content: center;
}
.block-cta-band-icon .icon {
  margin-bottom: var(--radix-space-2);
  background: rgba(255, 255, 255, 0.14);
  color: white;
}

/* ---------------------------------------------------------------------- form */
/* Card + two-column (text left, fields right) layout (PR THEME.9) —
   matches the mock's `<Box className="ff-card"><SimpleGrid
   cols={{base:1, sm:2}}>` for its "Get in touch" section; the previous
   version had no card, no text column, and centered the form fields alone
   under the heading. `.form-card` is more specific than the generic
   `main > section > *` centering rule above, so its own (narrower)
   max-width wins regardless of source order. */
.form-card {
  max-width: 900px;
  margin: var(--radix-space-2) auto 0;
  padding: var(--radix-space-3);
  border: 1px solid var(--radix-border);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
  box-shadow: var(--radix-shadow-card);
}
.form-card-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--radix-space-3);
  align-items: center;
}
@media (max-width: 700px) {
  .form-card-grid {
    grid-template-columns: 1fr;
  }
}
.block-form form {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-1);
}
.block-form input,
.block-form textarea,
.block-form select {
  padding: 0.65em 0.85em;
  border: 1px solid var(--radix-border);
  border-radius: 10px;
  font: inherit;
}
/* Mock parity follow-up — the small mail icon inside a `form_id`-backed
   form's email field. */
.form-field-icon-wrap {
  position: relative;
  display: block;
}
.form-field-icon-wrap .icon {
  position: absolute;
  left: 0.85em;
  top: 50%;
  transform: translateY(-50%);
  width: 15px;
  height: 15px;
  padding: 0;
  margin: 0;
  background: none;
  color: var(--radix-text-muted);
  pointer-events: none;
}
.form-field-icon-wrap input {
  width: 100%;
  padding-left: 2.3em;
}
/* ADR-0311 Piece 2 — a `form_id`-backed form's checkbox field renders as a
   plain inline label + input, not the padded box every text-like field
   above gets. */
.form-checkbox-label {
  display: flex;
  align-items: center;
  gap: 0.5em;
}
/* ADR-0398 D1 — `toggle_chip`: a row of clickable chips instead of a
   checkbox list. The real `<input type="checkbox">` is visually hidden
   (not `display:none`, which would drop it from the tab order) — the
   `<span>` label is what's actually styled, so a click anywhere in the
   pill toggles the underlying control natively, no JS required. */
.chip-toggle-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5em;
}
.chip-toggle {
  display: inline-flex;
  align-items: center;
  padding: 0.45em 0.95em;
  border: 1px solid var(--radix-border);
  border-radius: 999px;
  font-size: 0.9rem;
  cursor: pointer;
  user-select: none;
  transition:
    background 150ms ease,
    border-color 150ms ease,
    color 150ms ease;
}
.chip-toggle input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.chip-toggle:has(input:checked) {
  background: var(--radix-primary);
  border-color: var(--radix-primary);
  color: var(--radix-bright, #fff);
}
.chip-toggle:has(input:focus-visible) {
  outline: 2px solid var(--radix-primary);
  outline-offset: 2px;
}
/* ADR-0310 Step 2 — canvas mode only (never present on a public page or a
   shared review link). A visible highlight on hover gives a clear cue that
   a block is a drag/drop/click target, without a heavier permanent border
   competing with the real page design underneath it. `position: relative`
   anchors the floating type-label tag below, which is positioned relative
   to this wrapper.

   A single-hue outline (the original 1px dashed treatment, and its
   strengthened 2px solid follow-up) turned out to disappear almost
   entirely on a block whose own background is derived from the same
   `--radix-primary` hue — found live on a dark-gradient Hero section,
   where a `--radix-primary`-tinted outline sat on a `--radix-primary`-tinted
   background and nearly vanished. A two-ring "halo" (white, then the brand
   color) guarantees contrast against *any* underlying color: the white
   ring alone is enough on a dark/colored background, the colored ring
   alone is enough on a light one, and having both means neither ring ever
   has to do all the work by itself. Same technique browser devtools and
   design tools (Figma) use for an element-selection highlight that has to
   work over arbitrary content. Both states share the same two-layer
   `box-shadow` shape (only the colors change) so the hover transition
   interpolates smoothly instead of snapping. */
.radix-canvas-block {
  position: relative;
  box-shadow:
    0 0 0 2px transparent,
    0 0 0 4px transparent;
  transition: box-shadow 0.15s ease;
}
.radix-canvas-block:hover {
  box-shadow:
    0 0 0 2px white,
    0 0 0 4px var(--radix-primary, #666);
}
/* Persistent — set by the parent editor via postMessage (`focus`/`unfocus`)
   whenever a block's fields are open in the editor's pop-out panel, so
   there's still a visible anchor while scrolling the canvas away from the
   block itself, not just on mouseover. Same halo, so it's just as visible
   on a dark block as the hover state is. */
.radix-canvas-block.is-focused {
  box-shadow:
    0 0 0 2px white,
    0 0 0 4px var(--radix-primary, #666);
}
/* The floating type-name tag (canvas device-preview follow-up) —
   `canvas-preview.js` creates/positions this on `mouseover`/`mouseout`
   using a block-type → friendly-label map the parent pushes down once via
   `postMessage`, reading the type off this wrapper's own
   `data-radix-block-type`. Pinned to the wrapper's top-left corner,
   matching the convention most inspector-style overlays (browser dev
   tools, Figma, Webflow) use rather than following the cursor. */
.radix-canvas-block-label {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateY(-100%);
  background: var(--radix-primary, #666);
  color: white;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 0.2em 0.6em;
  border-radius: 4px 4px 4px 0;
  pointer-events: none;
  white-space: nowrap;
  z-index: 1;
}
/* Item-selection follow-up — the repeated-item counterpart to
   `.radix-canvas-block`'s hover halo above, applied directly to the item's
   own existing element (`.card`/`.tier`/`.logo`/`.stat`/`<details>`) rather
   than a shared class on a wrapper div — there is no wrapper div for an
   item, unlike a block or a slot (see `blocks/index.js`'s `itemCanvasAttrs`
   for why). Same two-ring halo technique, same reason (visible over
   arbitrary underlying content, including a dark hero-style background). */
[data-radix-item-index] {
  position: relative;
  box-shadow:
    0 0 0 2px transparent,
    0 0 0 4px transparent;
  transition: box-shadow 0.15s ease;
}
[data-radix-item-index]:hover {
  box-shadow:
    0 0 0 2px white,
    0 0 0 4px var(--radix-primary, #666);
}
/* Persistent — set by the parent editor via postMessage (`focusItem`/
   `unfocusItem`) whenever an item's fields are open in the editor's Drawer,
   same "still visible while scrolled away" reasoning `.radix-canvas-block.
   is-focused` already has for a whole block. */
[data-radix-item-index].is-item-focused {
  box-shadow:
    0 0 0 2px white,
    0 0 0 4px var(--radix-primary, #666);
}

/* ADR-0387 D5 — a `container` child's own hover/focus halo, identical
   two-ring technique to the item rule above (`blocks/index.js` only wraps a
   child in a real div when canvas mode is active or it's positioned — see
   `Container`'s own comment — so this never applies to a published page's
   markup, which carries no `data-radix-child-index` attribute at all). */
[data-radix-child-index] {
  position: relative;
  box-shadow:
    0 0 0 2px transparent,
    0 0 0 4px transparent;
  transition: box-shadow 0.15s ease;
}
[data-radix-child-index]:hover {
  box-shadow:
    0 0 0 2px white,
    0 0 0 4px var(--radix-primary, #666);
}
[data-radix-child-index].is-child-focused {
  box-shadow:
    0 0 0 2px white,
    0 0 0 4px var(--radix-primary, #666);
}
/* ADR-0387 D6 — applied by canvas-preview.js's own pointer-drag logic for
   the duration of a real move gesture (never present in stored/rendered
   markup, and never on a published page — see that file's own comment for
   why this is Pointer Events, not the native HTML5 DnD every other
   drag/reorder interaction here uses). Raised above sibling grid/layered
   items so it visibly tracks the cursor rather than disappearing behind
   whatever it's being dragged across. */
[data-radix-child-index].is-dragging {
  opacity: 0.85;
  cursor: grabbing;
  z-index: 50;
  pointer-events: none;
}

/* The empty-canvas placeholder (ADR-0310 Step 2) — a page with zero blocks
   still needs a drop target for the palette's first block; this is that
   target, styled so it reads as "drop here," not as broken/missing content. */
.radix-canvas-empty-state {
  margin: var(--radix-space-3, 2rem);
  padding: var(--radix-space-4, 3rem) var(--radix-space-3, 2rem);
  border: 2px dashed var(--radix-border, #ccc);
  border-radius: var(--radix-radius, 8px);
  text-align: center;
  color: var(--radix-text-muted, #888);
}
/* Honeypot (ADR-0311 Piece 2) — off-screen, not `display:none`/`hidden`,
   since a scraper that skips fields it detects as hidden that way would
   also skip filling this one, defeating the whole point. A sighted visitor
   never sees or tabs to it either way. */
.form-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}
.block-form button {
  padding: 0.7em 1.4em;
  border: none;
  border-radius: 999px;
  background: var(--radix-primary);
  color: white;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--radix-shadow-card);
}
.block-form button:hover {
  background: color-mix(in srgb, var(--radix-primary) 85%, black);
}
/* ADR-0393 — multi-step forms. Back/Next are hidden until `form-logic.js`
   marks the form JS-active — the zero-JS baseline is one long form with
   only the real Submit button, every step's fields already visible
   (`blocks/index.js`'s own SSR output), not a broken stepper with two
   inert buttons sitting on the page. */
.form-step-nav {
  display: flex;
  gap: 0.75em;
  margin-top: 0.5em;
}
.form-step-back,
.form-step-next {
  display: none;
}
form.form-js-active .form-step-back,
form.form-js-active .form-step-next {
  display: inline-block;
}
.block-form button.form-step-back {
  background: transparent;
  color: var(--radix-primary);
  box-shadow: none;
  border: 1.5px solid var(--radix-primary);
}

/* Final competitive-parity checkup follow-up — a "Step N of M" indicator
   and a brief fade/slide on step change, the two named UX gaps versus
   Typeform's own multi-step forms (the mechanism itself was already at
   functional parity with Gravity Forms/WPForms). Server-rendered with the
   correct step 1 count so a zero-JS visitor — who sees every step's fields
   already unhidden in one long form (`blocks/index.js`'s own SSR output) —
   never sees a wrong or stale count; `form-logic.js`'s `showStep` is what
   updates it as steps change once the widget is running. */
.form-step-progress {
  font-size: 0.85em;
  color: var(--radix-ink, #555);
  opacity: 0.7;
  margin: 0 0 0.75em;
}
@keyframes form-step-fade-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.form-step-fade-in {
  animation: form-step-fade-in 0.25s ease;
}
@media (prefers-reduced-motion: reduce) {
  .form-step-fade-in {
    animation: none;
  }
}

/* Final competitive-parity checkup, Tier 3 — the conversational
   (Typeform-style) form layout: `fields.layout: "conversational"`
   (`Form` in blocks/index.js). Pure presentation on top of the existing
   step markup — no new DOM shape, so this is entirely a CSS swap from the
   compact two-column card to one large, centered question at a time.
   `.form-card-grid` stays a grid (unchanged rule above) but collapses to
   one column here since there's no side-by-side text column in this mode;
   `form-card-text` moves above the fields as a persistent header instead
   of sitting beside them. */
.form-conversational .form-card {
  max-width: 640px;
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  border: none;
  box-shadow: none;
  background: transparent;
}
.form-conversational .form-card-grid {
  grid-template-columns: 1fr;
  text-align: center;
}
.form-conversational .form-card-text h3 {
  font-size: var(--radix-h2-size, 2rem);
}
.form-conversational .form-field {
  margin: 0 auto;
  max-width: 480px;
}
.form-conversational input,
.form-conversational textarea,
.form-conversational select {
  font-size: 1.15em;
  text-align: center;
  border: none;
  border-bottom: 2px solid var(--radix-border);
  border-radius: 0;
  padding: 0.5em 0.25em;
  background: transparent;
}
.form-conversational input:focus,
.form-conversational textarea:focus,
.form-conversational select:focus {
  outline: none;
  border-bottom-color: var(--radix-primary);
}
.form-conversational .form-step-nav {
  justify-content: center;
  margin-top: var(--radix-space-3);
}
.form-conversational .form-step-progress {
  text-align: center;
}

/* ------------------------------------------------------------------ logo wall */
.block-logo-wall .logos {
  display: flex;
  flex-wrap: wrap;
  gap: var(--radix-space-3);
  align-items: center;
  justify-content: center;
  margin-top: var(--radix-space-2);
}
.block-logo-wall img {
  max-height: 40px;
  filter: grayscale(1);
  opacity: 0.7;
}

/* --------------------------------------------------------------------- prose */
.block-prose {
  max-width: 720px;
  margin: 0 auto;
}

/* ----------------------------------------------------------------- custom_html */
/* ADR-0377 D4 — deliberately minimal: the author's own template controls
   its own layout. `overflow-x: auto` is the one safety rule, so an
   over-wide author element (a table, a fixed-width embed) scrolls inside
   itself rather than breaking the page's own horizontal layout. */
.block-custom-html {
  overflow-x: auto;
}

/* ---------------------------------------------------------------- stat strip */
.block-stat-strip {
  text-align: center;
}
/* Grid lives on the inner `.grid` wrapper (blocks/index.js), not the
   section itself — `main > section > *` centers/max-widths whatever is a
   direct child of the section, and that needs to land on one wrapper box,
   not on each individual `.stat` (which would otherwise shrink to its own
   content width and center inside its column instead of filling it evenly). */
.block-stat-strip .grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--radix-space-3);
}
.block-stat-strip .value {
  font-size: 2.6rem;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--radix-ink);
}
.block-stat-strip .label {
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--radix-text-muted);
  margin-top: 0.5rem;
}
/* G00103 follow-up — `.value`/`.label` hardcode `--radix-ink`/
   `--radix-text-muted`, both always derived from the site's brand primary
   color regardless of what Phase 1's block-level override sets on *this*
   instance, so a stat strip authored onto a dark section rendered its
   numbers at near-zero contrast. `[style]` gates this to only an instance
   `styleOverrideAttrs` actually put an inline style on — an unstyled stat
   strip is completely unaffected. */
.block-stat-strip[style] .value {
  color: var(--radix-text);
}
.block-stat-strip[style] .label {
  color: color-mix(in srgb, var(--radix-text) 75%, transparent);
}

/* ------------------------------------------------------------- process steps */
/* A vertical numbered rail (not a grid) — matches .ff-step's stacked
   layout with a connecting line between circles, including the
   dark-section inversion when a step rail sits on .radix-dark-band. */
.block-process-steps .steps {
  list-style: none;
  counter-reset: radix-step;
  /* G00143 sweep — `auto` centered this within `.items-grid-body`'s own
     (correctly positioned) box instead of matching the left-aligned
     eyebrow/heading beside it, the one layout on the page that would have
     read as centered while everything else read as left-aligned. */
  margin: var(--radix-space-3) 0 0;
  max-width: 640px;
  padding: 0;
}
.block-process-steps .step {
  counter-increment: radix-step;
  position: relative;
  padding-left: 3.1rem;
  padding-bottom: 1.9rem;
  text-align: left;
}
.block-process-steps .step:last-child {
  padding-bottom: 0;
}
.block-process-steps .step::before {
  content: counter(radix-step);
  position: absolute;
  left: 0;
  top: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.82rem;
  color: var(--radix-primary);
  background: color-mix(in srgb, var(--radix-primary) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--radix-primary) 26%, transparent);
}
.block-process-steps .step:not(:last-child)::after {
  content: '';
  position: absolute;
  left: 17px;
  top: 38px;
  bottom: 8px;
  width: 1px;
  background: var(--radix-border);
}
/* G00103 follow-up — the two rules above are always tinted from
   `--radix-primary`/`--radix-border`, which stay dark regardless of what
   Phase 1's block-level override sets `--radix-bg`/`--radix-text` to on
   *this* instance, so a step rail authored onto a dark section (no
   `.radix-dark-band` class — that's hero/cta_band only) rendered its number
   circles and connecting line at near-zero contrast against their own new
   dark background. `[style]` is a reliable, exact signal that
   `styleOverrideAttrs` put an override on this specific element (it is the
   only thing that ever sets an inline `style` here), so this only reaches
   an instance that actually opted in — an unstyled step rail is completely
   unaffected. */
.block-process-steps[style] .step::before {
  color: var(--radix-text);
  background: color-mix(in srgb, var(--radix-text) 12%, transparent);
  border-color: color-mix(in srgb, var(--radix-text) 26%, transparent);
}
.block-process-steps[style] .step:not(:last-child)::after {
  background: color-mix(in srgb, var(--radix-text) 20%, transparent);
}
.radix-dark-band .block-process-steps .step::before {
  color: white;
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.22);
}
.radix-dark-band .block-process-steps .step:not(:last-child)::after {
  background: rgba(255, 255, 255, 0.16);
}
.radix-dark-band .block-process-steps p {
  color: rgba(255, 255, 255, 0.78);
}

/* `numbering_direction: "descending"` — counts down from the item count
   instead of up from 1. `counter-increment` accepts a negative step, so
   this is the same counter mechanism as the ascending default two rules
   above, just seeded and stepped in the other direction. */
.block-process-steps .steps.steps-descending {
  counter-reset: radix-step var(--radix-step-count, 0);
}
.block-process-steps .steps.steps-descending .step {
  counter-increment: radix-step -1;
}

/* `companion_image` — a single static image beside the whole numbered list
   (not per-item), two-up above the `sm` breakpoint and stacked (image
   second) below it, matching the stacking direction every other two-column
   block in this file already uses. */
.block-process-steps .steps-with-image {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--radix-space-4);
  align-items: center;
  max-width: none;
}
.block-process-steps .steps-with-image .steps {
  margin: 0;
  max-width: none;
}
.block-process-steps .steps-companion-image {
  width: 100%;
  height: auto;
  border-radius: var(--radix-radius, 12px);
  display: block;
}
@media (max-width: 700px) {
  .block-process-steps .steps-with-image {
    grid-template-columns: 1fr;
  }
  .block-process-steps .steps-companion-image {
    order: -1;
  }
}

/* Mock parity follow-up — `layout_variant: "two_column"` (blocks/index.js):
   intro copy (eyebrow/heading/body/meta-note) in a left column, the
   numbered steps in a right column — matches the mock's own "How
   membership works" section, which is never the single centered vertical
   rail the default `stacked` variant renders. Same two-up/stack-at-700px
   shape as `.steps-with-image` above, just one level higher (section
   content, not the steps list itself). */
.process-steps-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--radix-space-4);
  align-items: start;
}
.process-steps-split .steps {
  margin: 0;
  max-width: none;
}
.process-steps-meta {
  display: flex;
  align-items: center;
  gap: 0.5em;
  margin-top: var(--radix-space-2);
  font-size: 0.85rem;
  color: var(--radix-text-muted);
}
.process-steps-meta .icon {
  width: 16px;
  height: 16px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}
@media (max-width: 700px) {
  .process-steps-split {
    grid-template-columns: 1fr;
  }
}

/* ------------------------------------------------------------- section head */
/* Eyebrow+heading paired with an optional right-aligned CTA button on the
   same row (PR THEME.9) — matches the mock's `<Group
   justify="space-between" align="flex-end">` header
   (`AudiencePage.js`/`HomePage.js`'s "Relevant right now" / "See all"),
   used by `opportunity_teaser_grid`/`collection_grid`. Wraps rather than
   force-fitting on narrow screens. */
.section-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--radix-space-2);
  margin-bottom: var(--radix-space-2);
}
.section-head-text h2 {
  margin-bottom: 0;
}
.section-head .cta {
  flex: none;
}
/* G00133 finding 1 — the one opt-in centered variant (`collection_grid`'s
   `style.text_align: "center"`). Stacks into a centered column — CTA below
   the heading, not beside it — rather than trying to keep the row shape
   with everything centered inside it, matching the one mock section that
   used a centered head among otherwise-left-aligned ones. `.items-grid-lead`
   (the lead-paragraph child) pins itself to the left edge via `margin-left:
   0` (below) specifically to defeat `main > section > *`'s auto-centering —
   that same override would leave it looking off-center inside a centered
   head, so it's restored to `margin: 0 auto` here, same fix shape
   `.hero-align-left` already uses for the identical class of box-vs-text
   alignment mismatch. */
.section-head--center {
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.section-head--center .items-grid-lead {
  margin-left: auto;
  margin-right: auto;
}
/* G00133 finding 1 — `items_grid`'s own separate centering treatment (its
   head-building never shared `sectionHeadEl` in the first place — see
   `itemsGridCore` in `blocks/index.js`). Same fix shape as `.section-head--
   center` above: text-align handles the eyebrow/heading, `.items-grid-lead`
   needs its `margin-left: 0` (below) explicitly overridden to stay centered
   rather than pinned left. */
.items-grid-head-center {
  text-align: center;
}
.items-grid-head-center .items-grid-lead {
  margin-left: auto;
  margin-right: auto;
}
/* G00133 finding 3 — `items_grid`'s new "See all"-style CTA (only present
   when `cta_label`/`cta_url` are both set) sits beside its own heading,
   same row shape `.section-head`'s `.section-head-text`/CTA pairing already
   uses. `h2` loses its usual bottom margin here so the row's two children
   sit on one baseline instead of the CTA floating below the heading's own
   spacing. */
.items-grid-heading-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--radix-space-2);
}
.items-grid-heading-row h2 {
  margin-bottom: 0;
}
.items-grid-heading-row .cta {
  flex: none;
}
/* ADR-0397 D3 — a lead paragraph under a section heading, shared by
   `items_grid` (cards/checklist/plain-numbered layouts) and
   `collection_grid`'s own `section-head`. Muted, not full-strength text
   colour, so it reads as intro copy rather than competing with the
   heading. */
.items-grid-lead {
  color: var(--radix-text-muted);
  max-width: 60ch;
  /* `main > section > *` (above) auto-centers every direct child inside
     its own `max-width` — harmless for a child that spans the full
     available width, but this one's narrower `max-width` (60ch) is more
     specific and wins, which combined with the inherited `margin: auto`
     centers the paragraph instead of leaving it flush with the heading
     above it. Pin it back to the left edge explicitly. */
  margin-left: 0;
  margin-top: var(--radix-space-1);
}

/* --------------------------------------------------- opportunity/collection grids */
.block-opportunity-teaser-grid .card,
.block-collection-grid .card {
  padding: var(--radix-card-padding, var(--radix-space-2));
  border: 1px solid var(--radix-border);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
  box-shadow: var(--radix-shadow-card);
  position: relative;
  overflow: hidden;
}
/* G00103 follow-up — mirrors `.block-icon-card-grid .card::before` exactly
   (mock's colored left-edge stripe per opportunity type); `--item-accent`
   is only ever set when `CollectionGrid` resolved one (an explicit
   `accent_color_field`, or the `listing_type` catalog's own color) —
   otherwise this paints nothing visually new; a card with no accent still
   just uses the shared primary color, same as before this rule existed. */
.block-collection-grid .card::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--item-accent, var(--radix-primary));
  opacity: 0.85;
}
.block-opportunity-teaser-grid .card.locked {
  background: var(--radix-canvas);
}
/* Mock parity follow-up — the mock's gated opportunity cards
   (`OpportunitiesPage.js`) put a dashed-border "locked box" (lock icon,
   heading, caption, its own Sign-in button) beside the content, not a
   small badge below it. `.has-locked-box` is opt-in (`blocks/index.js`'s
   `CollectionGrid`, set only when `locked_sign_in_url` is authored), so a
   `locked_badge`-only card (every one authored before this existed) keeps
   its plain badge, unchanged. `wrap: nowrap` deliberately matches the
   mock's own `<Group wrap="nowrap">` — the box never drops below the
   content, even in a narrow card; the grid's own `minmax(240px, ...)`
   keeps a card from getting narrow enough for that to break. */
.block-collection-grid .card.has-locked-box {
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: var(--radix-space-2);
}
.block-collection-grid .card-content {
  flex: 1;
  min-width: 0;
}
.block-collection-grid .card-locked-box {
  flex: none;
  width: 140px;
  padding: 0.85rem 0.75rem;
  border-radius: 11px;
  text-align: center;
  background: var(--radix-surface-2);
  border: 1px dashed var(--radix-border);
}
.card-locked-box .icon {
  width: 15px;
  height: 15px;
  padding: 0;
  margin: 0 auto;
  background: none;
  color: var(--radix-secondary);
}
.card-locked-box-heading {
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1.35;
  margin: 0.5rem 0 0;
}
.card-locked-box-caption {
  font-size: 0.72rem;
  color: var(--radix-text-muted);
  line-height: 1.4;
  margin: 0.2rem 0 0;
}
.card-locked-box-signin {
  display: block;
  width: 100%;
  margin-top: 0.55rem;
  padding: 0.4em 0;
  /* Same pill-vs-rounded-rect correction as `.cta` (theme.css's CTA-links
     section) — spotted alongside the date-chip work below, same root cause. */
  border-radius: var(--radix-radius);
  background: color-mix(in srgb, var(--radix-primary) 10%, white);
  color: var(--radix-primary);
  font-size: 0.72rem;
  font-weight: 650;
  text-decoration: none;
  text-align: center;
  transition: background 130ms ease;
}
.card-locked-box-signin:hover {
  background: color-mix(in srgb, var(--radix-primary) 18%, white);
}
@media (max-width: 700px) {
  .block-collection-grid .card.has-locked-box {
    flex-wrap: wrap;
  }
  .block-collection-grid .card-locked-box {
    width: 100%;
  }
}
/* Mock parity follow-up — `date_field` (blocks/index.js's `CollectionGrid`)
   lays a card out as a compact horizontal row — a small day/month chip
   beside the title/summary — instead of the plain vertical card every
   other field mapping produces. Matches the mock's own homepage event
   teaser (`HomePage.js`), which never used a plain metadata-line date. */
.block-collection-grid .card.has-date-chip {
  display: flex;
  align-items: flex-start;
  gap: var(--radix-space-2);
}
.item-date-chip {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 52px;
  padding: 0.5rem 0;
  border-radius: var(--radix-radius-sm);
  background: color-mix(in srgb, var(--radix-primary) 8%, transparent);
  color: var(--radix-primary);
}
.item-date-chip-day {
  font-size: 1.1rem;
  font-weight: 700;
  line-height: 1.1;
}
.item-date-chip-month {
  font-size: 0.68rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.item-date-content {
  flex: 1;
  min-width: 0;
}
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  margin-top: var(--radix-space-1);
  padding: 0.25em 0.75em;
  border-radius: 999px;
  background: color-mix(in srgb, var(--radix-secondary) 20%, white);
  color: color-mix(in srgb, var(--radix-secondary) 70%, black);
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.02em;
}
/* Mock parity follow-up — the mock pairs every lock icon with its gold
   accent color (`Lock ... color="var(--ff-gold)"`), never the muted-gray
   tone plain body icons use. */
.badge .icon {
  width: 13px;
  height: 13px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}

/* G00063 — `CollectionGrid`'s `filterable` chip row (`filter-chips.js`,
   ADR-0308 Part B widget #3). Same visual language as the Client
   Portal's `.deal-room-type-filter`/`[data-active]` pair — one shared
   look for "a toggleable filter chip" across both marketing and portal
   surfaces — but its own class, since this component has no dependency
   on portal-only CSS. */
.collection-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: var(--radix-space-2);
}
.collection-filter-chip {
  padding: 0.35em 0.9em;
  border-radius: 999px;
  border: 1px solid var(--radix-border);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--radix-text-muted);
  text-decoration: none;
}
.collection-filter-chip:hover {
  border-color: var(--radix-primary);
  color: var(--radix-primary);
}
.collection-filter-chip[data-active='true'] {
  background: var(--radix-primary);
  border-color: var(--radix-primary);
  color: white;
}
/* ADR-0376 D11 — "Showing X of Y", live-updated by filter-chips.js. */
.collection-grid-counter {
  font-size: 0.85rem;
  color: var(--radix-text-muted);
  margin-bottom: var(--radix-space-2);
}

/* --------------------------------------------------------- portal chrome */
/* Dedicated Client Portal shell (ADR-0288, portalChrome.js) — replaces
   .radix-site-header/.radix-site-footer on portal routes only. Mirrors the
   mock's `.ff-shell`/`.ff-rail`/`.ff-topbar` grid: a fixed-width sticky dark
   rail beside a canvas column, collapsing to a horizontal top strip below
   860px (mock's own breakpoint). */
.portal-shell {
  display: grid;
  grid-template-columns: 252px minmax(0, 1fr);
  min-height: 100dvh;
}
.portal-rail {
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: var(--radix-space-2) var(--radix-space-1);
  background: linear-gradient(
    168deg,
    var(--radix-ink) 0%,
    var(--radix-deep) 62%,
    var(--radix-primary) 100%
  );
  color: white;
}
.portal-rail__brand {
  display: flex;
  flex-direction: column;
  padding: var(--radix-space-1) var(--radix-space-1) var(--radix-space-2);
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.09);
  margin-bottom: var(--radix-space-1);
}
.portal-rail__wordmark {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: white;
}
.portal-rail__tagline {
  font-size: 0.66rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
  margin-top: 0.2rem;
}
.portal-rail__nav {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.portal-navitem {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.75rem;
  border-radius: 9px;
  color: rgba(255, 255, 255, 0.82);
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 500;
  position: relative;
  transition:
    background 130ms ease,
    color 130ms ease;
}
.portal-navitem:hover {
  background: rgba(255, 255, 255, 0.07);
  color: white;
}
.portal-navitem[data-active='true'] {
  background: rgba(255, 255, 255, 0.12);
  color: white;
  font-weight: 600;
}
.portal-navitem[data-active='true']::before {
  content: '';
  position: absolute;
  /* Flush with .portal-rail's own left padding (0.5rem) — anything more
     negative bleeds past the rail's edge and gets clipped by its
     overflow-y:auto (which implicitly clips x too), showing only a sliver
     at the viewport edge instead of a clean accent bar next to the item. */
  left: -0.5rem;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 20px;
  border-radius: 0 3px 3px 0;
  background: var(--radix-accent);
}
.portal-navitem .icon {
  width: 17px;
  height: 17px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}
/* ADR-0376 D10 — grouped-section headings and a per-item count badge,
   `portal_nav`'s only field before this ADR was `links` with neither. */
.portal-rail__nav-heading {
  margin: var(--radix-space-2) 0 0.3rem;
  padding: 0 0.75rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
}
.portal-rail__nav-heading:first-child {
  margin-top: 0;
}
.portal-navitem-badge {
  margin-left: auto;
  padding: 0.05em 0.5em;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  color: white;
  font-size: 0.72rem;
  font-weight: 650;
}

/* Leaves the signed-in portal for the public marketing site — kept visually
   separate from .portal-rail__nav's in-app links (its own border + margin)
   so it doesn't read as another portal page, same distinction the mock's
   SideNav draws between its nav list and its "First Flight home" item. */
.portal-rail__exit {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.75rem;
  margin: 0 0 var(--radix-space-1);
  border-radius: 9px;
  color: rgba(255, 255, 255, 0.56);
  text-decoration: none;
  font-size: 0.8rem;
  font-weight: 500;
  border-bottom: 1px solid rgba(255, 255, 255, 0.09);
}
.portal-rail__exit:hover {
  background: rgba(255, 255, 255, 0.07);
  color: white;
}
.portal-rail__exit .icon {
  width: 15px;
  height: 15px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}

.portal-canvas {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.portal-topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--radix-space-2);
  padding: 0.75rem var(--radix-space-2);
  background: color-mix(in srgb, var(--radix-canvas) 88%, transparent);
  backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid var(--radix-border);
}
.portal-topbar__search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  max-width: 460px;
  padding: 0.5rem 0.9rem;
  border-radius: 999px;
  background: var(--radix-bg);
  border: 1px solid var(--radix-border);
  color: var(--radix-text-muted);
  transition: border-color 170ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
/* TopBar-search follow-up (2026-09-02) — the input itself has no visible
   border (theme below), so the focus affordance lives on the pill
   container via :focus-within rather than the usual :focus ring. */
.portal-topbar__search:focus-within {
  border-color: var(--radix-bright);
}
.portal-topbar__search .icon {
  width: 16px;
  height: 16px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: var(--radix-text-muted);
}
.portal-topbar__search input {
  border: none;
  outline: none;
  background: none;
  font: inherit;
  flex: 1;
  color: var(--radix-text);
}
.portal-topbar__actions {
  display: flex;
  align-items: center;
  gap: var(--radix-space-1);
  margin-left: auto;
}
.portal-topbar__icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  border: none;
  background: transparent;
  color: var(--radix-text-muted);
  cursor: not-allowed;
}
.portal-topbar__icon-btn {
  position: relative;
}
.portal-topbar__icon-btn .icon {
  width: 17px;
  height: 17px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}
/* ADR-0376 D10 — only ever rendered when `account.unread_count` is real;
   no notification backend exists yet, so this has never had data to show
   before now. */
.portal-topbar__badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--radix-secondary);
  color: white;
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}
.portal-topbar__account {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0;
}
.portal-topbar__account-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--radix-primary) 14%, transparent);
  color: var(--radix-primary);
  font-size: 0.72rem;
  font-weight: 700;
  flex: none;
}
.portal-topbar__account-text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}
.portal-topbar__account-name {
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--radix-text);
}
.portal-topbar__account-role {
  font-size: 0.72rem;
  color: var(--radix-text-muted);
}
.portal-topbar__signout {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 999px;
  border: 1px solid var(--radix-border);
  background: var(--radix-bg);
  color: var(--radix-text-muted);
  cursor: pointer;
}
.portal-topbar__signout:hover {
  color: var(--radix-primary);
  border-color: color-mix(in srgb, var(--radix-primary) 30%, var(--radix-border));
}
.portal-topbar__signout .icon {
  width: 15px;
  height: 15px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}
.portal-page {
  padding: var(--radix-space-3) var(--radix-space-2) var(--radix-space-5);
  max-width: 1320px;
  margin: 0 auto;
  width: 100%;
}

@media (max-width: 860px) {
  .portal-shell {
    grid-template-columns: 1fr;
  }
  .portal-rail {
    position: static;
    height: auto;
    flex-direction: row;
    overflow-x: auto;
    align-items: center;
  }
  .portal-rail__brand {
    display: none;
  }
  .portal-rail__nav {
    flex-direction: row;
  }
}

/* Split-screen sign-in gate (mirrors the mock's `SignInPage`). */
.portal-signin {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  min-height: 100dvh;
}
/* G00091 — own flat background rather than sharing `.radix-dark-band`
   (the hero/cta-band treatment): a plain two-stop gradient, no radial
   highlight, no grid-paper texture. Same ink/deep tokens, fewer layers, to
   match the mock's calmer sign-in art panel. */
.portal-signin__art {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--radix-space-4);
  color: white;
  background: linear-gradient(160deg, var(--radix-ink) 0%, var(--radix-deep) 100%);
}
.portal-signin__art-inner {
  text-align: center;
}
.portal-signin__art .portal-rail__wordmark {
  font-size: 1.3rem;
}
/* Wordmark + "Back to site" sit on one row (design-analysis follow-up,
   2026-09-02, mirrors the mock's SignInPage header) — .portal-signin__exit
   is absent entirely on a portal-only site, so this row collapses to just
   the wordmark, same as before. */
.portal-signin__art-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--radix-space-2);
}
.portal-signin__exit {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.8);
  text-decoration: none;
  padding: 0.3em 0.7em;
  border-radius: 999px;
  transition: background-color 140ms ease;
}
.portal-signin__exit:hover {
  background: rgba(255, 255, 255, 0.1);
}
/* G00091 — mirrors the mock's SignInPage badge (a Mantine `Badge` with a
   light gold background + icon) rather than plain text; scoped to this one
   class, not `.radix-dark-band` itself, so nothing shared with the public
   hero/cta-band treatment changes. */
.portal-signin__tagline {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  /* `--radix-secondary` always has a `:root` default (theme.css above), so
     this never actually needs a fallback — but per that block's own comment
     ("never hardcode a literal color for this role in shared block/CSS
     code"), a fallback here must be a neutral, not a brand color. Every
     other `var(--radix-*, #hex)` fallback in this file is a plain gray
     (#666/#ccc/#888); this one shipped with First Flight's own gold
     (#cfa622) instead, which is exactly the bleed that comment warns about. */
  color: var(--radix-secondary, #6b7280);
  background: color-mix(in srgb, var(--radix-secondary, #6b7280) 16%, transparent);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0.45rem 0.7rem;
  border-radius: 6px;
  margin-top: 0;
}
.portal-signin__tagline .icon {
  width: 13px;
  height: 13px;
  padding: 0;
  margin-bottom: 0;
  border-radius: 0;
  background: none;
  color: inherit;
}
.portal-signin__headline {
  color: white;
  font-size: clamp(2rem, 3.4vw, 3rem);
  letter-spacing: -0.025em;
  margin-top: var(--radix-space-2);
}
.portal-signin__subtext {
  color: rgba(255, 255, 255, 0.74);
  max-width: 46ch;
  margin: var(--radix-space-1) auto 0;
}
.portal-signin__stats {
  display: flex;
  justify-content: center;
  gap: var(--radix-space-3);
  margin-top: var(--radix-space-3);
  padding-top: var(--radix-space-2);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.portal-signin__stat-value {
  display: block;
  font-size: 1.3rem;
  font-weight: 700;
  color: white;
}
.portal-signin__stat-label {
  display: block;
  font-size: 0.72rem;
  color: rgba(255, 255, 255, 0.6);
  margin-top: 0.15rem;
}
.portal-signin__form {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--radix-space-3);
  background: var(--radix-canvas);
}
@media (max-width: 1080px) {
  .portal-signin {
    grid-template-columns: 1fr;
    min-height: auto;
  }
  .portal-signin__art {
    padding: var(--radix-space-4) var(--radix-space-2);
  }
}

/* ------------------------------------------------- portal utility pages */
/* register/magic-link/redeem (portalAuth.js) — single-purpose pages, not
   part of the main sign-in gate's split-screen treatment (portalChrome.js),
   just a centered card on the neutral canvas (ADR-0288). */
.portal-utility-page {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--radix-space-3);
  background: var(--radix-canvas);
}
.portal-utility-card {
  max-width: 420px;
  width: 100%;
  padding: var(--radix-space-3);
}
.portal-utility-card h1 {
  font-size: 1.4rem;
}
.portal-utility-card form {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-1);
  margin-top: var(--radix-space-2);
}
.portal-utility-card input:not([type='radio']):not([type='checkbox']) {
  padding: 0.65em 0.85em;
  border: 1px solid var(--radix-border);
  border-radius: 10px;
  font: inherit;
}
/* Registration's dynamic text/number/date/email/url/select fields wrap a
   label around their control (2026-09-02, matching every other product form
   that pairs a label above its control) — stack them rather than the
   inline default so the label text isn't crammed against the input. */
.portal-utility-card form > label:has(> input:not([type='checkbox']):not([type='radio'])),
.portal-utility-card form > label:has(> select) {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.85rem;
  color: var(--radix-text-muted);
}
.portal-utility-persona {
  border: 1px solid var(--radix-border);
  border-radius: 10px;
  padding: 0.7em 0.85em;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.portal-utility-persona legend {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--radix-text-muted);
  padding: 0;
}
.portal-utility-persona label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.92rem;
  cursor: pointer;
}
.portal-utility-card button {
  padding: 0.7em 1.4em;
  border: none;
  border-radius: 999px;
  background: var(--radix-primary);
  color: white;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--radix-shadow-card);
}
.portal-utility-card button:hover {
  background: color-mix(in srgb, var(--radix-primary) 85%, black);
}
.portal-utility-error {
  color: #b3261e;
  font-weight: 600;
}

/* ----------------------------------------------------- client portal shared */
/* Shared primitives for the 13 Client Portal block types (ADR-0288),
   modeled 1:1 on the firstflight-portal mock's `.ff-card`/`.ff-card--hover`/
   tinted-badge system (brand.css) — reusing this file's own tokens/easing
   rather than introducing a second design language. `.portal-card` is the
   one card shape every portal widget (dashboard tiles, deal-room/event
   cards, profile groups, document rows) builds on, same as `.ff-card` is
   the mock's one primitive underneath all of its Card-shaped UI. */
.portal-card {
  border: 1px solid var(--radix-border);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
  box-shadow: var(--radix-shadow-card);
}
.portal-card--hover {
  transition:
    transform 170ms cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 170ms ease,
    border-color 170ms ease;
}
.portal-card--hover:hover {
  transform: translateY(-3px);
  box-shadow: var(--radix-shadow-lift);
  border-color: color-mix(in srgb, var(--radix-primary) 30%, var(--radix-border));
}

/* Tinted badge — background/text both derived from one caller-supplied
   `--tone` custom property (set inline per instance, e.g.
   `style={{ '--tone': '#14746f' }}`), since the color varies per
   listing-type/interest-status and isn't known at CSS-author time. Falls
   back to --radix-secondary so a badge with no explicit tone still renders
   sensibly rather than transparent-on-transparent. Mirrors the mock's rule
   of never using a flat/filled badge color — always a `color-mix()` tint
   with the same hue as text. */
.badge-tinted {
  display: inline-block;
  padding: 0.25em 0.75em;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.02em;
  background: color-mix(in srgb, var(--tone, var(--radix-secondary)) 14%, white);
  color: color-mix(in srgb, var(--tone, var(--radix-secondary)) 70%, black);
}

/* Left accent stripe — the one place a listing/card gets a persistent
   "type" color-key outside its badge (mirrors the mock's `.ff-stripe` on
   OpportunityCard/deal-room cards). Also caller-toned via --tone. */
.portal-stripe {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  border-radius: var(--radix-radius) 0 0 var(--radix-radius);
  background: var(--tone, var(--radix-secondary));
}

.portal-empty-state {
  padding: var(--radix-space-3);
  text-align: center;
  color: var(--radix-text-muted);
  border: 1px dashed var(--radix-border);
  border-radius: var(--radix-radius);
}

/* --------------------------------------------------------- unsupported block */
.radix-unsupported-block {
  max-width: var(--radix-max-width);
  margin: var(--radix-space-2) auto;
  padding: var(--radix-space-2);
  border: 1px dashed var(--radix-border);
  color: var(--radix-text-muted);
  font-size: 0.9rem;
}

@media (max-width: 700px) {
  main > section {
    /* Mock parity follow-up — matches `.ff-band`'s own mobile breakpoint
       override (`padding-top/bottom: 3rem`) exactly. */
    padding: 3rem 1.15rem;
  }
  .block-hero {
    padding-top: 8rem;
    padding-bottom: 4rem;
  }
  .radix-site-header {
    padding-left: 1.15rem;
    padding-right: 1.15rem;
  }
}

/* ================================================ portal block styling */
/* Per-block styling for the 13 Client Portal block types (ADR-0288,
   Radix/src/blocks/index.js), reusing the `.portal-card`/`.badge-tinted`/
   `.portal-stripe` primitives declared above. Shared form chrome first
   (every portal write-form gets the same input/button treatment as
   `.block-form` already has for the marketing `form` block), then one
   section per block type. */
.block-portal-login form,
.block-portal-profile form,
.block-register-interest form,
.block-submit-listing form,
.listing-detail-rail form {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-1);
}
.block-portal-login input:not([type='hidden']),
.block-portal-profile input:not([type='hidden']),
.block-portal-profile select,
.block-submit-listing input:not([type='hidden']),
.block-submit-listing select,
.block-register-interest textarea,
.block-submit-listing textarea,
.listing-detail-rail textarea {
  padding: 0.65em 0.85em;
  border: 1px solid var(--radix-border);
  border-radius: 10px;
  font: inherit;
  width: 100%;
}
.block-portal-login button[type='submit'],
.block-portal-profile button[type='submit'],
.block-register-interest button[type='submit'],
.block-submit-listing button[type='submit'],
.listing-detail-actions button[type='submit'],
.deal-room-save-btn,
.event-list-card button,
.block-portal-account-menu button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  padding: 0.6em 1.2em;
  border: none;
  border-radius: 999px;
  background: var(--radix-primary);
  color: white;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--radix-shadow-card);
}
.block-portal-login button[type='submit']:hover,
.block-portal-profile button[type='submit']:hover,
.block-register-interest button[type='submit']:hover,
.block-submit-listing button[type='submit']:hover,
.listing-detail-actions button[type='submit']:hover,
.deal-room-save-btn:hover,
.event-list-card button:hover,
.block-portal-account-menu button:hover {
  background: color-mix(in srgb, var(--radix-primary) 85%, black);
}
.block-portal-login button .icon,
.block-portal-profile button .icon,
.block-register-interest button .icon,
.block-submit-listing button .icon,
.listing-detail-actions button .icon,
.deal-room-save-btn .icon,
.event-list-card button .icon,
.block-portal-account-menu button .icon {
  width: 16px;
  height: 16px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}

/* --- portal_login (split-screen form card, portalChrome.js's other half) */
.block-portal-login {
  width: 100%;
  max-width: 420px;
}
.portal-login-card {
  padding: var(--radix-space-3);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
  box-shadow: var(--radix-shadow-lift);
}
.portal-login-error {
  color: #b3261e;
  font-weight: 600;
}
.portal-login-card p {
  margin-top: var(--radix-space-1);
  font-size: 0.9rem;
}

/* --- portal_document_library (ADR-0376 D5 — styled file-card + dropzone,
   replacing a bare native <input type="file"> and a plain link list) --- */
.portal-document-list {
  list-style: none;
  margin: 0 0 var(--radix-space-2);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.portal-document-card {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--radix-border);
  border-radius: 10px;
  background: var(--radix-surface-2);
}
.portal-document-card-icon {
  display: inline-flex;
  flex: none;
  color: var(--radix-primary);
}
.portal-document-card-icon .icon {
  width: 22px;
  height: 22px;
  padding: 0;
  margin-bottom: 0;
  background: none;
}
.portal-document-card-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}
.portal-document-card-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.portal-document-card-meta {
  font-size: 0.78rem;
  color: var(--radix-text-muted);
}
.portal-document-scan-status {
  align-self: flex-start;
  margin-top: 0.2rem;
  padding: 0.1em 0.6em;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 650;
  text-transform: capitalize;
  background: color-mix(in srgb, var(--radix-text-muted) 16%, transparent);
  color: var(--radix-text-muted);
}
.portal-document-scan-status--clean {
  background: color-mix(in srgb, #1a7f4b 16%, transparent);
  color: #1a7f4b;
}
.portal-document-scan-status--infected,
.portal-document-scan-status--failed {
  background: color-mix(in srgb, #b3261e 16%, transparent);
  color: #b3261e;
}
.portal-document-card-action {
  flex: none;
  text-decoration: none;
  color: var(--radix-primary);
  font-weight: 600;
  font-size: 0.85rem;
}
.portal-document-card-action:hover {
  text-decoration: underline;
}
.portal-upload-dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: var(--radix-space-3) var(--radix-space-2);
  border: 2px dashed var(--radix-border);
  border-radius: 12px;
  text-align: center;
  color: var(--radix-text-muted);
  transition:
    border-color 130ms ease,
    background 130ms ease;
}
.portal-upload-dropzone .icon {
  color: var(--radix-primary);
}
.portal-upload-dropzone[data-dragging='true'] {
  border-color: var(--radix-primary);
  background: color-mix(in srgb, var(--radix-primary) 6%, transparent);
}
.portal-upload-dropzone-text {
  margin: 0;
  font-size: 0.9rem;
}
.portal-upload-dropzone-browse {
  color: var(--radix-primary);
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}
.portal-upload-dropzone-browse input[type='file'] {
  /* Visually hidden, not `display:none` — stays in the tab order and
     keeps `input.files` assignment (drag-and-drop) working identically to
     a real user-driven file pick. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}
.portal-upload-dropzone-filename {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--radix-ink);
}
.portal-upload-dropzone-filename:empty {
  display: none;
}
.portal-upload-submit {
  margin-top: 0.3rem;
  padding: 0.45em 1.3em;
  border-radius: 999px;
  border: none;
  background: var(--radix-primary);
  color: white;
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
}
.portal-upload-submit:hover {
  background: color-mix(in srgb, var(--radix-primary) 88%, black);
}

/* --- portal_account_menu --- */
.block-portal-account-menu {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--radix-space-1);
  flex-wrap: wrap;
}
.portal-account-label {
  font-weight: 600;
}

/* --- deal_room's real filter/sort (ADR-0289) --- */
.deal-room-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--radix-space-1);
  margin-top: var(--radix-space-1);
}
/* ADR-0376 D7 — root cause traced: ADR-0366 D3 added the persona row
   (`deal-room-persona-filters`/`deal-room-persona-filter`) alongside this
   type row but never gave it its own rules, so every persona chip rendered
   as a bare, unstyled `<a>` — no padding/gap/pill treatment — which is what
   read as one unspaced run-on string ("AllNon-Executive DirectorStart-up…").
   Both rows share one ruleset now; the type row's own look is unchanged. */
.deal-room-type-filters,
.deal-room-persona-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.deal-room-type-filter,
.deal-room-persona-filter {
  padding: 0.35em 0.9em;
  border-radius: 999px;
  border: 1px solid var(--radix-border);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--radix-text-muted);
  text-decoration: none;
}
.deal-room-type-filter:hover,
.deal-room-persona-filter:hover {
  border-color: var(--radix-primary);
  color: var(--radix-primary);
}
.deal-room-type-filter[data-active='true'],
.deal-room-persona-filter[data-active='true'] {
  background: var(--radix-primary);
  border-color: var(--radix-primary);
  color: white;
}
.deal-room-sort {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.deal-room-sort select {
  padding: 0.4em 0.7em;
  border: 1px solid var(--radix-border);
  border-radius: 8px;
  font: inherit;
  font-size: 0.85rem;
}
.deal-room-sort button {
  padding: 0.4em 0.9em;
  border: 1px solid var(--radix-border);
  border-radius: 999px;
  background: var(--radix-bg);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.deal-room-sort button:hover {
  border-color: var(--radix-primary);
  color: var(--radix-primary);
}
/* ADR-0376 D10 — the Live/Closed/All display toggle. `flex: 1 0 100%`
   forces it onto its own full-width row inside `.deal-room-filters`'s
   flex-wrap layout, so it reads as a view switch above the type/persona/
   sort row rather than crowding into that row's own space-between. */
.deal-room-status-filters {
  display: flex;
  flex: 1 0 100%;
  gap: 0.3rem;
  margin-bottom: 0.3rem;
}
.deal-room-status-filter {
  padding: 0.3em 0.85em;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 650;
  color: var(--radix-text-muted);
  text-decoration: none;
}
.deal-room-status-filter:hover {
  color: var(--radix-primary);
}
.deal-room-status-filter[data-active='true'] {
  background: color-mix(in srgb, var(--radix-primary) 12%, transparent);
  color: var(--radix-primary);
}
.deal-room-footer-note {
  margin-top: var(--radix-space-2);
  font-size: 0.82rem;
  color: var(--radix-text-muted);
}
/* Mock parity follow-up — Sector/Stage rows + results counter. Same chip
   look the type/persona rows above already use. */
.deal-room-sector-filters,
.deal-room-stage-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.4rem;
}
.deal-room-sector-filter,
.deal-room-stage-filter {
  padding: 0.35em 0.9em;
  border-radius: 999px;
  border: 1px solid var(--radix-border);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--radix-text-muted);
  text-decoration: none;
}
.deal-room-sector-filter:hover,
.deal-room-stage-filter:hover {
  border-color: var(--radix-primary);
  color: var(--radix-primary);
}
.deal-room-sector-filter[data-active='true'],
.deal-room-stage-filter[data-active='true'] {
  background: var(--radix-primary);
  border-color: var(--radix-primary);
  color: white;
}
.deal-room-counter {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--radix-text-muted);
}

/* --- deal_room / saved_listings (shared card, left accent stripe) --- */
.deal-room-grid {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-2);
  margin-top: var(--radix-space-2);
}
.deal-room-card {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--radix-space-3);
  padding: var(--radix-space-2) var(--radix-space-3) var(--radix-space-2) 1.4rem;
  overflow: hidden;
}
.deal-room-card-text {
  flex: 1;
  min-width: 0;
}
.deal-room-card-text h3 {
  margin: 0.3rem 0 0.2rem;
  font-size: 1.1rem;
}
.deal-room-card-subtitle {
  margin: 0 0 0.3rem;
  font-size: 0.82rem;
  color: var(--radix-text-muted);
}
.deal-room-meta {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-top: 0.4rem;
}
.deal-room-matched-factors {
  font-size: 0.85rem;
  color: var(--radix-text-muted);
  margin-top: 0.3rem;
}
.deal-room-card-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  flex: none;
}
.deal-room-view-link {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--radix-primary);
  text-decoration: none;
}

/* --- register_interest --- */
.block-register-interest textarea {
  min-height: 5rem;
  resize: vertical;
  margin-bottom: var(--radix-space-1);
}

/* --- submit_listing (ADR-0294 Phase 3) --- */
.submit-listing-error {
  color: #b3261e;
  font-weight: 600;
  margin: 0 0 var(--radix-space-1);
}
.submit-listing-success {
  padding: 0.7em 1em;
  border-radius: 10px;
  background: color-mix(in srgb, #1e7a3d 12%, transparent);
  color: #1e7a3d;
  font-weight: 600;
  margin: 0 0 var(--radix-space-1);
}

/* --- listing_detail (3-column layout, NDA blur, sticky action rail) --- */
/* Mock parity follow-up — the "Back to Deal Room" link every listing
   detail page has (this block previously had no fields at all to author
   one). */
.listing-detail-back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  margin-bottom: var(--radix-space-2);
  color: var(--radix-text-muted);
  font-size: 0.88rem;
  font-weight: 600;
  text-decoration: none;
}
.listing-detail-back-link:hover {
  color: var(--radix-primary);
}
.listing-detail-back-link .icon {
  width: 15px;
  height: 15px;
  padding: 0;
  margin: 0;
  background: none;
}
.listing-detail-header {
  border-radius: 18px;
  padding: var(--radix-space-4) var(--radix-space-3);
  margin-bottom: var(--radix-space-3);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--radix-space-3);
  flex-wrap: wrap;
}
.listing-detail-header-text {
  /* Without an explicit basis, a heading/paragraph's natural content width
     claims the full row and pushes .listing-detail-match-panel onto a
     second line despite .listing-detail-header's flex layout — this is
     what actually keeps the two side by side. */
  flex: 1 1 420px;
  min-width: 0;
}
.listing-detail-header-text h1 {
  margin-top: 0.5rem;
  font-size: clamp(1.6rem, 3vw, 2.2rem);
}
.listing-detail-header-text p {
  color: rgba(255, 255, 255, 0.78);
}
.listing-detail-match-panel {
  flex: none;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 14px;
  padding: var(--radix-space-2);
}
.listing-detail-layout {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--radix-space-3);
  align-items: start;
}
.listing-detail-main {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-2);
}
.listing-detail-main .portal-card {
  padding: var(--radix-space-3);
}
.listing-detail-criteria {
  width: 100%;
  border-collapse: collapse;
}
.listing-detail-criteria th {
  text-align: left;
  width: 40%;
  padding: 0.4rem 0;
  color: var(--radix-text-muted);
  font-weight: 600;
  vertical-align: top;
}
.listing-detail-criteria td {
  padding: 0.4rem 0;
  vertical-align: top;
}
.listing-detail-document-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--radix-border);
}
.listing-detail-document-row:last-child {
  border-bottom: none;
}
.listing-detail-document-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 9px;
  flex: none;
  background: color-mix(in srgb, var(--radix-primary) 12%, transparent);
  color: var(--radix-primary);
}
.listing-detail-document-icon.locked {
  background: color-mix(in srgb, var(--radix-accent) 16%, transparent);
  color: var(--radix-accent);
}
.listing-detail-document-icon .icon {
  width: 16px;
  height: 16px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}
.listing-detail-document-form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.5rem;
  flex: 1;
}
.listing-detail-nda-notice {
  font-size: 0.85rem;
  color: var(--radix-text-muted);
  margin: 0;
  max-width: 46ch;
}
.listing-detail-nda-consent {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--radix-text);
  cursor: pointer;
}
.listing-detail-nda-consent input {
  margin-top: 0.2em;
  flex: none;
}
.listing-detail-document-name {
  font-weight: 600;
  color: var(--radix-text);
  text-decoration: none;
}
a.listing-detail-document-name:hover {
  color: var(--radix-primary);
}
/* NDA-gated filename — the mock's "confidential" blur treatment. */
.portal-locked {
  filter: blur(4.5px);
  user-select: none;
}
/* Gold, not teal — mirrors the mock's NDA-unlock button, the one other
   place (besides match score) --radix-accent is used for a "premium
   moment" rather than a routine action. */
.listing-detail-document-form button {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5em 1.1em;
  border: none;
  border-radius: 999px;
  background: var(--radix-accent);
  color: white;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--radix-shadow-card);
}
.listing-detail-document-form button:hover {
  background: color-mix(in srgb, var(--radix-accent) 85%, black);
}
.listing-detail-rail {
  position: sticky;
  top: calc(var(--radix-space-2) + 60px);
}
/* Every other portal layout (shell, dashboard columns, sign-in split)
   already collapses to one column at a narrow viewport — this one didn't
   (design-analysis follow-up, 2026-09-02). Below 700px the main content and
   sticky action rail were sharing a row with no override at all. */
@media (max-width: 700px) {
  .listing-detail-layout {
    grid-template-columns: 1fr;
  }
  .listing-detail-rail {
    position: static;
    top: auto;
  }
}
.listing-detail-actions {
  padding: var(--radix-space-3);
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-1);
}
.listing-detail-interest-status {
  align-self: flex-start;
}

/* --- my_interests (status stepper) --- */
.my-interests-list {
  list-style: none;
  margin: var(--radix-space-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-2);
}
.my-interests-item {
  padding: var(--radix-space-2) var(--radix-space-3);
}
.my-interests-item h3 {
  margin: 0 0 0.4rem;
  font-size: 1rem;
}
.my-interests-stepper {
  display: flex;
  list-style: none;
  margin: var(--radix-space-2) 0 0;
  padding: 0;
  gap: 0.4rem;
}
.my-interests-stepper li {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  text-align: center;
  padding: 0.35rem 0.4rem;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: color-mix(in srgb, var(--radix-text-muted) 12%, transparent);
  color: var(--radix-text-muted);
}
.my-interests-stepper li[data-state='done'] {
  background: color-mix(in srgb, var(--radix-primary) 16%, transparent);
  color: var(--radix-primary);
}
.my-interests-stepper li[data-state='terminal'] {
  background: color-mix(in srgb, #b3261e 14%, transparent);
  color: #b3261e;
}
/* ADR-0376 D10 — the per-step transition date, from `status_history`. */
.my-interests-step-date {
  font-size: 0.6rem;
  font-weight: 500;
  text-transform: none;
  letter-spacing: normal;
  opacity: 0.75;
}

/* --- portal_profile (ADR-0372 D1/D2 — sectioned cards, replacing the old
   flat dt/dd list) --- */
.block-portal-profile {
  padding: var(--radix-space-3);
}
.profile-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--radix-space-3);
  align-items: start;
  margin-top: var(--radix-space-2);
}
@media (max-width: 720px) {
  .profile-layout {
    grid-template-columns: 1fr;
  }
}
.profile-rail {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-2);
}
.profile-groups {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-2);
}
/* ADR-0376 D10 — outline-top-right icon treatment, replacing the plain
   filled inline-left icon: `row-reverse` + `space-between` puts the icon
   (listed first among this element's children) on the right, and the
   border/no-fill makes it read as an outline rather than a solid glyph. */
.profile-card-head {
  display: flex;
  flex-direction: row-reverse;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: var(--radix-space-1);
}
.profile-card-head h3 {
  margin: 0;
  font-size: 0.95rem;
}
.profile-card-head .icon {
  width: 30px;
  height: 30px;
  padding: 6px;
  margin-bottom: 0;
  border: 1px solid var(--radix-border);
  border-radius: 8px;
  background: none;
  color: var(--radix-primary);
  flex: none;
}
.profile-group-card,
.profile-identity-card,
.profile-documents-card {
  padding: var(--radix-space-2) var(--radix-space-3);
}
.profile-identity-card {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-2);
}
.profile-identity-head {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}
.profile-avatar {
  flex: none;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 0.95rem;
  background: color-mix(in srgb, var(--radix-primary) 15%, transparent);
  color: var(--radix-primary);
}
.profile-identity-text {
  min-width: 0;
}
.profile-identity-name {
  font-weight: 650;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.profile-identity-persona {
  margin-top: 0.15rem;
  font-size: 0.82rem;
  color: var(--radix-text-muted);
}
/* Mock parity follow-up — membership badge + Persona/Location/Member
   since/Availability summary rows, both new siblings of the existing
   avatar+name+persona head. */
.profile-membership-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  margin-top: 0.3rem;
}
.profile-membership-badge .icon {
  width: 12px;
  height: 12px;
  padding: 0;
  margin: 0;
  background: none;
}
.profile-identity-rows {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: var(--radix-space-2);
  border-top: 1px solid var(--radix-border);
  margin: 0;
}
.profile-identity-row {
  display: flex;
  justify-content: space-between;
  gap: var(--radix-space-2);
}
.profile-identity-row dt {
  flex: none;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--radix-text-muted);
}
.profile-identity-row dd {
  margin: 0;
  font-size: 0.86rem;
  text-align: right;
}
.profile-strength-tip {
  display: flex;
  align-items: flex-start;
  gap: 0.5em;
  margin: var(--radix-space-2) 0 0;
  padding: 0.7em 0.85em;
  border-radius: 10px;
  background: color-mix(in srgb, var(--radix-secondary) 14%, transparent);
  font-size: 0.8rem;
  line-height: 1.5;
}
.profile-strength-tip .icon {
  flex: none;
  width: 15px;
  height: 15px;
  padding: 0;
  margin-top: 0.1em;
  background: none;
  color: color-mix(in srgb, var(--radix-secondary) 70%, black);
}
.profile-save-btn {
  align-self: flex-start;
}
/* ADR-0376 D10 — header eyebrow/actions row and an optional info banner;
   this block's header was a bare `<h2>` before this. */
.profile-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--radix-space-1);
}
.profile-header-actions {
  display: flex;
  gap: 0.5rem;
}
.profile-info-banner {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin-top: var(--radix-space-1);
  padding: var(--radix-space-2);
  border-radius: 10px;
  background: color-mix(in srgb, var(--radix-primary) 6%, transparent);
}
.profile-info-banner .icon {
  width: 18px;
  height: 18px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: var(--radix-primary);
  flex: none;
}
.profile-info-banner p {
  margin: 0;
  font-size: 0.88rem;
}
/* ADR-0376 D10 — "Profile strength" completeness card. */
.profile-strength-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--radix-canvas);
  overflow: hidden;
}
.profile-strength-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--radix-primary);
  transition: width 200ms ease;
}
.profile-strength-text {
  margin: 0.4rem 0 0;
  font-size: 0.8rem;
  color: var(--radix-text-muted);
}
/* ADR-0376 D10 — expertise (multi_select) values as filled pill chips,
   replacing a plain comma-joined string. */
.profile-chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.profile-chip {
  padding: 0.2em 0.75em;
  border-radius: 999px;
  background: color-mix(in srgb, var(--radix-primary) 12%, transparent);
  color: var(--radix-primary);
  font-size: 0.82rem;
  font-weight: 600;
}
.portal-profile-field {
  margin-bottom: var(--radix-space-2);
}
.portal-profile-field:last-child {
  margin-bottom: 0;
}
.portal-profile-field label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.3rem;
  font-size: 0.85rem;
}

/* ADR-0308 Part B, widget #2 — `multi_select` field editing
   (`multiSelectInput`, blocks/index.js). The `fieldset`/`label`/checkbox
   structure below is the zero-JS baseline: a plain, always-expanded
   scrollable checkbox list, fully functional with `chip-field.js` never
   loading. `.chip-field-chips`/`.chip-field-picker`/`.chip-field-search`
   only ever appear once the widget has actually run (it creates those
   elements itself, collapsing the fieldset behind a "+ Add" toggle to
   match the firstflight-portal mock's compact chip-row shape) — nothing
   here reserves space for them up front. */
.chip-field fieldset {
  border: 1px solid var(--radix-border);
  border-radius: 10px;
  padding: 0.5rem 0.7rem;
  margin: 0;
  max-height: 220px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.chip-field-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 400;
  font-size: 0.88rem;
  cursor: pointer;
}
.chip-field-option input {
  width: auto;
  flex: none;
}
/* The bug both these rules fix: an author rule setting `display` on an
   element always wins over the user-agent stylesheet's own
   `[hidden] { display: none }` — regardless of specificity — so any
   element this widget hides via `.hidden = true` needs its own explicit
   `[hidden] { display: none }` here or it stays visibly displayed
   regardless of the attribute. Bit twice: once when `chip-field.js`'s
   search filter correctly set `row.hidden` on non-matching options (the
   search box looked completely broken), and again when the combobox
   rework set `fieldset.hidden = true` to replace the checkbox list
   outright — caught the second time by a Playwright `isVisible()` check
   in the same verification pass, not by re-reading the CSS. */
.chip-field-option[hidden] {
  display: none;
}
.chip-field fieldset[hidden] {
  display: none;
}
.chip-field-picker {
  margin-top: 0.5rem;
}
.chip-field-picker[hidden] {
  display: none;
}
/* The combobox (widget #2 rework, 2026-09-03) — replaces the always-shown
   checkbox fieldset with type-to-filter + a floating option list, once the
   widget has actually loaded. `.chip-field-search`'s border/padding come
   from the generic `.block-portal-profile input:not([type='hidden'])`
   rule above; this only adds the positioning context for the dropdown. */
.chip-field-combo {
  position: relative;
}
.chip-field-listbox {
  position: absolute;
  z-index: 5;
  top: calc(100% + 0.3rem);
  left: 0;
  right: 0;
  margin: 0;
  padding: 0.3rem;
  list-style: none;
  background: var(--radix-bg);
  border: 1px solid var(--radix-border);
  border-radius: 10px;
  box-shadow: var(--radix-shadow-card);
  max-height: 220px;
  overflow-y: auto;
}
.chip-field-listbox[hidden] {
  display: none;
}
.chip-field-listbox-option {
  padding: 0.45em 0.6em;
  border-radius: 7px;
  font-size: 0.88rem;
  cursor: pointer;
}
.chip-field-listbox-option--active {
  background: color-mix(in srgb, var(--radix-primary) 10%, transparent);
  color: var(--radix-primary);
}
.chip-field-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
.chip-field-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.25em 0.4em 0.25em 0.7em;
  border-radius: 999px;
  background: color-mix(in srgb, var(--radix-primary) 12%, transparent);
  color: var(--radix-primary);
  font-size: 0.82rem;
  font-weight: 600;
}
.chip-field-chip-remove {
  border: none;
  background: none;
  color: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.2em;
}
.chip-field-chip-remove:hover {
  color: var(--radix-text);
}
/* The "+ Add" trigger — deliberately matches the firstflight-portal design
   mock's dashed, outlined "Add" pill (`ProfilePage.js`'s `ChipField`), the
   one piece of that mock this widget makes actually functional: clicking
   it reveals the search + option list instead of doing nothing. */
.chip-field-toggle {
  display: inline-flex;
  align-items: center;
  padding: 0.25em 0.7em;
  border-radius: 999px;
  border: 1px dashed var(--radix-border);
  background: none;
  color: var(--radix-text-muted, var(--radix-text));
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}
.chip-field-toggle:hover {
  border-color: var(--radix-primary);
  color: var(--radix-primary);
}
.chip-field-toggle--open {
  border-style: solid;
  border-color: var(--radix-primary);
  color: var(--radix-primary);
}

/* ADR-0306 Part C — a profile field an AI assistant can read, flagged at
   the point the account holder looks at it. Orange, not the site's own
   accent, so it never reads as "this field matters more" — only "this is
   a different kind of fact about this field" (mirrors the operator-side
   `CustomFieldsCard.js` badge's orange choice). */
.ai-visible-tag {
  display: inline-block;
  margin-left: 0.5em;
  padding: 0.05em 0.5em;
  border-radius: 999px;
  background: color-mix(in srgb, #b5650a 14%, transparent);
  color: #b5650a;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  vertical-align: middle;
}

/* --- dashboard --- */
/* ADR-0289 — stat tiles / top-matches / next-event, each its own
   `.portal-card` (unlike the old single-card layout) so the block degrades
   gracefully section-by-section when a piece of its data is absent. */
/* ADR-0376 D10 — hero banner, "Where things stand" pipeline, and the
   membership footer strip; all three render nothing when `membership`/
   `interest_pipeline` are absent (accounts with no tier set, or no
   interests registered). */
.dashboard-hero {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--radix-space-1);
  margin-bottom: var(--radix-space-2);
  padding: var(--radix-space-3);
  border: 1px solid var(--radix-border);
  border-radius: var(--radix-radius);
  background: var(--radix-bg);
  box-shadow: var(--radix-shadow-card);
}
.dashboard-hero h2 {
  margin: 0;
}
/* Mock parity — "Open the Deal Room"/"Update my profile", same `.cta`
   look every other block's action buttons already use. */
.dashboard-hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--radix-space-1);
  margin-top: var(--radix-space-1);
}
/* The nested Profile Strength card sits directly inside the hero on the
   mock; `.profile-strength-card`'s own `.portal-card` styling (used on
   `/profile`) would double a border/shadow already provided by
   `.dashboard-hero` itself, so it's flattened here instead. */
.dashboard-hero .profile-strength-card {
  border: none;
  box-shadow: none;
  padding: 0;
  margin-top: var(--radix-space-2);
  width: 100%;
}
.dashboard-membership-badge {
  padding: 0.25em 0.85em;
  border-radius: 999px;
  background: color-mix(in srgb, var(--radix-secondary) 16%, transparent);
  color: color-mix(in srgb, var(--radix-secondary) 70%, black);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: capitalize;
}
.dashboard-pipeline-list {
  list-style: none;
  margin: var(--radix-space-1) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.dashboard-pipeline-list li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.dashboard-pipeline-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
  background: var(--tone, var(--radix-primary));
}
.dashboard-pipeline-label {
  flex: 1;
}
.dashboard-pipeline-count {
  font-weight: 650;
  color: var(--radix-text-muted);
}
.dashboard-membership-strip {
  display: flex;
  align-items: center;
  gap: var(--radix-space-1);
  margin-top: var(--radix-space-2);
  padding: var(--radix-space-1) var(--radix-space-2);
  border-top: 1px solid var(--radix-border);
  font-size: 0.82rem;
  color: var(--radix-text-muted);
  text-transform: capitalize;
}
.dashboard-membership-strip span + span::before {
  content: '·';
  margin-right: var(--radix-space-1);
}
.dashboard-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--radix-space-2);
  margin-bottom: var(--radix-space-2);
}
.dashboard-stat-tile {
  padding: var(--radix-space-2);
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.dashboard-stat-tile-icon {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--radix-primary) 12%, transparent);
  color: var(--radix-primary);
}
.dashboard-stat-tile-icon .icon {
  width: 16px;
  height: 16px;
  padding: 0;
  margin-bottom: 0;
  background: none;
  color: inherit;
}
.dashboard-stat-tile-value {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--radix-text);
}
.dashboard-stat-tile-label {
  font-size: 0.8rem;
  color: var(--radix-text-muted);
}
.dashboard-columns {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: var(--radix-space-2);
  align-items: start;
}
@media (max-width: 860px) {
  .dashboard-columns {
    grid-template-columns: 1fr;
  }
}
.dashboard-top-matches,
.dashboard-activity {
  padding: var(--radix-space-3);
}
.dashboard-top-matches .deal-room-grid {
  margin-top: var(--radix-space-2);
}
.dashboard-activity-list {
  list-style: none;
  margin: var(--radix-space-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
}
.dashboard-activity-list li {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}
.dashboard-activity-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
  background: var(--tone, var(--radix-secondary));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tone, var(--radix-secondary)) 18%, transparent);
}
.dashboard-activity-time {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--radix-text-muted);
  flex: none;
}
.dashboard-next-event {
  display: flex;
  gap: 0.8rem;
  align-items: center;
  margin-top: var(--radix-space-2);
  padding-top: var(--radix-space-2);
  border-top: 1px solid var(--radix-border);
}
.dashboard-next-event-label {
  display: block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--radix-text-muted);
}
.dashboard-next-event h3 {
  margin: 0.1rem 0 0;
  font-size: 0.98rem;
}

/* --- event_list --- */
.event-list-subtitle {
  color: var(--radix-text-muted);
  margin-top: 0.2rem;
}
.event-list-propose-callout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--radix-space-1);
  margin-top: var(--radix-space-3);
  padding: var(--radix-space-2) var(--radix-space-2);
  border: 1px solid var(--radix-border);
  border-radius: 10px;
  background: var(--radix-surface-2);
}
.event-list-propose-callout p {
  margin: 0;
}
/* Mock parity follow-up — the mock stacks events as full-width rows
   (date square on the left, content filling the rest), not a card grid;
   `.event-list-card`'s own `display: flex` already matches that per-row
   shape, this just stops wrapping them into columns. */
.event-list-grid {
  display: flex;
  flex-direction: column;
  gap: var(--radix-space-2);
  margin-top: var(--radix-space-2);
}
.event-list-card {
  padding: var(--radix-space-2);
  display: flex;
  gap: var(--radix-space-2);
}
.event-list-card h3 {
  margin-top: 0;
}
.event-list-card-body {
  flex: 1;
  min-width: 0;
}
/* Day/month chip (ADR-0289) — absent entirely when `starts_at` is unset,
   same "render nothing rather than a fabricated placeholder" rule the rest
   of this file follows for missing data. */
.event-list-date-chip {
  flex: none;
  width: 52px;
  height: 52px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--radix-primary) 10%, transparent);
  color: var(--radix-primary);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1.1;
}
.event-list-date-chip-day {
  font-size: 1.2rem;
  font-weight: 700;
}
.event-list-date-chip-month {
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.05em;
}
.event-list-meta {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  font-size: 0.82rem;
  color: var(--radix-text-muted);
  margin: 0.3rem 0 0.7rem;
}
.event-list-meta span + span::before {
  content: '·';
  margin-right: 0.6rem;
}
.event-list-card button:disabled {
  cursor: not-allowed;
  background: var(--radix-text-muted);
  box-shadow: none;
}

/* Mock parity follow-up — eyebrow/subtitle on the three blocks that had no
   fields at all before (`saved_listings`, `my_interests`, `event_list`
   already had its own `.event-list-subtitle` need but never got one
   either). One shared rule since all three want the same look. */
.saved-listings-subtitle,
.my-interests-subtitle,
.event-list-subtitle {
  color: var(--radix-text-muted);
  margin: 0.2rem 0 var(--radix-space-2);
}

/* ---------------------------------------------------- ADR-0378 D2 — entrance
   motion. Zero-JS baseline: a [data-animate] element with no matching
   reveal.js run simply never gets [data-revealed] and stays at whatever
   these rules define as its resting, fully-visible state below — nothing
   here can leave content permanently hidden. `prefers-reduced-motion`
   collapses every transition to instant, matching the CSS spec's own
   "reduce" (not "no-preference") name — content still changes state, it
   just never animates getting there. */
[data-animate='fade-up'],
[data-animate='fade-in'],
[data-animate='animated-accordion'] {
  transition:
    opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
[data-animate='fade-up']:not([data-revealed]),
[data-animate='fade-in']:not([data-revealed]),
[data-animate='animated-accordion']:not([data-revealed]) {
  opacity: 0;
}
[data-animate='fade-up']:not([data-revealed]) {
  transform: translateY(24px);
}

/* stagger-reveal — the CONTAINER stays visible; its repeated-item children
   (`.grid`/`.tiers`/`.testimonials`, matching `items_grid`/`collection_grid`,
   `pricing_table`, and `testimonial_set` respectively) each fade in on the
   same curve, `:nth-child` giving each a small additional delay — plain
   CSS, no per-item JS or per-item addressing needed for the sequencing
   itself. Capped at the 6th child so a long list doesn't take seconds to
   finish revealing; every item past the 6th shares its delay. */
[data-animate='stagger-reveal'] > .grid > *,
[data-animate='stagger-reveal'] > .tiers > *,
[data-animate='stagger-reveal'] > .testimonials > * {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
[data-animate='stagger-reveal'][data-revealed] > .grid > *,
[data-animate='stagger-reveal'][data-revealed] > .tiers > *,
[data-animate='stagger-reveal'][data-revealed] > .testimonials > * {
  opacity: 1;
  transform: none;
}
[data-animate='stagger-reveal'] > .grid > *:nth-child(2),
[data-animate='stagger-reveal'] > .tiers > *:nth-child(2),
[data-animate='stagger-reveal'] > .testimonials > *:nth-child(2) {
  transition-delay: 90ms;
}
[data-animate='stagger-reveal'] > .grid > *:nth-child(3),
[data-animate='stagger-reveal'] > .tiers > *:nth-child(3),
[data-animate='stagger-reveal'] > .testimonials > *:nth-child(3) {
  transition-delay: 180ms;
}
[data-animate='stagger-reveal'] > .grid > *:nth-child(4),
[data-animate='stagger-reveal'] > .tiers > *:nth-child(4),
[data-animate='stagger-reveal'] > .testimonials > *:nth-child(4) {
  transition-delay: 270ms;
}
[data-animate='stagger-reveal'] > .grid > *:nth-child(5),
[data-animate='stagger-reveal'] > .tiers > *:nth-child(5),
[data-animate='stagger-reveal'] > .testimonials > *:nth-child(5) {
  transition-delay: 360ms;
}
[data-animate='stagger-reveal'] > .grid > *:nth-child(n + 6),
[data-animate='stagger-reveal'] > .tiers > *:nth-child(n + 6),
[data-animate='stagger-reveal'] > .testimonials > *:nth-child(n + 6) {
  transition-delay: 450ms;
}

/* marquee (logo_wall only) — a continuous loop, not an entrance, so it
   never gates on [data-revealed] or an observer at all; `blocks/index.js`'s
   `LogoWall` duplicates the logo row once (`.logos-marquee-dup`,
   `aria-hidden`) so `translateX(-50%)` has an identical second copy to loop
   into instead of a visible jump-cut. Zero-JS/no-marquee baseline never
   renders the duplicate at all, so nothing here applies to it. */
[data-animate='marquee'] .logos {
  display: flex;
  flex-wrap: nowrap;
  width: max-content;
  animation: radix-marquee 28s linear infinite;
}
.logos-marquee-dup {
  display: flex;
  gap: var(--radix-space-3);
}
@keyframes radix-marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

/* animated-accordion (faq only) — pure CSS, no JS at all: a native
   `<details>` element's own open/close already fights any attempt to
   transition its content's height reliably across browsers, so rather than
   fight that, this animates the one thing that CAN transition smoothly
   regardless — a custom chevron indicator, rotating on `[open]`. The
   content itself still opens/closes exactly as it natively always has,
   with or without this rule. */
[data-animate='animated-accordion'] details > summary {
  list-style: none;
  position: relative;
  padding-right: var(--radix-space-3);
  cursor: pointer;
}
[data-animate='animated-accordion'] details > summary::-webkit-details-marker {
  display: none;
}
[data-animate='animated-accordion'] details > summary::after {
  content: '';
  position: absolute;
  right: 2px;
  top: 50%;
  width: 0.5em;
  height: 0.5em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-65%) rotate(45deg);
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
[data-animate='animated-accordion'] details[open] > summary::after {
  transform: translateY(-35%) rotate(-135deg);
}

@media (prefers-reduced-motion: reduce) {
  [data-animate='fade-up'],
  [data-animate='fade-in'],
  [data-animate='animated-accordion'],
  [data-animate='stagger-reveal'] > .grid > *,
  [data-animate='stagger-reveal'] > .tiers > *,
  [data-animate='stagger-reveal'] > .testimonials > *,
  [data-animate='animated-accordion'] details > summary::after {
    transition-duration: 0.001ms !important;
    transition-delay: 0s !important;
  }
  [data-animate='marquee'] .logos {
    animation: none;
  }
}
