/*
  Oppward marketing site layout. Composes tokens.css + components.css primitives
  into the landing page / niche page sections. Section class prefixes match the
  component filenames (hero, problem, hiw, proof, niches, pricing, faq,
  site-footer) plus shared bits (nav, opp-card, hero-proof, signup, skel).
*/

/* ============================= nav ============================= */
.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  padding-block: var(--space-4);
  background: var(--bg);
  border-bottom: var(--border-w) solid var(--border-soft);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
}

.nav__brand {
  /* No explicit color: this class is reused as .site-footer__brand-mark
     inside the dark .band--alt footer (Footer.tsx / render.ts's
     renderFooter()) -- an own color declaration here would win over the
     inherited --text-on-alt regardless of the ancestor's specificity, the
     same bug class STATE.md documents for h1-h4 (found during this pass's
     screenshot sweep: the footer wordmark was rendering near-invisible,
     dark-on-dark, in the default light theme). Letting it inherit keeps it
     legible in both the plain nav (.band) and the footer (.band--alt). */
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 560;
  letter-spacing: var(--ls-tight);
  text-decoration: none;
}

.nav__brand-dot {
  color: var(--accent);
}

.nav__links {
  display: none;
  gap: var(--space-6);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text-muted);
}

.nav__links a {
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}

.nav__links a:hover {
  color: var(--text);
}

@media (min-width: 860px) {
  .nav__links {
    display: flex;
  }
}

.nav__cta {
  padding: 0.6em 1.2em;
  font-size: var(--fs-xs);
}

/* ============================= hero ============================= */
.hero {
  position: relative;
  overflow: hidden;
  padding-block: var(--space-9) var(--space-11);
}

.hero::before {
  content: "";
  position: absolute;
  inset: -20% -10% auto auto;
  width: 60vw;
  height: 60vw;
  max-width: 900px;
  max-height: 900px;
  background: radial-gradient(circle, color-mix(in srgb, var(--green-500) 30%, transparent) 0%, transparent 70%);
  pointer-events: none;
}

.hero::after {
  content: "";
  position: absolute;
  inset: auto auto -30% -10%;
  width: 40vw;
  height: 40vw;
  max-width: 600px;
  max-height: 600px;
  background: radial-gradient(circle, color-mix(in srgb, var(--rust-500) 14%, transparent) 0%, transparent 70%);
  pointer-events: none;
}

.hero__inner {
  position: relative;
  display: grid;
  gap: var(--space-8);
  align-items: start;
}

@media (min-width: 960px) {
  .hero__inner {
    /* Redesign note (founder rejection: "hero is not distributed correctly"):
       a near-even split forced the giant default h1 size into a narrow column
       and overflowed. The copy column now carries most of the width (these
       headlines are full sentences, up to ~90 characters, not two-word punch
       lines) and the two columns are vertically centered against each other
       instead of top-aligned, so a tall multi-line headline balances against
       the single proof card rather than stranding it at the top. */
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
    gap: var(--space-9);
    align-items: center;
  }
}

.hero__copy,
.hero__feed {
  /* Grid items default to min-width:auto, so an intrinsically wide descendant
     (the ticker's width:max-content marquee track) can force this track --
     and the whole page -- wider than the viewport. min-width:0 lets it shrink
     to the grid track and rely on internal overflow:hidden/wrapping instead. */
  min-width: 0;
}

.hero__copy {
  animation: rise var(--dur-slow) var(--ease-out) both;
}

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

.hero__eyebrow-icon {
  width: 2.25rem;
  height: 2.25rem;
}

.hero__eyebrow-icon svg {
  width: 1.15rem;
  height: 1.15rem;
}

/* The icon-badge already anchors the eyebrow row visually, so the kicker's own
   leading rule (components.css) would be a redundant second marker right next
   to it -- suppressed here only. */
.hero__eyebrow .kicker::before {
  display: none;
}

/* Redesign note (founder rejection: headline "overflown"). h1's default size
   is --fs-hero (up to 112px, clamp defined in tokens.css for short two/three-
   word headlines). Every real headline on this site is a full sentence -- the
   longest, "Federal buildings need janitorial contracts renewed constantly.
   Yours could be next.", is ~86 characters -- so it needs its own,
   dramatically smaller fluid scale plus a wrap width tuned in ch (which scales
   with the font-size itself, so this stays correct at every viewport) rather
   than inheriting a scale built for a much shorter string. Verified against
   the literal longest niche headline at both 1280px and 375px. */
.hero__headline {
  margin-top: var(--space-4);
  max-width: 21ch;
  font-size: clamp(2rem, 1.5rem + 2.2vw, 3.375rem);
  line-height: var(--lh-snug);
}

.hero__subhead {
  margin-top: var(--space-5);
  max-width: 42ch;
  font-size: var(--fs-md);
  color: var(--text-muted);
  line-height: var(--lh-relaxed);
}

.hero__copy .signup {
  margin-top: var(--space-7);
}

.hero__feed {
  animation: rise var(--dur-slow) var(--ease-out) 120ms both;
}

/* ---- post-email layout flip (Hero.tsx / SignupForm.tsx, signup funnel rebuild,
   2026-07-20, founder rejection: "too crowded" once the profile questions
   appeared alongside the full headline and the full proof panel). `.hero--flip`
   is added by Hero.tsx once the visitor moves past the email step. Desktop only
   changes the grid ratio (mobile already stacks hero__copy above hero__feed by
   DOM order, so the questions already lead there with no extra rule needed --
   see Hero.tsx's header comment). */
@media (min-width: 960px) {
  .hero--flip .hero__inner {
    grid-template-columns: minmax(0, 1.7fr) minmax(0, 0.3fr);
  }
}

/* Headline/subhead recede once the questions become the primary focus: shrunk,
   muted, and the subhead drops out entirely so nothing competes with the form
   for the visitor's attention. The eyebrow stays -- it's the one line of context
   ("Real listings, updated daily") worth keeping visible while answering. */
.hero__copy--recede .hero__headline {
  margin-top: var(--space-2);
  max-width: none;
  font-size: var(--fs-md);
  opacity: 0.62;
}

.hero__copy--recede .hero__subhead {
  display: none;
}

.hero__copy--recede .signup {
  margin-top: var(--space-5);
}

/* Proof panel shrinks to a smaller, secondary card once flipped -- see
   HeroFeed.tsx's `compact` prop (drops the "more open right now" list entirely)
   and the `.hero-proof--compact` / `.hero-feed--compact` rules below for the
   sizing itself. */
.hero__feed--compact {
  max-width: 22rem;
  margin-inline: auto;
}

@media (min-width: 960px) {
  .hero__feed--compact {
    max-width: none;
    margin-inline: 0;
  }
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ---- opportunity card ------------------------------------------------------
   Redesign note (founder rejection: "the whole cards look very unsexy",
   "cheap and cramped"). Rebuilt with a real spacing rhythm, the title as the
   card's dominant element (not the dollar figure), and a fixed visual ORDER
   applied via flex (title -> meta -> amount -> deadline) rather than relying
   on each surface's actual DOM order. `.opp-card__deadline` order:4 below
   (see that rule) is no longer emitted by any PUBLIC card as of the
   2026-07-20 deadline-removal pass -- OpportunityCard.tsx and the SSR grid
   template (web/functions/lib/seo/opportunityCard.ts) both stopped rendering
   it -- but the order rule is left in place because the signed-in app's
   GatedOpportunityCard.tsx still renders `.opp-card__deadline` and shares
   every class name in this block, so this single block still restyles that
   surface too. Both surfaces share every class name below, so this single
   block restyles the SSR grid at /opportunities/:niche too. */
.opp-card {
  /* No explicit color: inherits (a{color:inherit} in base.css), so it picks
     up --ink-on-paper correctly when nested in a .band--alt .card context. */
  display: flex;
  flex-direction: column;
  background: var(--surface-raised);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  text-decoration: none;
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-base) var(--ease-out), transform var(--dur-base) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out);
}

a.opp-card:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-md);
  transform: translateY(-3px);
}

.band--alt .opp-card {
  background: #fff;
  border-color: var(--paper-300);
  color: var(--ink-on-paper);
}

.band--alt .opp-card .text-muted,
.band--alt .opp-card__meta,
.band--alt .opp-card__deadline {
  color: var(--ink-on-paper-muted);
}

/* Same fixed-surface reasoning as the amount/estimate overrides below:
   .badge--scope uses the theme-flipping --border/--text-muted tokens by
   default, which read low-contrast against this card's hardcoded-white
   background in dark theme -- pin it to the fixed paper pair instead, same
   fix class as the rest of this .band--alt .opp-card block. */
.band--alt .opp-card .badge--scope {
  border-color: var(--paper-300);
  color: var(--ink-on-paper-muted);
}

/* --accent-strong is deliberately pale in dark theme (for contrast against a
   dark surface), but this card's background is hardcoded white regardless of
   theme -- so on dark theme the pale accent reads as low-contrast mint-on-white.
   Force the dark-green value here, since the surface never actually goes dark. */
.band--alt .opp-card__amount {
  color: var(--money-on-paper);
}

.opp-card__top {
  order: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Only add the gap below the badge row when it actually holds a visible badge.
   The SSR template always emits a kind ("Contract"/"Grant") badge, hidden just
   below via ".opp-card__top .badge--neutral" -- without this :has() guard, a
   card with no visible badge would still carry an empty gap above its title
   from the now-invisible kind badge. badge--deadline is kept in this selector
   for the signed-in app's GatedOpportunityCard.tsx (web/src/components/app/,
   not owned by this pass), which still shows a deadline-urgency chip -- that
   user opted in and wants it. PUBLIC cards (OpportunityCard.tsx, the SSR grid
   template) no longer render badge--deadline at all as of the 2026-07-18
   urgency-badge removal, so on those surfaces this selector matches only on
   badge--set-aside. badge--scope added (competitor-gap pass): every card now
   always renders a Federal/State & Local badge, so this selector always
   matches on at least that one -- effectively unconditional again, but kept
   as a :has() rather than reverted to a flat rule so a future badge that
   really is sometimes-absent doesn't have to re-add this guard. */
.opp-card__top:has(.badge--set-aside, .badge--deadline, .badge--scope) {
  margin-bottom: var(--space-5);
}

/* Cards keep at most a set-aside indicator (and, on the signed-in app's cards
   only, a deadline-urgency cue -- see the :has() comment above) -- the kind
   label is no longer a badge anywhere. OpportunityCard.tsx simply stops
   rendering it; this hides the equivalent markup in the SSR template, which
   this pass cannot edit. */
.opp-card__top .badge--neutral {
  display: none;
}

.opp-card__title {
  order: 1;
  margin-top: 0;
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 560;
  line-height: var(--lh-snug);
  /* Real SAM.gov titles occasionally contain one long unspaced token
     ("Plant/Generator", a PSC code fragment) with no natural word-break
     point -- overflow-wrap: break-word (base.css's global safety net
     against horizontal overflow) then breaks it at an arbitrary character,
     which reads as a typo ("Plant/Gener" / "ator") at this size. hyphens:
     auto asks the browser to break at a real syllable boundary with a
     visible hyphen instead, when it must break at all. */
  hyphens: auto;
}

.opp-card--feed .opp-card__title {
  font-size: var(--fs-base);
}

.opp-card--hero .opp-card__title {
  /* fs-2xl (up to 64px) is oversized for the hero card's constrained
     column width (~0.8fr of the hero grid) -- it made multi-word titles
     very tall and made the rare unbroken-token title (above) break more
     often. fs-xl reads as clearly the card's dominant element without
     that. */
  font-size: var(--fs-xl);
}

.opp-card__meta {
  order: 2;
  margin-top: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* A real amount is rare on an open contract (more common later via award
   estimates) -- when opportunity.amount is null, OpportunityCard.tsx renders
   no amount element at all, never a labeled-empty "Amount not listed" row.
   The SSR template (not editable here) still emits the old unlisted-span
   markup in that case; this hides the whole wrapping row via :has() so it
   never appears there either, without touching that file. */
.opp-card__amount {
  order: 3;
  margin-top: var(--space-4);
  font-size: var(--fs-md);
  line-height: 1;
  /* Dedicated money tone, not --accent-strong: a real dollar figure should
     outrank a button for attention (Ledger brand-study retune). */
  color: var(--money);
}

.opp-card__amount:has(.opp-card__amount--unlisted) {
  display: none;
}

.opp-card--feed .opp-card__amount {
  font-size: var(--fs-sm);
  margin-top: var(--space-3);
}

.opp-card--hero .opp-card__amount {
  font-size: var(--fs-lg);
  margin-top: var(--space-5);
}

.opp-card__amount--unlisted {
  /* Only ever rendered by the SSR template now (React no longer emits this
     span), and immediately hidden by the :has() rule above -- kept purely so
     that template's existing "Amount not listed" text node still exists in
     the DOM (its own test asserts on that string) without visually showing an
     empty amount slot. */
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-weight: 500;
  letter-spacing: normal;
}

/* Honest "typical past award" estimate, the award-estimate feature: shown ONLY on
   a card with no listed amount whose NAICS clears shared/estimate.ts's
   MIN_SAMPLE_COUNT (see OpportunityCard.tsx / lib/seo/opportunityCard.ts). Uses
   the same money token as a real amount (it IS a dollar figure, just an
   estimated one), but deliberately smaller/quieter than .opp-card__amount so a
   real number always outranks an estimate for attention, and never sits in the
   same slot as a real amount -- the two are mutually exclusive per card. The
   sample-count line is the honesty rule made visible ("estimated from N similar
   past awards"), not decorative. */
.opp-card__estimate {
  order: 3;
  margin-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.opp-card__estimate-figure {
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.2;
  color: var(--money);
}

.band--alt .opp-card__estimate-figure {
  /* Same dark-theme-on-white-card fix as .band--alt .opp-card__amount above. */
  color: var(--money-on-paper);
}

.opp-card--hero .opp-card__estimate-figure {
  font-size: var(--fs-md);
}

.opp-card__estimate-count {
  font-size: var(--fs-2xs);
}

.opp-card__deadline {
  order: 4;
  margin-top: var(--space-2);
  font-size: var(--fs-2xs);
}

/* NAICS enrichment (competitor-gap pass item 1): a small, quiet, single-line
   reference under everything else on the card -- real code + real title
   ("513210 - Software Publishers", see naicsTitles.ts), but deliberately the
   card's least prominent element (order:5, last, smallest text) since it's a
   reference detail, not the headline. Truncates with an ellipsis rather than
   wrapping/growing the card; the full label is still in the title attribute. */
.opp-card__naics {
  order: 5;
  margin-top: var(--space-1);
  font-size: var(--fs-2xs);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.opp-card--hero {
  padding: var(--space-7);
}

/* Belt and suspenders on top of .hero__feed--compact (Hero.tsx's grid-item
   wrapper): keeps HeroFeed's own compact rendering correctly sized even if this
   component is ever mounted outside that specific grid context. */
.hero-feed--compact {
  max-width: 22rem;
  margin-inline: auto;
}

.hero-feed__empty-title {
  margin-top: var(--space-4);
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 500;
}

.hero-feed__empty-body {
  margin-top: var(--space-3);
  font-size: var(--fs-sm);
  max-width: 34ch;
}

/* ---- skeleton loading state ---- */
.opp-card--skeleton {
  overflow: hidden;
}

.skel {
  background: linear-gradient(
    90deg,
    var(--border) 0%,
    color-mix(in srgb, var(--border) 40%, var(--surface)) 50%,
    var(--border) 100%
  );
  background-size: 200% 100%;
  animation: shimmer 1.6s ease-in-out infinite;
  border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
  .skel {
    animation: none;
  }
}

@keyframes shimmer {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

.skel--badge {
  width: 90px;
  height: 22px;
  margin-bottom: var(--space-5);
}

.skel--title {
  width: 85%;
  height: 22px;
  margin-bottom: var(--space-3);
}

.skel--meta {
  width: 50%;
  height: 14px;
}

/* ---- hero-proof: the hero's single proof panel -----------------------------
   Redesign note (founder rejection: "hero is not distributed correctly...
   a big card plus a redundant ticker below it, both showing the SAME
   listing"). Replaces the old separate opp-card + auto-scrolling marquee
   ticker with ONE bordered frame: the flagship OpportunityCard on top (full
   card treatment, border/radius/shadow removed here since the outer frame
   already provides it) and a quiet static list of other open listings below a
   hairline divider. No amount is ever shown in that list (title + place
   only, per the founder's note that a ticker must never show an amount
   placeholder), and no deadline either as of the 2026-07-20 deadline-removal
   pass (see HeroFeed.tsx's header comment), and the listings are always
   DIFFERENT from the flagship (see HeroFeed.tsx: "rest" already excludes
   it), so nothing can duplicate. */
.hero-proof {
  position: relative;
  background: var(--surface-raised);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.band--alt .hero-proof {
  background: #fff;
  border-color: var(--paper-300);
  color: var(--ink-on-paper);
}

.band--alt .hero-proof .text-muted {
  color: var(--ink-on-paper-muted);
}

/* Compact mode (post-email flip, HeroFeed.tsx's `compact` prop): smaller,
   quieter proof panel so it reads as secondary once the signup questions become
   the primary focus. Reduces the flagship card's own title/amount sizing rather
   than a CSS transform:scale (which would also shrink the border/shadow/spacing
   inconsistently) -- feed variant sizes already exist for exactly this ("feed"
   is deliberately smaller than "hero"), this reuses the same scale one step
   further down. */
.hero-proof--compact {
  box-shadow: none;
}

.hero-proof--compact .opp-card__title {
  font-size: var(--fs-base);
}

.hero-proof--compact .opp-card__amount {
  font-size: var(--fs-sm);
  margin-top: var(--space-3);
}

.hero-proof--compact .hero-proof__eyebrow {
  padding: var(--space-4) var(--space-5) 0;
}

.hero-proof--compact .opp-card--hero {
  padding-inline: var(--space-5);
  padding-bottom: var(--space-4);
}

.hero-proof .opp-card--hero {
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  padding-top: var(--space-3);
}

.hero-proof a.opp-card--hero:hover {
  transform: none;
  border-color: transparent;
  box-shadow: none;
}

.hero-proof__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-7) 0;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}

.hero-proof__live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

.hero-proof__more {
  border-top: var(--border-w) solid var(--border);
  padding: var(--space-2) var(--space-3);
}

.band--alt .hero-proof__more {
  border-color: var(--paper-300);
}

.hero-proof__more-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out);
}

.hero-proof__more-item:hover {
  background: var(--surface);
}

.band--alt .hero-proof__more-item:hover {
  background: var(--paper-100);
}

.hero-proof__more-title {
  /* min-width: 0 is required here, not optional: .hero-proof__more-item is a
     column-direction flex container, and a nowrap flex item's automatic
     min-width defaults to its own max-content size, which can exceed the
     stretched container width and force the whole card wider than the
     viewport -- the same class of bug STATE.md documents for the old ticker
     track. This is the fix, applied at the item that actually carries
     unpredictable-length text (a real SAM.gov/Grants.gov title). */
  min-width: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hero-proof__more-meta {
  min-width: 0;
  font-size: var(--fs-2xs);
}

.hero-proof--empty {
  padding: var(--space-7);
  min-height: 220px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  border-style: dashed;
}

.hero-proof--empty .hero-proof__eyebrow {
  padding: 0;
}

/* The skeleton state renders skel-- placeholders directly inside .hero-proof
   (no nested .opp-card--hero to supply padding), so it needs its own. */
.hero-proof.opp-card--skeleton {
  padding: var(--space-7);
}

/* ============================= signup form ============================= */
.signup__row {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

@media (min-width: 480px) {
  .signup__row {
    flex-direction: row;
  }
}

.signup__email-input {
  flex: 1;
}

.signup__submit {
  flex-shrink: 0;
}

.signup__fineprint {
  margin-top: var(--space-3);
  font-size: var(--fs-2xs);
  max-width: 46ch;
}

.signup--profile {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  background: var(--surface-raised);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  margin-top: var(--space-6);
}

.band--alt .signup--profile {
  background: #fff;
  border-color: var(--paper-300);
  color: var(--ink-on-paper);
}

.band--alt .signup--profile .text-muted,
.band--alt .signup--profile .field__label,
.band--alt .signup--profile .field__hint {
  color: var(--ink-on-paper-muted);
}

.signup__profile-lede {
  font-size: var(--fs-sm);
}

.signup__grid {
  display: grid;
  gap: var(--space-4);
}

@media (min-width: 480px) {
  .signup__grid {
    grid-template-columns: 1fr 1fr;
  }
}

.signup__actions {
  display: flex;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.signup--done {
  background: var(--accent-tint);
  border: var(--border-w) solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  margin-top: var(--space-6);
}

.signup__done-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  color: var(--accent-strong);
  margin-bottom: var(--space-2);
}

/* ---- multi-step progress indicator (signup funnel rebuild, 2026-07-20) ----- */
.signup__progress {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.signup__progress-list {
  display: flex;
  gap: var(--space-2);
}

.signup__progress-dot {
  width: 1.75rem;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--border);
  transition: background-color var(--dur-fast) var(--ease-out);
}

.band--alt .signup__progress-dot {
  background: var(--paper-300);
}

.signup__progress-dot.is-active {
  background: var(--accent);
}

.signup__progress-label {
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: var(--ls-snug);
}

/* Honest, non-hypey encouragement between/around steps -- per docs/BRAND.md's
   "confident, not hypey" voice rule, this is a plain statement of fact, not a
   nag or an exclamation point. */
.signup__nudge {
  font-size: var(--fs-2xs);
  margin-top: calc(-1 * var(--space-2));
}

/* ---- award showcase: "what you could have won" (signup funnel rebuild,
   2026-07-20, AwardShowcase.tsx) -------------------------------------------
   Real, closed USAspending awards for the visitor's chosen sector, sized to
   their revenue band. Deliberately plain: amount, what the work was, who won
   it -- never where (see web/functions/lib/routes/sector-awards.ts's header
   comment for the location-stripping rule this panel has nothing to violate,
   the API response itself carries no state/city field). */
.award-showcase {
  padding: var(--space-5);
}

.award-showcase__eyebrow {
  display: block;
  margin-bottom: var(--space-4);
}

.award-showcase__eyebrow::before {
  display: none;
}

.award-showcase__status {
  font-size: var(--fs-sm);
}

.award-showcase__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-height: 20rem;
  overflow-y: auto;
  padding-right: var(--space-2);
}

.award-showcase__item {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-3);
  row-gap: 2px;
  padding: var(--space-3) var(--space-2);
  border-bottom: var(--border-w) solid var(--border);
}

.band--alt .award-showcase__item {
  border-color: var(--paper-300);
}

.award-showcase__item:last-child {
  border-bottom: none;
}

.award-showcase__amount {
  grid-row: 1 / 3;
  align-self: center;
  font-size: var(--fs-md);
  color: var(--money);
  white-space: nowrap;
}

.award-showcase__title {
  font-size: var(--fs-sm);
  font-weight: 550;
  line-height: var(--lh-snug);
}

.award-showcase__recipient {
  font-size: var(--fs-2xs);
}

.award-showcase__note {
  margin-top: var(--space-3);
  font-size: var(--fs-2xs);
}

/* ============================= problem ============================= */
.problem {
  padding-block: var(--space-10);
}

.problem__inner {
  display: grid;
  gap: var(--space-6);
  grid-template-areas: "figure" "lead" "body";
}

.problem__figure {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  grid-area: figure;
}

.problem__stat {
  font-size: clamp(2.75rem, 2rem + 4vw, 4.5rem);
}

.problem__stat-label {
  max-width: 14ch;
  font-size: var(--fs-2xs);
  line-height: var(--lh-snug);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
}

@media (min-width: 860px) {
  .problem__inner {
    grid-template-columns: 0.9fr 1.5fr;
    grid-template-areas: "figure lead" "figure body";
    gap: var(--space-3) var(--space-9);
    align-items: start;
  }

  .problem__figure {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
    padding-top: var(--space-2);
  }

  .problem__stat {
    font-size: clamp(4rem, 2rem + 6vw, 6.5rem);
  }
}

.problem__lead {
  grid-area: lead;
}

.problem__lead h2 {
  margin-top: var(--space-4);
  max-width: 16ch;
}

.problem__body {
  grid-area: body;
}

.problem__body p {
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
}

.problem__body p + p {
  margin-top: var(--space-5);
}

/* ============================= how it works ============================= */
.hiw {
  padding-block: var(--space-10);
}

.hiw__title {
  margin-top: var(--space-4);
  max-width: 20ch;
}

.hiw__list {
  margin-top: var(--space-8);
  display: flex;
  flex-direction: column;
  position: relative;
}

.hiw__step {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-6);
  padding-block: var(--space-7);
  border-top: var(--border-w) solid var(--border);
  transition: padding-left var(--dur-base) var(--ease-out);
}

.hiw__step:last-child {
  border-bottom: var(--border-w) solid var(--border);
}

@media (min-width: 640px) {
  .hiw__step:nth-child(2) {
    padding-left: var(--space-9);
  }
  .hiw__step:nth-child(3) {
    padding-left: var(--space-11);
  }
  .hiw__step:hover {
    padding-left: calc(var(--space-3) + var(--step-indent, 0px));
  }
  .hiw__step:nth-child(1) {
    --step-indent: 0px;
  }
  .hiw__step:nth-child(2) {
    --step-indent: var(--space-9);
  }
  .hiw__step:nth-child(3) {
    --step-indent: var(--space-11);
  }
}

.hiw__num {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(2.5rem, 2rem + 2vw, 3.5rem);
  font-weight: 420;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  min-width: 1.6ch;
  transition: color var(--dur-base) var(--ease-out);
}

.hiw__step:hover .hiw__num {
  color: var(--accent-strong);
}

.hiw__step-copy {
  padding-top: var(--space-1);
}

.hiw__step-title {
  max-width: 32ch;
}

.hiw__step-copy p {
  margin-top: var(--space-2);
  max-width: 48ch;
}

/* ============================= proof feed ============================= */
.proof {
  padding-block: var(--space-10);
}

.proof__header h2 {
  margin-top: var(--space-4);
  max-width: 20ch;
}

.proof__sub {
  margin-top: var(--space-3);
  max-width: 44ch;
}

.proof__grid {
  margin-top: var(--space-8);
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}

.proof__empty {
  margin-top: var(--space-8);
  padding: var(--space-8);
  border: var(--border-w) dashed var(--border);
  border-radius: var(--radius-lg);
  text-align: center;
}

.proof__empty-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
}

.proof__cta {
  margin-top: var(--space-8);
}

/* ============================= niches ============================= */
.niches {
  padding-block: var(--space-10);
}

.niches__sub {
  margin-top: var(--space-3);
  max-width: 44ch;
}

.niches__grid {
  margin-top: var(--space-8);
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}

.niches__card {
  position: relative;
  display: block;
  padding: var(--space-6);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--text);
  transition: border-color var(--dur-base) var(--ease-out), background-color var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out);
}

.niches__card:hover {
  border-color: var(--accent);
  background: var(--accent-tint);
  transform: translateY(-3px);
}

.niches__card--active {
  border-color: var(--accent);
  background: var(--accent-tint);
}

.niches__card-icon {
  margin-bottom: var(--space-5);
  transition: transform var(--dur-base) var(--ease-out);
}

.niches__card:hover .niches__card-icon {
  transform: scale(1.08);
}

.niches__card-title {
  font-size: var(--fs-base);
  font-weight: 600;
  font-family: var(--font-body);
  padding-right: var(--space-6);
}

.niches__card-body {
  margin-top: var(--space-2);
  font-size: var(--fs-xs);
  line-height: var(--lh-relaxed);
}

.niches__card-arrow {
  position: absolute;
  top: var(--space-6);
  right: var(--space-5);
  color: var(--accent);
  font-size: var(--fs-md);
  transition: transform var(--dur-base) var(--ease-out);
}

.niches__card:hover .niches__card-arrow {
  transform: translateX(3px);
}

/* ============================= pricing ============================= */
.pricing {
  padding-block: var(--space-10);
}

.pricing__header {
  max-width: 46ch;
}

.pricing__sub {
  margin-top: var(--space-3);
  max-width: 44ch;
}

.pricing__grid {
  margin-top: var(--space-9);
  display: grid;
  gap: var(--space-5);
}

@media (min-width: 720px) {
  .pricing__grid {
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }

  /* The paid card reads as the considered choice, not just a taller box:
     nudged up on desktop so its top edge (and ribbon) breaks the shared
     baseline the free card sits on, per BRAND.md's asymmetry direction. */
  .pricing__card--paid {
    margin-top: calc(-1 * var(--space-5));
  }
}

.pricing__card {
  position: relative;
  display: flex;
  flex-direction: column;
}

.pricing__card--paid {
  border-color: var(--accent);
  box-shadow: var(--shadow-glow);
  overflow: hidden;
}

.pricing__card--paid::after {
  content: "";
  position: absolute;
  inset: -40% -30% auto auto;
  width: 55%;
  aspect-ratio: 1;
  background: radial-gradient(circle, color-mix(in srgb, var(--green-500) 16%, transparent) 0%, transparent 70%);
  pointer-events: none;
  z-index: 0;
}

.pricing__card--paid > * {
  position: relative;
  z-index: 1;
}

.pricing__plan-name {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 500;
}

.pricing__for {
  margin-top: var(--space-1);
  font-size: var(--fs-xs);
}

.pricing__price {
  margin-top: var(--space-5);
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
}

.pricing__price-amount {
  /* No explicit color on the free tier: inherits --ink-on-paper from the
     nested .band--alt .card. The paid tier below overrides to the accent
     color intentionally. */
  font-size: var(--fs-2xl);
}

.pricing__card--paid .pricing__price-amount {
  /* Fixed dark bank-green, not a theme-flipping token: this card's
     background is hardcoded white in both themes (see .band--alt .card),
     and this is a real dollar figure -- the dedicated money-on-paper tone. */
  color: var(--money-on-paper);
}

.pricing__price-note {
  margin-top: var(--space-1);
  font-size: var(--fs-xs);
}

.pricing__features {
  margin-top: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1;
}

.pricing__features li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  font-size: var(--fs-sm);
}

.pricing__features-icon {
  /* Fixed dark green, not the theme-flipping --accent: .pricing__card is a
     .band--alt .card, hardcoded white in both themes (see the tokens.css
     note on --ink-on-paper), so the icon needs the same fixed on-white
     green --money-on-paper uses, not a value tuned for whichever theme
     happens to be active. */
  flex-shrink: 0;
  width: 1.05em;
  height: 1.05em;
  margin-top: 0.2em;
  color: var(--money-on-paper);
}

.pricing__card .btn {
  margin-top: var(--space-7);
}

/* ============================= faq ============================= */
.faq {
  padding-block: var(--space-10);
}

.faq__inner {
  display: grid;
  gap: var(--space-7);
}

@media (min-width: 860px) {
  .faq__inner {
    /* Same 0.9fr/1.5fr ratio as .problem__inner -- one shared asymmetric
       pattern reused across the page reads as a deliberate system, not two
       one-off layouts. */
    grid-template-columns: 0.9fr 1.5fr;
    gap: var(--space-9);
    align-items: start;
  }

  .faq__lead {
    position: sticky;
    top: calc(var(--space-9) + 60px);
  }
}

.faq__lead h2 {
  margin-top: var(--space-4);
  max-width: 14ch;
}

.faq__lead-body {
  margin-top: var(--space-4);
  max-width: 32ch;
  line-height: var(--lh-relaxed);
}

.faq__lead-cta {
  margin-top: var(--space-5);
  padding-inline: 0;
}

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

.faq__item {
  border-top: var(--border-w) solid var(--border);
  padding-block: var(--space-5);
}

.faq__item:last-child {
  border-bottom: var(--border-w) solid var(--border);
}

.faq__question {
  cursor: pointer;
  list-style: none;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: var(--space-4);
  transition: color var(--dur-base) var(--ease-out);
}

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

.faq__question::-webkit-details-marker {
  display: none;
}

.faq__num {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

.faq__question-text {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 500;
}

.faq__toggle {
  position: relative;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  align-self: center;
}

.faq__toggle::before,
.faq__toggle::after {
  content: "";
  position: absolute;
  background: var(--accent);
  border-radius: 2px;
  transition: transform var(--dur-base) var(--ease-out);
}

.faq__toggle::before {
  inset: 6px 0;
  height: 2px;
}

.faq__toggle::after {
  inset: 0 6px;
  width: 2px;
}

.faq__item[open] .faq__toggle::after {
  transform: rotate(90deg);
}

.faq__answer {
  margin-top: var(--space-4);
  margin-left: calc(2.4ch + var(--space-4));
  font-size: var(--fs-sm);
  max-width: 58ch;
  line-height: var(--lh-relaxed);
}

/* ============================= footer ============================= */
.site-footer {
  padding-block: var(--space-9) var(--space-7);
}

.site-footer__top {
  display: grid;
  gap: var(--space-7);
}

@media (min-width: 720px) {
  .site-footer__top {
    grid-template-columns: 1.4fr 1fr 1fr 1fr;
  }
}

/* Rich link-tree variant (footer enrichment, task brief item 4): six items now
   (brand + Fields + Browse listings + Guides + Company + Legal) instead of the
   original four, so the fixed 4-column template above would either overflow or
   awkwardly wrap two columns onto their own half-empty row. auto-fit reflows the
   column count to whatever actually fits at the current width instead of a fixed
   breakpoint list -- at 375px this still falls through to the base single-column
   rule above (this rule only applies at >=720px), so mobile is unaffected. The
   brand block gets its own wider minimum (220px) so the wordmark/tagline never
   gets forced into as narrow a column as a link list. */
@media (min-width: 720px) {
  .site-footer__top--rich {
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  }

  .site-footer__top--rich .site-footer__brand {
    min-width: 220px;
  }
}

.site-footer__brand-mark {
  font-size: var(--fs-md);
}

.site-footer__tag {
  margin-top: var(--space-3);
  max-width: 32ch;
  font-size: var(--fs-sm);
}

.site-footer__col-title {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--text-on-alt-muted);
  margin-bottom: var(--space-3);
}

.site-footer__col ul {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.site-footer__col a {
  text-decoration: none;
  font-size: var(--fs-sm);
  color: var(--text-on-alt);
  opacity: 0.85;
}

.site-footer__col a:hover {
  opacity: 1;
  color: var(--accent-strong);
}

.site-footer__rule {
  margin-block: var(--space-7);
  border-color: var(--border);
}

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

.site-footer__disclaimer {
  font-size: var(--fs-2xs);
  max-width: 68ch;
  line-height: var(--lh-relaxed);
  color: var(--text-on-alt-muted);
}

.site-footer__copyright {
  font-size: var(--fs-2xs);
}

/* ============================= 404 ============================= */
.notfound {
  position: relative;
  overflow: hidden;
  min-height: 100dvh;
  display: flex;
  align-items: center;
}

.notfound::before {
  content: "";
  position: absolute;
  inset: -10% -20% auto auto;
  width: 55vw;
  height: 55vw;
  max-width: 800px;
  max-height: 800px;
  background: radial-gradient(circle, color-mix(in srgb, var(--green-500) 22%, transparent) 0%, transparent 70%);
  pointer-events: none;
}

.notfound__inner {
  position: relative;
  padding-block: var(--space-9);
}

.notfound__brand {
  display: inline-block;
}

.notfound__digits {
  margin-top: var(--space-8);
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 440;
  font-size: clamp(5rem, 3rem + 9vw, 10rem);
  line-height: 0.85;
  letter-spacing: var(--ls-tight);
  color: var(--accent);
  opacity: 0.5;
}

.notfound__digit-off {
  display: inline-block;
  /* One digit visibly knocked off its baseline -- an editorial "broken"
     accent rather than a stock icon-and-message 404 pattern. */
  transform: translateY(-0.1em) rotate(-6deg);
  color: var(--alert);
}

.notfound__title {
  margin-top: var(--space-5);
  max-width: 20ch;
}

.notfound__body {
  margin-top: var(--space-4);
  max-width: 42ch;
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
}

.notfound__actions {
  margin-top: var(--space-7);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.notfound__quicklinks {
  margin-top: var(--space-9);
  padding-top: var(--space-6);
  border-top: var(--border-w) solid var(--border);
}

.notfound__quicklinks-label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
}

.notfound__quicklinks-list {
  margin-top: var(--space-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.notfound__quicklinks-list a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.5em var(--space-4);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-pill);
  text-decoration: none;
  font-size: var(--fs-xs);
  font-weight: 600;
  transition: border-color var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}

.notfound__quicklinks-list a:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* ============================= hero map ============================= */
/* Decorative US dot-grid map with live opportunity pins (HeroMap.tsx). Sits in the
   hero's visual column. The map's own rotating card carries the live proof, so the
   standalone HeroFeed proof panel is hidden while the map is showing (the
   coordination rule at the end of this block) to avoid the "card plus redundant
   ticker showing the SAME listing" the founder rejected. */
.hero-map {
  position: relative;
  width: 100%;
}

/* Post-email flip: the map recedes so the profiling questions lead (Hero.tsx /
   HeroFeed.tsx compact convention). Hidden, not unmounted, so no data re-fetch. */
.hero-map--compact {
  display: none;
}

.hero-map__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.hero-map__loading,
.hero-map--loading {
  min-height: 16rem;
}

.hero-map__skel {
  width: 100%;
  height: 16rem;
  border-radius: var(--radius-lg);
}

/* Faint backdrop constellation of every state, so the lit pins read as the US
   rather than a scatter of dots. Dim on purpose: the real opportunities are the
   bright pins, not this. */
.hero-map__backdrop-dot {
  fill: var(--text-muted);
  opacity: 0.28;
}

.hero-map__dot {
  fill: var(--accent);
}

.hero-map__pin--featured .hero-map__dot {
  fill: var(--money);
}

.hero-map__pulse {
  fill: none;
  stroke: var(--money);
  stroke-width: 0.5;
  transform-box: fill-box;
  transform-origin: center;
  animation: hero-map-pulse 2.6s var(--ease-out) infinite;
}

@keyframes hero-map-pulse {
  0% {
    opacity: 0.55;
    transform: scale(0.5);
  }
  70% {
    opacity: 0;
    transform: scale(2.6);
  }
  100% {
    opacity: 0;
    transform: scale(2.6);
  }
}

.hero-map__connector {
  stroke: var(--accent);
  stroke-width: 0.4;
  stroke-dasharray: 1.3 1.3;
  opacity: 0.6;
}

/* Floating opportunity card. On the hero's `.band` (not `.band--alt`) surface, so
   it uses the theme-aware raised-surface + money tokens directly, matching the
   hero-proof card on the same band -- not the hardcoded-white `.band--alt` path. */
.hero-map__card {
  position: absolute;
  right: 2%;
  bottom: 3%;
  width: min(66%, 20rem);
  display: block;
  padding: var(--space-4);
  background: var(--surface-raised);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  color: var(--text);
  text-decoration: none;
  transition: transform var(--dur-base) var(--ease-out), border-color var(--dur-base) var(--ease-out);
}

a.hero-map__card:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
}

.hero-map__card-top {
  margin-bottom: var(--space-3);
}

.hero-map__card-title {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.hero-map__card-meta {
  margin-top: var(--space-2);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.hero-map__card-amount {
  margin-top: var(--space-3);
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--money);
}

.hero-map__card-amount--estimate {
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* Pre-email, only one of {map, standalone proof feed} shows: when the map has real
   pins its `.hero-map` element exists and this hides the adjacent feed; when the
   map has no mappable data it renders nothing, this selector finds no `.hero-map`
   to match, and the feed's own loading/empty panel shows through instead. Scoped to
   the non-flipped column so the post-email compact feed (which replaces the hidden
   map) is unaffected. */
.hero__feed:not(.hero__feed--compact) .hero-map + .hero-feed {
  display: none;
}

@media (max-width: 600px) {
  /* Card drops below the map instead of overlapping it, so it can never push past
     the viewport edge on a narrow screen. Connector points at nothing useful once
     the card is below, so it goes away too. */
  .hero-map__card {
    position: static;
    width: 100%;
    margin-top: var(--space-4);
    box-shadow: var(--shadow-md);
  }

  .hero-map__connector {
    display: none;
  }
}

/* ============================= what you could win ============================= */
.win {
  padding-block: var(--space-10);
}

.win__header h2 {
  margin-top: var(--space-4);
  max-width: 22ch;
}

.win__sub {
  margin-top: var(--space-3);
  max-width: 54ch;
}

.win__groups {
  margin-top: var(--space-8);
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

.win__group-title {
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: 600;
  margin-bottom: var(--space-4);
}

/* Horizontal, snap-scrolling row. overflow-x is contained here (not on the page):
   the flex children are flex:0 0 auto so the row is wider than its container, and
   this element scrolls internally instead of widening the page (body already has
   overflow-x:hidden as a backstop). */
.win__row {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  padding-bottom: var(--space-3);
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}

.win__card {
  flex: 0 0 auto;
  width: min(19rem, 80vw);
  scroll-snap-align: start;
}

.win__card .opp-card {
  height: 100%;
}

.win__cta {
  margin-top: var(--space-8);
}

/* ============================= sources strip ============================= */
.sources {
  padding-block: var(--space-7);
}

.sources__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  text-align: center;
}

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

.sources__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

.sources__mark {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: var(--ls-snug);
  color: var(--text-on-alt);
}

.sources__note {
  font-size: var(--fs-xs);
}

.sources__disclaimer {
  font-size: var(--fs-2xs);
  max-width: 62ch;
  line-height: var(--lh-normal);
}

/* ============================= closing cta ============================= */
.closing-cta {
  padding-block: var(--space-11);
}

.closing-cta__inner {
  max-width: var(--content-narrow);
  margin-inline: auto;
  text-align: center;
}

.closing-cta__headline {
  font-size: var(--fs-2xl);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
}

.closing-cta__sub {
  margin-top: var(--space-5);
  margin-inline: auto;
  max-width: 48ch;
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
}

.closing-cta__button {
  margin-top: var(--space-7);
}
