/*
 * sites/health — shell, tokens and the lead form's own CSS.
 *
 * The design is secure.reviewyourmortgage.com.au's, ported value for value
 * from a SingleFile save of its /step-1 (18 Sep 2026). Every number in this
 * file that has a counterpart there is that page's number, not an
 * approximation: the tokens, the type scale, the card geometry, the option
 * buttons, the logo grid and the footer. Where this file departs, the comment
 * on the rule says so and why.
 *
 * What that page does NOT contain is noted where it comes up. It was saved on
 * its first step, so the later steps' controls (text inputs past `.input`, the
 * submit button's resting state, the code boxes) have no resting rules in it
 * and are derived from its tokens here.
 *
 * Component styles live in each .astro file's scoped <style>. What is here is
 * what those cannot own: the page background, the tokens, the reset, and the
 * lead form (whose markup is a locked section, so its CSS is a manual review
 * like the section itself).
 */

/* ── Font ──────────────────────────────────────────────────────────────
 *
 * Open Sans, which is what the reference page sets as --font-body. The file
 * is the latin subset Google serves for it, lifted from that save rather than
 * re-fetched, so the bytes are the ones that page actually rendered with.
 *
 * ONE @font-face for every weight: it is a variable font, and the same file
 * served 400, 500, 600, 700 and 800 there. A face per weight would download
 * identical bytes five times.
 *
 * `font-display: optional` rather than `swap`: a face that misses its block
 * period sits out this pageview entirely instead of arriving late and
 * reflowing the page. The preload in BaseLayout is what makes `optional`
 * usually win the race.
 */
@font-face {
  font-family: 'Open Sans';
  src: url('/fonts/open-sans-var.woff2') format('woff2-variations');
  font-weight: 300 800;
  font-stretch: 100%;
  font-style: normal;
  font-display: optional;
}

/*
 * ── Tokens ────────────────────────────────────────────────────────────
 *
 * Every colour is a SURFACE and the FOREGROUND that is legible on it, named
 * in pairs the way Tailwind's own semantic layer does: `--background` /
 * `--foreground`, `--card` / `--card-foreground`, and so on. The values are
 * Tailwind's palette, because the reference design's already were
 * (`#0f172a` is slate-900, `#64748b` slate-500, `#e5e7eb` gray-200,
 * `#dc2626` red-600); what was missing was the pairing.
 *
 * The pairing is the point, and it is a contrast rule rather than a naming
 * convention. Picking a background used to leave "and what colour is the text
 * on it?" as a judgement somebody made per rule, which is how a footer ends up
 * at 3.75:1: nothing in the stylesheet said which foreground belonged to that
 * ground. Now the answer travels with the surface, every pair below is
 * measured, and the site's CLAUDE.md carries the table.
 *
 * So: never set a `background` without setting the matching `-foreground`, and
 * never reach across pairs for a colour because it looks about right.
 *
 * ── Where the two brand colours go ────────────────────────────────────
 *
 * The logo is two colours, so the page is. The split is by ROLE, which is what
 * makes it applicable to the next thing somebody adds rather than a list to
 * memorise:
 *
 *   --accent  (#0D61AA, the wordmark's blue) is the CHROME. The header and the
 *             footer: the bands that frame the page and that a visitor never
 *             acts on.
 *   --primary (#2F8F5B, the reference design's green) is the FOREGROUND. The
 *             headline's accent line, the hero's ticks, and everything inside
 *             the card that a visitor acts on.
 *
 * Put a new element on whichever side of that line it falls: does it frame the
 * page, or is it the page?
 */
:root {
  /* The page, and the card on it. Both white here, and named separately
     anyway: they are two surfaces that happen to agree, and the card is the
     one that would move if the page ever took a tint. */
  --background: #ffffff;
  --foreground: #0f172a;              /* slate-900, 17.85:1 on --background */
  --foreground-soft: #334155;         /* slate-700, 10.35:1. Subheads and body */

  --card: #ffffff;
  --card-foreground: #0f172a;

  /* A dimmer surface, and the dim copy that goes on --background. The two are
     deliberately not a pair in the Tailwind sense: --muted-foreground is the
     fine print anywhere, and it clears 4.5:1 on both white and --muted. */
  --muted: #f8fafc;                   /* slate-50 */
  --muted-foreground: #64748b;        /* slate-500, 4.76:1 on white, 4.55:1 on --muted */

  /* ACTION. The reference design's green, and the whole of the card. */
  --primary: #2F8F5B;
  --primary-foreground: #ffffff;      /* 4.04:1, and the one pair under the bar */
  --primary-hover: #26794C;
  --primary-active: #1E6440;
  --primary-soft: #F1F8F3;
  --primary-tint: #F1F8F3;
  --primary-accent: #5CC089;
  --primary-border: #C2E2CF;
  --primary-rgb: 47, 143, 91;
  --headline-accent: var(--primary);
  /* The box a mobile browser paints over whatever was just tapped. Built from
     --primary-active so a press reads as one gesture whether the browser
     paints the tint, the button paints its own :active, or both. */
  --press-tint: rgba(30, 100, 64, .28);

  /* CHROME. The wordmark's blue, and the header and footer only. */
  --accent: #0D61AA;
  --accent-foreground: #ffffff;       /* 6.35:1 */
  --accent-foreground-soft: #dbeafe;  /* blue-100, 5.20:1. The footer's fine print */
  --accent-rgb: 13, 97, 170;

  --destructive: #dc2626;             /* red-600, 4.83:1 on --background */
  --destructive-foreground: #ffffff;
  --destructive-surface: #fdf0f0;
  --destructive-on-surface: #a81f27;  /* 6.54:1 on --destructive-surface */

  --border: #e5e7eb;                  /* gray-200 */
  --border-soft: #eef2f7;
  --input: #e5e7eb;                   /* a field's resting border */
  --ring: #2F8F5B;                    /* the focus ring, everywhere */

  --star: #fbbf24;                    /* amber-400, 3.80:1 on --accent */

  /* The progress bar's two stripe stops and the trough they run in. Two
     elements paint this gradient (the bar and the processing rows), so it is
     a token rather than a literal written twice. */
  --track: #eef2f7;
  --stripe-a: #3fb6a0;
  --stripe-b: #5cc089;

  --font-body: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, Helvetica, Arial, sans-serif;

  --radius-btn: 12px;
  --radius-card: 20px;
  --radius-lg: 24px;
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-md: 0 4px 14px rgba(15, 23, 42, 0.06);
  --shadow-lg: 0 18px 60px rgba(var(--primary-rgb), 0.1), 0 2px 8px rgba(15, 23, 42, 0.04);
}

* { box-sizing: border-box; }

/*
 * ONE element owns the page background, and it is html. Every other background
 * on this site is a deliberate surface whose edges are decided by the layout,
 * never by how much content happens to sit in it.
 *
 * The reference paints html and body the same white, which is invisible only
 * because the two agree. A second painter is the shape of the grey-then-white
 * flash in the root CLAUDE.md, so body is transparent here.
 */
html {
  background: var(--background);
  /* `only light`, not bare `light`. Bare `light` states which schemes the page
     supports; it does not opt out of Chrome on Android's Auto Dark Theme,
     which is then free to re-map every surface and discard the ratios rated
     by hand above. A declaration that reads like the opt-out and is not it is
     worse than none, because it looks handled. */
  color-scheme: only light;
  /* Without this a tap flashes the browser's own highlight over the CTA, in
     Android's holo blue, and no rule on the element can change it: the
     browser paints that box, not the page. It inherits, so this one
     declaration covers everything tappable on the site. See sites/CLAUDE.md,
     "A press is never the browser's own blue". */
  -webkit-tap-highlight-color: var(--press-tint);
  /* Removes double-tap-to-zoom, and nothing else: pinch-zoom and panning are
     untouched, so WCAG 1.4.4 is untouched. Effective touch-action is the
     intersection down the chain, so this cannot be re-enabled by a
     descendant, and NEVER `none`, which would take pinch-zoom with it.
     What it buys: without it every tap on a control waits out the browser's
     double-tap window before it acts. */
  touch-action: manipulation;
  /* iOS inflates text after an orientation change unless told not to. Both
     forms, so a future unprefixing does not silently drop the guard. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  padding: 0;
  background: transparent;
  color: var(--foreground);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection { background: var(--primary-soft); color: var(--foreground); }

/*
 * A long press on a control raises iOS's selection handles and, over an image,
 * its "Save Image" sheet: a system UI over the funnel, on a gesture the
 * visitor made to press a button.
 *
 * KEYED ON CONTROL TYPES, never blanket on html. Both properties inherit, so a
 * blanket rule makes every future block of copy unselectable until somebody
 * remembers, and Chrome renders no highlight for a programmatic
 * `Selection.addRange()` inside a `user-select: none` subtree, which would
 * break a copy-to-clipboard fallback rather than merely annoy someone. Nothing
 * on this site builds a Range today; keying it is what keeps that true.
 *
 * `label` is what covers the option tiles and the fund logo inside them, since
 * both properties inherit. `[role="button"]` and `summary` are a fence for
 * markup nobody has written here yet.
 */
button,
[role="button"],
summary,
label,
.combo__opt,
.otp__boxes > span {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

img { max-width: 100%; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/*
 * A visible ring on everything focusable. The reference has no global ring at
 * all, which is a gap rather than a decision to copy: with the policy links
 * styled to look like the copy around them, the ring is what a keyboard user
 * has left. Never `outline: none` on any of these.
 */
:focus-visible {
  outline: 3px solid var(--ring);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ── The page shell ────────────────────────────────────────────────────── */

/*
 * The reference is one centred column: topbar, hero, card, logo band, footer.
 * There is no two-up split and no page-wide container; each band carries its
 * own max-width, which is why those live on the sections rather than here.
 */
.page {
  min-height: 100dvh;
  display: grid;
  grid-template-rows: auto 1fr auto; /* topbar / body / footer */
}
.page__body { min-width: 0; }

/* The reference wraps the logo band and the space under the card in this. */
.page__below {
  padding: 0 24px 48px;
  margin-top: 60px;
}

/* ── Buttons ───────────────────────────────────────────────────────────── */

/*
 * The reference was saved on a step with no submit button, so it carries
 * `.btn`'s hover, active and disabled states but no resting rule. The resting
 * state here is `.opt`'s, which those three states match exactly (same
 * background token, same inset-shadow shape, same lift); only the radius
 * differs, and it takes --radius-btn, which is the token the reference
 * defines and never otherwise uses.
 */
.btn {
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  border-radius: var(--radius-btn);
  background: var(--primary);
  color: var(--primary-foreground);
  padding: 20px 24px;
  min-height: 60px; /* past the 44px touch minimum */
  font-family: inherit;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -0.005em;
  cursor: pointer;
  box-shadow: inset 0 -4px #00000029, 0 4px 12px rgba(var(--primary-rgb), 0.2);
  transition: background 0.15s ease, transform 0.12s ease, box-shadow 0.18s ease;
}
.btn:active:not(:disabled) {
  transform: translateY(1px);
  background: var(--primary-active);
  box-shadow: inset 0 -2px #0000002e, 0 3px 8px rgba(var(--primary-rgb), 0.24);
}
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* ── The funnel card ───────────────────────────────────────────────────── */

.funnel-card {
  max-width: 1040px;
  margin: 40px auto 0;
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 44px 64px 30px;
  position: relative;
  overflow: hidden;
  scroll-margin-top: 84px;
}
/* The moving hairline across the top of the card. */
.funnel-card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4px;
  background: linear-gradient(90deg, var(--primary) 0%, var(--primary-accent) 50%, var(--primary) 100%);
  background-size: 200% 100%;
  animation: gradMove 3s linear infinite;
}
@keyframes gradMove { to { background-position: 200% 0; } }

@media (max-width: 900px) {
  .funnel-card { padding: 36px 36px 24px; margin-top: 32px; border-radius: 20px; }
}
@media (max-width: 640px) {
  /*
   * The side margins take the safe-area inset, because BaseLayout sets
   * `viewport-fit=cover`: the layout viewport then extends under the notch,
   * and in landscape on a notched handset the inset is around 44px, which is
   * wider than this margin plus the card's own padding. Every option tile and
   * every submit button is full width inside this card, so without it the left
   * edge of the control the ad was bought to get tapped sits under the notch.
   */
  .funnel-card {
    margin: 12px max(10px, env(safe-area-inset-right)) 0 max(10px, env(safe-area-inset-left));
    padding: 20px 16px 16px;
    border-radius: 16px;
    scroll-margin-top: 64px;
  }
}

/* ── Progress ──────────────────────────────────────────────────────────── */

.progress-wrap { max-width: none; margin: 0 0 36px; padding: 0; }
@media (max-width: 900px) { .progress-wrap { margin-bottom: 32px; } }
@media (max-width: 640px) { .progress-wrap { margin-bottom: 24px; } }

.progress {
  height: 14px;
  border-radius: 999px;
  background: var(--track);
  overflow: hidden;
  position: relative;
  box-shadow: inset 0 1px 2px #0f172a0a;
}
.progress > i {
  display: block;
  height: 100%;
  background-image: linear-gradient(45deg, var(--stripe-a) 25%, var(--stripe-b) 25%, var(--stripe-b) 50%, var(--stripe-a) 50%, var(--stripe-a) 75%, var(--stripe-b) 75%);
  background-size: 24px 24px;
  animation: stripeMove 1.2s linear infinite;
  transition: width 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
  border-radius: 999px;
  box-shadow: 0 1px 3px rgba(var(--primary-rgb), 0.3);
}
@keyframes stripeMove { 0% { background-position: 0 0; } to { background-position: 24px 0; } }

/* ── The stage, and the panes inside it ────────────────────────────────── */

.stage {
  max-width: none;
  margin: 0;
  padding: 0;
  min-height: 300px;
  animation: stageFade 0.35s ease-out both;
}
@media (max-width: 640px) { .stage { min-height: 240px; } }
@keyframes stageFade {
  0% { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

/*
 * Every pane fills the stage's reserved height, so advancing from a six-tile
 * question to a two-tile one does not shorten the card and jump the band
 * below it up the page.
 */
.pane { display: flex; flex-direction: column; justify-content: center; min-height: inherit; }
/*
 * `hidden` is an attribute, and the UA stylesheet implements it as
 * `display: none`. ANY author `display` rule outranks that, so the line above
 * silently un-hides every pane and the whole funnel renders as one long page.
 * Any rule that gives a toggled element a `display` needs this line with it.
 */
.pane[hidden] { display: none; }

.q {
  font-size: 30px;
  font-weight: 700;
  text-align: center;
  margin: 0 0 30px;
  color: var(--foreground);
  letter-spacing: -0.015em;
  line-height: 1.25;
}
@media (max-width: 900px) { .q { font-size: 24px; } }
@media (max-width: 640px) { .q { font-size: 23px; margin-bottom: 24px; line-height: 1.3; } }

/*
 * A pane's sub-line. The reference has no equivalent on the step it was saved
 * on, so this is derived: --muted-foreground on --background measures 4.76:1,
 * which clears 4.5:1 for copy this size.
 */
.q-sub {
  margin: -18px 0 26px;
  text-align: center;
  font-size: 15px;
  font-weight: 600;
  color: var(--muted-foreground);
  line-height: 1.45;
}
@media (max-width: 640px) { .q-sub { margin-top: -14px; margin-bottom: 20px; } }

/*
 * The always-expanded explainer under a question ("why we need your age").
 * No reference equivalent. --foreground-soft on --muted measures 9.90:1.
 */
.help {
  margin: 0 0 22px;
  padding: 14px 16px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-btn);
  background: var(--muted);
  text-align: center;
}
.help__label { margin: 0 0 4px; font-size: 14px; font-weight: 700; color: var(--foreground); }
.help__text { margin: 0; font-size: 14px; line-height: 1.5; color: var(--foreground-soft); }

/* ── Options ───────────────────────────────────────────────────────────── */

.choice { border: 0; margin: 0; padding: 0; min-width: 0; }

.grid {
  display: grid;
  gap: 14px;
  grid-auto-rows: 1fr;
  align-items: stretch;
}
.grid.cols-1 { grid-template-columns: 1fr; }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
/* No reference equivalent: its widest option grid is two up. Four is the
   states question, which collapses to two rather than one on a phone so eight
   of them do not become eight full-width rows. */
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 640px) {
  .grid.cols-2 { grid-template-columns: 1fr; }
  .grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
}

/*
 * The reference's options are <button>s. Here they are <label>s wrapping a
 * real radio, so the group is required, announced and keyboard-operable and
 * no option ships `checked`. Everything paintable is the reference's.
 *
 * Contrast: #fff on --primary (#2F8F5B) measures 4.04:1. 16px at weight 800
 * is not "large text" (that starts at 18.66px bold), so this is under WCAG
 * 2.2 AA 1.4.3's 4.5:1. It is the reference page's own pairing and is kept
 * deliberately; --primary-hover (#26794C) measures 5.35:1 and is the one-token
 * fix if the finding is taken.
 */
.opt {
  position: relative;
  appearance: none;
  -webkit-appearance: none;
  background: var(--primary);
  color: var(--primary-foreground);
  border: 0;
  border-radius: 6px;
  padding: 20px 24px;
  font-size: 16px;
  font-weight: 800;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 14px;
  transition: background 0.15s ease, transform 0.12s ease, box-shadow 0.18s ease;
  box-shadow: inset 0 -4px #00000029, 0 4px 12px rgba(var(--primary-rgb), 0.2);
  text-align: left;
  line-height: 1.25;
  min-height: 66px;
  font-family: inherit;
  letter-spacing: -0.005em;
}
.opt:active {
  transform: translateY(1px);
  background: var(--primary-active);
  box-shadow: inset 0 -2px #0000002e, 0 3px 8px rgba(var(--primary-rgb), 0.22);
}
@media (max-width: 640px) {
  .opt { padding: 16px; min-height: 60px; font-size: 15.5px; gap: 12px; }
}

/* Four up is a set of two- and three-letter labels, so they centre. */
.grid.cols-4 .opt { justify-content: center; text-align: center; padding-inline: 8px; }
.grid.cols-4 .opt-check { display: none; }

/*
 * The radio itself is the control and stays in the accessibility tree and in
 * the tab order; the label is what is painted. `appearance: none` rather than
 * `display: none`, because a hidden input is not focusable.
 */
.opt input[type='radio'] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  cursor: pointer;
}
.opt__label { position: relative; pointer-events: none; }

.opt .ico-wrap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--primary-foreground);
  flex-shrink: 0;
  transition: background 0.15s ease;
}

/*
 * A fund mark sits on its own white chip inside the option, exactly as a bank
 * mark does on the reference. Full colour, never greyscale: a visitor on this
 * question is scanning for the one brand they already hold.
 */
.opt .ico-wrap.bank-chip {
  background: var(--card);
  width: 52px;
  height: 31px;
  border-radius: 6px;
  padding: 3px;
  overflow: hidden;
}
.opt .ico-wrap.bank-chip img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
@media (max-width: 640px) { .opt .ico-wrap { width: 34px; height: 34px; } }
@media (max-width: 640px) { .opt .ico-wrap.bank-chip { width: 48px; height: 29px; } }

.opt .opt-check {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.45);
  margin-left: auto;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: transparent;
  background: transparent;
  transition: all 0.15s ease;
}
.opt .opt-check svg { width: 12px; height: 12px; opacity: 0; transition: opacity 0.15s ease; }

/*
 * Selected. The reference was saved with nothing chosen, so it carries the
 * resting state only; this is the state its resting rules are written to be
 * filled in by (the circle already reserves the tick's box and animates
 * `all`), painted with its own tokens.
 */
.opt:has(input:checked) { background: var(--primary-active); }
.opt:has(input:checked) .opt-check { background: var(--primary-foreground); border-color: var(--primary-foreground); color: var(--primary); }
.opt:has(input:checked) .opt-check svg { opacity: 1; }
.opt:has(input:focus-visible) { outline: 3px solid var(--ring); outline-offset: 3px; }

/* `:user-invalid` never matches a radio the visitor has not touched, which is
   exactly the state an unanswered question is in, so the cue comes from the
   aria-invalid the module sets instead. */
.opt:has(input[aria-invalid='true']) { box-shadow: inset 0 -4px #00000029, 0 0 0 2px var(--destructive); }

/* ── Text fields ───────────────────────────────────────────────────────── */

.input {
  width: 100%;
  /* Without this iOS Safari paints its own inner shadow and rounding UNDER the
     declared border, so the field renders as two nested boxes. */
  appearance: none;
  -webkit-appearance: none;
  padding: 18px 16px;
  background: var(--background);
  border: 1.5px solid var(--input);
  border-radius: var(--radius-btn);
  /* 16px minimum on every control that takes focus: under it, iOS Safari
     zooms the page on focus and leaves it zoomed. */
  font-size: 16px;
  font-family: inherit;
  color: var(--foreground);
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.input:focus {
  outline: none;
  border-color: var(--ring);
  box-shadow: 0 0 0 4px rgba(var(--primary-rgb), 0.14);
}
.input::placeholder { color: var(--muted-foreground); }
.input[aria-invalid='true'] { border-color: var(--destructive); }

/* Chrome paints an autofilled field yellow otherwise. The long transition
   defers its highlight rather than covering it with an inset box, which would
   also hide the code boxes below. */
.input:-webkit-autofill,
.input:-webkit-autofill:hover,
.input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--foreground);
  transition: background-color 600000s 0s, color 600000s 0s;
}
/* The unprefixed selector in its OWN block, never folded into the comma list
   above: one unknown selector invalidates the whole rule, so a browser that
   has only the prefixed form would lose the prefixed declarations too. This is
   the Firefox for Android path. */
.input:autofill,
.input:autofill:hover,
.input:autofill:focus {
  -webkit-text-fill-color: var(--foreground);
  color: var(--foreground);
  transition: background-color 600000s 0s, color 600000s 0s;
}

/* The long tail of the fund question: same control, its own presentation. */
.other-select {
  margin-top: 16px;
  min-height: 62px;
  cursor: pointer;
  font-weight: 800;
  letter-spacing: -0.005em;
  text-align: center;
  text-align-last: center;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%232F8F5B' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 16px center;
  box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.14);
}
.other-select:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px rgba(var(--primary-rgb), 0.18);
}

/* ── The combobox over the long-tail select ────────────────────────────
 *
 * Painted only under `[data-embr-combobox-ui]`, which @embr/form adds when it
 * has actually built the surface. A bundle that fails to load leaves the
 * native select untouched and styled by `.other-select` above, which is the
 * same guard as `.otp__boxes[data-mirrored]` and for the same reason: an
 * enhancement must not be able to leave a visitor with less than they started
 * with. See packages/form/src/combobox.ts.
 */
.combo { position: relative; margin-top: 16px; }

.combo__input {
  min-height: 62px;
  cursor: pointer;
  font-weight: 800;
  letter-spacing: -0.005em;
  text-align: center;
  padding-right: 44px;
  box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.14);
}
.combo__input::placeholder { color: var(--foreground); opacity: 1; font-weight: 800; }

/* The chevron is the affordance that says this opens, so it is the one part
   that has to survive the native dropdown going away. */
.combo__caret {
  position: absolute;
  top: 31px;
  right: 16px;
  width: 20px;
  height: 20px;
  margin-top: -10px;
  pointer-events: none;
  background: no-repeat center / 20px 20px
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%232F8F5B' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  transition: transform 0.15s ease;
}
.combo:has([aria-expanded='true']) .combo__caret { transform: rotate(180deg); }

/*
 * In flow, not absolutely positioned. `.funnel-card` is `overflow: hidden` for
 * its top hairline, and this control sits near the bottom of its pane, so a
 * floating menu would be clipped across the options rather than off screen.
 * Growing the card is a change the visitor asked for; a clipped list is not.
 */
.combo__list {
  list-style: none;
  margin: 8px 0 0;
  padding: 6px;
  max-height: 280px;
  overflow-y: auto;
  /* Contained, or reaching the end of the options carries the scroll on into
     the page behind the card. */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  border: 1.5px solid var(--input);
  border-radius: var(--radius-btn);
  background: var(--card);
  color: var(--card-foreground);
  box-shadow: var(--shadow-md);
}
.combo__list[hidden] { display: none; }

.combo__opt {
  display: flex;
  align-items: center;
  /* Past the 44px touch minimum, which a native option row is not obliged to
     meet and this one is. */
  min-height: 44px;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
}
.combo__opt[hidden] { display: none; }
/* `.is-active` is the keyboard's position and `:hover` (in the hover block at
   the foot of this file) is the pointer's, and they are deliberately the same
   paint: there is one active option at a time. */
.combo__opt.is-active {
  background: var(--primary-soft);
}
.combo__opt[aria-selected='true'] {
  background: var(--primary);
  color: var(--primary-foreground);
}

.combo__empty {
  margin: 8px 0 0;
  padding: 12px 14px;
  border-radius: var(--radius-btn);
  background: var(--muted);
  color: var(--muted-foreground);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
}
.combo__empty[hidden] { display: none; }

.field { margin-top: 0; }
.row { display: flex; gap: 14px; }
.row .field { flex: 1 1 0; min-width: 0; }
.field + .field { margin-top: 14px; }
/* The stacking gap is for fields ABOVE one another. In a two-up row the second
   field is still an adjacent sibling, so the same rule pushed it 14px down and
   the name inputs sat on different lines. `gap` already spaces a row. */
.row .field + .field { margin-top: 0; }
@media (max-width: 460px) {
  .row { flex-direction: column; gap: 14px; }
  /* Stacked again, so the row's own gap is what separates them. */
  .row .field + .field { margin-top: 0; }
}

/* The gap between the last control and the button that acts on it.
   Deliberately larger than the gap between the controls themselves: at the
   same spacing the button reads as one more option. */
.choice > .btn, .field > .btn, .pane > .btn { margin-top: 28px; }
[data-embr-step-form='otp'] .btn { margin-top: 20px; }

/*
 * The age field's visible label, joined to its input as one control.
 *
 * A grid rather than a flex row, so the error element can be the input's NEXT
 * SIBLING in the DOM (which is what @embr/form's audit checks, and what puts
 * the message under the field that failed) while still spanning its own line
 * underneath both.
 */
.numfield { display: grid; grid-template-columns: auto 1fr; align-items: stretch; }
.numfield__label {
  display: flex;
  align-items: center;
  padding: 0 18px;
  background: var(--primary);
  color: var(--primary-foreground);
  font-weight: 800;
  font-size: 16px;
  letter-spacing: -0.005em;
  white-space: nowrap;
  border-radius: var(--radius-btn) 0 0 var(--radius-btn);
}
.numfield .input { border-radius: 0 var(--radius-btn) var(--radius-btn) 0; border-left: 0; }
.numfield .field-err { grid-column: 1 / -1; }

/* ── Errors ────────────────────────────────────────────────────────────── */

.field-err {
  margin: 8px 0 0;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.45;
  /* --destructive on --background measures 4.83:1. 13.5px bold is not "large text". */
  color: var(--destructive);
}
.field-err:not(:empty)::before { content: '⚠'; font-size: 14px; margin-right: 6px; }
.field-err--form {
  margin-top: 16px;
  padding: 12px 14px;
  border-radius: 10px;
  background: var(--destructive-surface);
  color: var(--destructive-on-surface);
}

/* ── The verification code ─────────────────────────────────────────────── */

/* One variable, so the boxes and the input that covers them cannot disagree
   about how tall the row is. */
.otp { --otp-box-h: 66px; position: relative; margin-top: 4px; }
.otp__boxes { display: flex; gap: 12px; justify-content: center; }
/*
 * Painted only under `data-mirrored`, which @embr/form sets once it is driving
 * the boxes. Without that guard, a bundle that fails to load leaves empty
 * boxes with invisible text behind them; with it, the same failure leaves an
 * ordinary readable field.
 *
 * The reference has no single-input code field, so this takes `.input`'s
 * resting border and radius and the focus treatment of its `.otp input` rules.
 */
.otp__boxes[data-mirrored] > span {
  flex: 0 0 auto;
  width: 58px;
  height: var(--otp-box-h);
  border-radius: var(--radius-btn);
  border: 1.5px solid var(--input);
  background: var(--card);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  font-weight: 700;
  color: var(--foreground);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.otp__boxes[data-mirrored] > span[data-filled] { border-color: var(--primary); background: var(--primary-tint); }
.otp__boxes[data-mirrored] > span[data-active] { border-color: var(--primary); background: var(--card); }

/*
 * The input keeps its full size and stays visible, with only its text and
 * caret transparent: browser autofill skips a field it judges invisible, and
 * Chrome's SMS fill is the only automatic path an SMS with no origin binding
 * has. It covers the BOXES, not the whole wrapper: the error element is a
 * sibling underneath, and `inset: 0` would put this invisible input on top of
 * it and swallow taps that should reach nothing at all.
 */
.otp:has([data-mirrored]) input {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: var(--otp-box-h);
  border: 0 !important;
  background: transparent !important;
  color: transparent !important;
  caret-color: transparent;
  box-shadow: none !important;
  /* Still 16px, because the iOS zoom rule does not care that it is invisible. */
  font-size: 16px;
  letter-spacing: 0;
}
/*
 * The ring lands on a box: the active one, or the last one when a complete
 * code has no active box. The input's OWN ring is suppressed here and only
 * here, because it is a full-width element and the global rule drew a second
 * rectangle around the whole row that read as a fifth box. Suppressed only
 * under `data-mirrored`, so a bundle that failed to load leaves an ordinary
 * field with its ordinary ring rather than no focus indicator at all.
 */
.otp:has([data-mirrored]) input:focus-visible { outline: none; }
.otp:has(input:focus-visible) [data-mirrored] > span[data-active],
.otp:has(input:focus-visible) [data-mirrored][data-end] > span:last-child {
  outline: 3px solid var(--ring);
  outline-offset: 2px;
}
/* Chrome's inset autofill block would hide the boxes and print every digit
   twice, so it is re-overridden here rather than inherited from the rule
   above. */
.otp input:-webkit-autofill { -webkit-text-fill-color: transparent; }
/* Its own block, for the reason on `.input:autofill` above. */
.otp input:autofill { -webkit-text-fill-color: transparent; color: transparent; }

.resend {
  display: block;
  width: 100%;
  min-height: 44px;
  margin-top: 14px;
  padding: 10px;
  border: 0;
  background: none;
  color: var(--muted-foreground);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.resend:disabled { cursor: default; opacity: 0.7; }

/*
 * The two ways out of a code that has not arrived: ask for another, or fix the
 * number it went to. Stacked rather than side by side, because each is a
 * full-width 44px target and a row of two at 390px is two halves too narrow to
 * hold their own labels.
 */
.otp-actions { margin-top: 14px; }
.otp-actions .resend { margin-top: 0; }
.otp-actions .resend + .resend { margin-top: 4px; }

/*
 * `hidden` is an attribute, and the UA stylesheet implements it as
 * `display: none`. ANY author `display` rule outranks that, so the line below
 * un-hid the spinner and the verify button shipped with a permanent one beside
 * its label. Any rule that gives a toggled element a `display` needs the
 * `[hidden]` line with it.
 */
.spinner[hidden] { display: none; }
.spinner {
  display: inline-block;
  width: 16px; height: 16px;
  margin-left: 8px;
  border: 2px solid rgba(255, 255, 255, 0.4);
  border-top-color: var(--primary-foreground);
  border-radius: 50%;
  vertical-align: -3px;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Processing ────────────────────────────────────────────────────────── */

.processing__rows { list-style: none; margin: 0 0 28px; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.processing__row { display: flex; align-items: center; gap: 12px; font-size: 15px; font-weight: 700; }
.processing__label { flex: 1 1 auto; min-width: 0; }
.processing__track { flex: 0 0 34%; height: 14px; border-radius: 999px; background: var(--track); overflow: hidden; box-shadow: inset 0 1px 2px #0f172a0a; }
.processing__fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background-image: linear-gradient(45deg, var(--stripe-a) 25%, var(--stripe-b) 25%, var(--stripe-b) 50%, var(--stripe-a) 50%, var(--stripe-a) 75%, var(--stripe-b) 75%);
  background-size: 24px 24px;
  transition: width var(--fill-travel) linear;
}
.processing__row[data-done] .processing__fill { width: 100%; }

/*
 * The tick marks the moment the bar ARRIVES, so it waits out the bar's own
 * travel. One attribute drives both: `data-done` starts the fill and would
 * otherwise print the tick on the same frame, which puts a finished mark
 * beside a bar the visitor can still see moving, for the whole 0.6s. A row
 * that says done while it is visibly still running is the one thing this
 * pane cannot afford, because reassurance is all it is for.
 *
 * The delay is `--fill-travel`, the same value the bar transitions over,
 * rather than a second 0.6s written here: the two are one duration and have
 * to move together. Opacity rather than `content`, because content does not
 * transition, and the 18px box is reserved either way so nothing shifts.
 */
.processing__row { --fill-travel: 0.6s; }
.processing__mark {
  flex: 0 0 18px;
  color: var(--primary);
  font-weight: 700;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.processing__mark::before { content: '\2713'; }
.processing__row[data-done] .processing__mark { opacity: 1; transition-delay: var(--fill-travel); }

/* ── Consent line, and the security note under the card ────────────────── */

.consent { margin: 18px 0 0; font-size: 12.5px; line-height: 1.55; color: var(--muted-foreground); text-align: center; }
/*
 * The same two dialogs behind the same two labels as the footer, so they get
 * the same treatment: inherit everything, hover underline only. Two
 * appearances for one thing is a cost with no benefit.
 */
/*
 * These two keep their inline size, and **do not extend them to 44px.**
 *
 * They are links inside a sentence, which is the "Inline" exception in both
 * WCAG 2.5.5 and 2.5.8: a target whose size is constrained by the line-height
 * of the non-target text around it is exempt. The geometry says the same
 * thing. The line box is 19.4px, and the sentence wraps so the two links land
 * on adjacent lines with overlapping x ranges at 390px. Two hit areas taller
 * than one line box therefore OVERLAP, and in the overlap the later-painted
 * link wins, so a tap meant for the Terms opens the Privacy Policy. A 44px
 * extension makes that band 24px deep.
 *
 * Reaching 44px honestly would mean a 44px line-height on a 12.5px consent
 * paragraph. The footer's standalone policy links are the ones that get the
 * `::after` extension, because nothing constrains them.
 */
.consent button {
  color: inherit;
  font: inherit;
  background: none;
  border: 0;
  /* No horizontal padding: it renders as a space, which put a visible gap
     before the comma that follows the second link. */
  padding: 2px 0;
  text-decoration: none;
  cursor: pointer;
}

/* The reference writes this row with inline styles; the values are its. */
.sec-note {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 18px 0 0;
  padding: 0 12px;
}
.sec-note__icon {
  display: inline-flex;
  width: 15px;
  height: 15px;
  color: var(--muted-foreground);
  flex: 0 0 auto;
}
.sec-note__icon svg { width: 100%; height: 100%; }
.sec-note__text {
  font-size: 13px;
  color: var(--muted-foreground);
  font-weight: 600;
  text-align: left;
  line-height: 1.4;
}
/* The sentence is one line on a desktop and two on a phone, so the full stop
   and the break swap places rather than one of them always being wrong. */
.sec-dot { display: inline; }
.sec-br { display: none; }
@media (max-width: 640px) {
  .sec-dot { display: none; }
  .sec-br { display: inline; }
}

/* ── Motion ────────────────────────────────────────────────────────────── */

/*
 * The blanket is the FLOOR: it catches every transition and every animation,
 * including one somebody adds later without reading this. What follows it is
 * the per-animation decision, because "stop everything" is the wrong answer
 * for one of them.
 *
 * `gradMove` (the card's top hairline) and `stripeMove` (the progress bar's
 * stripes) are decorative: the bar's own WIDTH is what carries progress, and
 * the moving stripes are texture on top of it, so both stop outright.
 *
 * `spin` is a PENDING signal, and it is on the verify button, which is the one
 * screen where the lead is won. A frozen ring reads as a hung page, so it is
 * slowed rather than stopped. `!important` and after the blanket, because the
 * blanket's own `animation-iteration-count: 1 !important` would otherwise end
 * it after one turn.
 *
 * `stageFade` is a 0.35s one-shot entrance with no iteration, so the blanket's
 * 0.01ms is the whole of it: the pane simply appears. Its `both` fill still
 * lands on the resting state.
 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    /* A delay is motion's timing without the motion: left alone, an element
       whose transition was just flattened to nothing still sits and waits for
       it. The processing pane's tick is the case here, which is staggered
       behind its bar's travel and would land after the bar had already
       jumped. */
    transition-delay: 0ms !important;
    animation-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .funnel-card::before { animation: none !important; }
  .progress > i { animation: none !important; }

  .spinner {
    animation-name: spin !important;
    animation-duration: 2.4s !important;
    animation-iteration-count: infinite !important;
  }
}

/* ── Legal dialogs (markup created by embr-disclaimer.js) ──────────────── */

/* A <dialog> opened with showModal(). Keyed on [open] rather than :modal so
   one selector covers the modal state and the no-JS fallback both; the base
   rule only cancels the UA dialog box, so a closed dialog keeps the UA's
   `display: none`. packages/tracking/CLAUDE.md has why it is a dialog. */
.modal {
  margin: 0;
  padding: 0;
  border: 0;
  max-width: none;
  max-height: none;
  width: 100%;
  height: 100%;
  background: transparent;
  color: var(--foreground);
}
.modal[open] {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(15, 23, 42, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding:
    max(16px, env(safe-area-inset-top))
    max(16px, env(safe-area-inset-right))
    max(16px, env(safe-area-inset-bottom))
    max(16px, env(safe-area-inset-left));
}
/* The element paints the scrim, so ::backdrop is forced transparent: two
   translucent layers composite darker. */
.modal::backdrop { background: transparent; }
.modal__panel {
  background: var(--card);
  color: var(--card-foreground);
  border-radius: 16px;
  width: 100%;
  max-width: 680px;
  max-height: 86dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
}
.modal__close {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  border: 0;
  background: none;
  font-size: 18px;
  cursor: pointer;
  color: var(--muted-foreground);
}
/* `.modal__close:hover` is in the one hover block at the foot of this file. */
.modal__close:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }
/* Contained, or scrolling to the end of the policy carries on into the page
   behind it and a drag at the top fires Chrome Android's pull to refresh. */
.modal__scroll { overflow-y: auto; overscroll-behavior: contain; padding: 20px; }
.legal__body { font-size: 14px; line-height: 1.6; }
.legal__body h3 { font-size: 15px; margin: 20px 0 8px; }
.legal__body p, .legal__body li { margin: 0 0 10px; }
.legal__body .meta { color: var(--muted-foreground); font-size: 12.5px; }

/* ── Hover is not an input here ─────────────────────────────────────────
 *
 * ONE block, holding every `:hover` on this stylesheet that changes a colour,
 * a background or a text-decoration. `:active` stays unwrapped beside its base
 * rule.
 *
 * On touch there is no pointer to leave, so a tapped element keeps its hover
 * state until the visitor taps something else: the CTA stays in the LIGHTER
 * hover green after release, which inverts "held is darker, hovered is
 * lighter" on the one control the ad was bought to get tapped. `hover: hover`
 * matches a device that can actually hover, so on a phone these rules do not
 * exist.
 *
 * `:not(:active)` on the three that have a later state rule, and nothing to do
 * with sticky hover: this block sits at the FOOT of the file, so it would
 * otherwise out-order `.btn:active`, `.opt:active` and
 * `.combo__opt[aria-selected='true']`, which carry equal specificity and win
 * today only on source order. With it, a mouse press still paints the darker
 * state and the selected combobox option still paints as selected.
 *
 * `:-webkit-autofill:hover` and `:autofill:hover` are deliberately NOT in
 * here: they are autofill states, not affordances.
 */
@media (hover: hover) {
  .btn:hover:not(:disabled):not(:active) {
    background: var(--primary-hover);
    transform: translateY(-1px);
    box-shadow: inset 0 -4px #0000002e, 0 8px 22px rgba(var(--primary-rgb), 0.34);
  }

  .opt:hover:not(:active) {
    background: var(--primary-hover);
    transform: translateY(-2px);
    box-shadow: inset 0 -4px #0000002e, 0 8px 20px rgba(var(--primary-rgb), 0.3);
  }
  .opt:hover .ico-wrap { background: #ffffff38; }

  /* gray-300, one step off --input. */
  .input:hover { border-color: #d1d5db; }

  .other-select:hover {
    background-color: var(--primary-soft);
    transform: translateY(-1px);
    box-shadow: 0 8px 20px rgba(var(--primary-rgb), 0.22);
  }

  .combo__input:hover { background-color: var(--primary-soft); }
  .combo__opt:hover:not([aria-selected='true']) { background: var(--primary-soft); }

  .resend:hover:not(:disabled) { text-decoration: underline; }
  .consent button:hover { text-decoration: underline; }
  .modal__close:hover { color: var(--foreground); }
}

/* ── Forced colours ────────────────────────────────────────────────────
 *
 * Forced colours (Windows High Contrast and its kin) replaces every colour on
 * the page with the user's own palette and sets `box-shadow` to NONE. So any
 * indicator carried by a box-shadow alone does not degrade, it disappears, and
 * a state carried by `background` alone disappears with it.
 *
 * Each rule below restates one of those as something forced colours keeps: an
 * outline, a border, or an element that is simply there.
 */
@media (forced-colors: active) {
  /* Both suppress the outline and draw the ring as a box-shadow, which is the
     one thing this mode removes, so neither has a focus indicator left. */
  .input:focus,
  .other-select:focus-visible {
    outline: 3px solid Highlight;
    outline-offset: 2px;
  }

  /* The keyboard's position in the listbox, and the chosen option, are both
     `background` alone. Insetting keeps them clear of the input's own ring. */
  .combo__opt.is-active,
  .combo__opt[aria-selected='true'] {
    outline: 2px solid Highlight;
    outline-offset: -2px;
  }

  /*
   * One-up and two-up options carry their chosen state on the tick's OPACITY,
   * which forced colours does not touch. The four-up grid hides the tick and
   * leaves `background` as the only cue, so the tick comes back there. An
   * outline is not available: `.opt:has(input:checked)` out-specifies
   * `.opt:has(input:focus-visible)`, so it would eat the focus ring.
   */
  .grid.cols-4 .opt-check { display: inline-flex; }

  /*
   * A control whose paint is a background and a box-shadow has no edge at all
   * here. `border` rather than `outline`, so the focus rings above and the
   * global `:focus-visible` still have the outline to themselves; every one of
   * these is `box-sizing: border-box` with padding to spare, so nothing moves.
   */
  .btn,
  .opt,
  .combo__opt { border: 1px solid ButtonText; }
}
