/* ══════════════════════════════════════════════════════════════════════════
   Layout, colour and spacing below. Type sizes as noted above.

   Its own file rather than more of marketing/pages/onboarding.css, which is
   already 3,500 lines shared with the buyer and broker steppers. Loaded per
   screen via `content_for :marketing_page_asset, "seller_setup"`.

   Type is the design's own px and weights, NOT the shared --type-* ramp. The
   design uses 32/750 on every headline, 34/700 on the collections input and
   weights of 750 and 800, none of which have a rung, and snapping them cost
   roughly ten visible deviations per screen. This file is therefore excluded from
   rake typography:check in one documented place (see lib/tasks/typography.rake),
   the way themes/broker-v2.css already is, rather than carrying 41 inline opt-outs.
   The class names are the roles; each size lives once, on its role.

   Values are measured off claude-design/seller-onboarding-v3/Seller Onboarding
   v3.dc.html. The prototype's palette is declared once here rather than reaching
   for the marketing tokens, because the design ships an explicit ramp and the
   near-matches would drift under it.
   ══════════════════════════════════════════════════════════════════════════ */

/* 🚨 The page canvas, and it is a correctness fix rather than a preference.
   layouts/onboarding.html.erb paints .onboarding-body with onboarding.css's default,
   --color-gray-50, which resolves to #F7F7F7. That is the SAME value as this
   stepper's own --n50, so an option card rendered flat against the page and only its
   1px border said it was there. Seller onboarding never showed the bug because its
   controller adds .onb-theme--dashboard, which repaints the body white; the invite,
   referral and routing flows share this shell and get no such class, so they sat on
   grey while the screen they were copied from sat on white.

   broker_steps.css hit exactly this and records it at length: same layout, same
   default, six of its components rendered invisible. This is the same fix in the
   same shape.

   🚨 NOW SCOPED TO THE SHELL ITSELF, widened from the invite flow's own attribute
   exactly as the note here used to say to do "the day those two are in scope". They
   are: the referral and routing steppers sat on #F7F7F7 while every screen they were
   copied from sat on #FFFFFF, and the four flows are meant to read as one component.

   The blast radius is precise and is worth stating, because `:has()` reads as though
   it could reach anything. `.onb-s` is emitted in exactly ONE place,
   seller_onboarding/_setup_shell.html.erb, so this selector covers those four flows
   and nothing else:

     SellerSetup::Flow    already white, via .onb-theme--dashboard — unchanged
     SellerInvites::Flow  already white, via the attribute this replaces — unchanged
     Referrals::Flow      #F7F7F7 -> #FFFFFF
     Routing::Flow        #F7F7F7 -> #FFFFFF

   Buyer onboarding, broker_steps and the three invite landings share this LAYOUT but
   not this shell, so none of them match and none of them move.

   --canvas is :root-declared, so it resolves here; --surf is declared ON .onb-s and
   would be invalid at this level. */
.onboarding-body:has(.onb-s) {
  background: var(--canvas);
}

/* 🚨 THE DARK COUNTERPART, and the rule above is why it has to exist. `--canvas`
   is declared once at :root as var(--white) and has no dark value, so widening
   the selector above painted the body WHITE in dark and put light text on a white
   ground: four assertions in routing_stepper_modes_system_test went red on it.

   --base-bg-base, not --surf. In light the page and the cards are both white and
   the card reads by its border; in dark `.dark .onb-s` lifts --surf to
   --base-bg-low so a card sits ABOVE the page, which only works if the page is
   the base ground. Using the card's own colour here would flatten the two
   together and lose the lift that block was written to create. */
.dark .onboarding-body:has(.onb-s) {
  background: var(--base-bg-base);
}

.onb-s {
  --g: #0ea184;
  --gh: #0b8770;
  --gd: #0a6e5c;
  --g50: #e7f7f1;
  --ink: #222222;
  --tx: #484848;
  --tx2: #5e5e5e;
  /* 🚨 #767676 was the prototype's value and it sits ON the AA line: 4.54:1 on
     --surf, which is where 36 of this stepper's 39 muted nodes live, and 4.24:1
     the moment one lands on a --n50 card (the payer-mix option descriptions, the
     invite stepper's group note). Measured in the browser, compositing each node's
     background up its ancestors, across all 20 shipped stepper screens.
     #6f6f6f moves every one of them clear: 5.02:1 on --surf, 4.69:1 on --n50,
     4.54:1 on --g50 (a selected card). Two RGB steps; it is not visible next to
     the old value, and it changes nothing about size, weight or layout.
     Do NOT restore #767676 to match the prototype. */
  --tx3: #6f6f6f;
  /* 🚨 --tx4 is NOT AA and is deliberately left alone. It measures 2.61:1 on
     --surf and 2.19:1 on the disabled CTA's --n100 fill. Eight of its twelve
     nodes ARE that disabled CTA, which WCAG 1.4.3 exempts as an inactive control;
     the other four (.onb-s__money-label, .onb-s__reveal-ends) are real failures
     that no nudge fixes, because clearing 4.5:1 on white needs #767676 exactly and
     that both flattens the ramp against --tx3 and makes the disabled Next button
     stop reading as disabled. That is a design call, not a token nudge. */
  --tx4: #a0a0a0;
  --n50: #f7f7f7;
  --n100: #ebebeb;
  --n200: #dddddd;
  --n300: #c8c8c8;

  /* The raised surface every card, counter and text field sits on. It was eight
     separate `#fff` literals; they are one name now so the dark block at the
     foot of this file has a single lever instead of eight overrides. The value
     is unchanged in light. */
  --surf: #ffffff;

  /* The refusal ramp. Same reason: three literals, three names, one place to
     flip. --err-bd and --err-icon belong to the referral stepper's no-match
     state further down. */
  --err-bg: #fdecea;
  --err-ink: #8a1c14;
  --err-tx: #b42318;
  --err-bd: #e5b4b4;
  --err-icon: #c0392b;

  /* The nav this shell sits under is 64px (.onboarding-nav in
     marketing/pages/onboarding.css), NOT the prototype's 76px. Subtracting the
     prototype's number left the footer floating 12px above the bottom of the
     viewport on every screen, with the page background showing under it. */
  --onb-nav-h: 64px;

  display: flex;
  flex-direction: column;
  /* 🚨 A fixed height with the SCROLL INSIDE, not min-height.
     The design pins its footer and scrolls the question area within it. With
     min-height the whole page grows instead, so on any screen taller than the
     viewport (payer mix, size, review, type) the act bar and the forward CTA slide
     below the fold and the seller sees no way onward. */
  height: calc(100dvh - var(--onb-nav-h));
  overflow: hidden;
  font-size: 15px;
  color: var(--tx);
  line-height: 1.55;
}

/* The native app drops the web nav entirely (see layouts/onboarding.html.erb), so
   there is nothing to subtract. Mirrors what .buyer-setup already does. */
.onboarding-body.is-native .onb-s { --onb-nav-h: 0px; }

.onb-s__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  justify-content: center;
  padding: 8px 40px 40px;
}

.onb-s__col {
  width: 100%;
  max-width: var(--onb-s-w, 720px);
  margin: auto 0;
  padding: 24px 0;
  /* The design gives every screen the same entrance. In the prototype it plays on
     a state change; here it plays on load, which is the same moment for a seller.
     Without it the act bar slides and the content it belongs to snaps. */
  animation: onb-s-in 0.26s ease;
}

/* The two screens that arrive rather than advance: a reveal and a conclusion. The
   design swaps the rise for a settle on both. */
.onb-s--valuation .onb-s__col,
.onb-s--handoff .onb-s__col {
  animation: onb-s-pop 0.3s ease;
}

@keyframes onb-s-in {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

@keyframes onb-s-pop {
  from { opacity: 0; transform: scale(0.98); }
  to { opacity: 1; transform: none; }
}

/* ── Type ────────────────────────────────────────────────────────────────── */

.onb-s__title {
  margin: 0;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 32px;
  font-weight: 750;
  letter-spacing: -0.026em;
  /* 1.55 to match the prototype's COMPUTED result, not its source. It declares no
     line-height on the h1, so the value comes from its body. Simply omitting it
     here does not reproduce that: the marketing bundle's own h1 rule wins and
     lands on 1.25, which made every headline 10px short and pulled all twelve
     screens' content up. Match what renders, not what is authored. */
  line-height: 1.55;
  color: var(--ink);
  text-wrap: balance;
}

.onb-s__sub {
  margin: 10px 0 0;
  font-size: 16px;
  color: var(--tx2);
  text-wrap: pretty;
}

.onb-s__eyebrow {
  margin: 0;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}

.onb-s__grouplabel {
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 17px;
  font-weight: 700;
  color: var(--ink);
}

.onb-s__note {
  margin: 24px 0 0;
  font-size: 14.5px;
  color: var(--tx3);
}

/* Same voice, different job. .onb-s__note is spaced as a trailing footnote
   (24px above, nothing below); this modifier HEADS the group it introduces, so
   it needs room beneath it too. Used by the broker invite stepper's seller
   screen for "Optional. The seller confirms these when they accept". Spacing
   only: the type and colour stay the note's. */
.onb-s__note--group {
  margin: 26px 0 12px;
}

/* The form's default submit for implicit submission (Enter in a text field). Kept in
   the layout at 1px rather than display:none, because a display:none button is not a
   reliable default button across engines. tabindex="-1" keeps it out of the tab order. */
.onb-s__enter {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  pointer-events: none;
}

.onb-s__body {
  margin-top: 28px;
}

.onb-s__group {
  margin-top: 34px;
}

.onb-s__group .onb-s__opts {
  margin-top: 14px;
}

.onb-s__error {
  margin-top: 18px;
  padding: 12px 14px;
  border-radius: var(--radius-input);
  background: var(--err-bg);
  color: var(--err-ink);
  font-size: 14.5px;
}

.onb-s__field-error {
  margin-top: 10px;
  font-size: 14px;
  color: var(--err-tx);
}

/* ── Option cards ────────────────────────────────────────────────────────── */

.onb-s__opts {
  display: grid;
  gap: 12px;
}

.onb-s__opts.is-cols-1 { grid-template-columns: minmax(0, 1fr); }
.onb-s__opts.is-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.onb-s__opts.is-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.onb-s__opt {
  /* 🚨 LOAD-BEARING, and it is not about painting anything.
     `.onb-s__opt-input` is the visually-hidden radio and it is `position: absolute`
     with auto offsets. Without a positioned ancestor its containing block is the
     INITIAL one, so it resolved to its static position in the scroller's full
     content height and sat ~130px BELOW the viewport, extending the document past
     the shell. See the note on that rule for what that did. */
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 20px;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-input);
  background: var(--surf);
  text-align: left;
  font: inherit;
  cursor: pointer;
  /* Only the border and background move on selection. Animating the border WIDTH
     would shift every neighbour in the grid by half a pixel. */
  transition: background-color 0.16s ease, border-color 0.16s ease;
}

.onb-s__opt:hover { border-color: var(--n300); }

/* The selected card thickens its border. The 0.5px inset keeps the box the same
   size as its unselected neighbours so the grid does not twitch. */
.onb-s__opt.is-on,
.onb-s__opt:has(.onb-s__opt-input:checked) {
  border-color: var(--ink);
  border-width: 2px;
  padding: 19.5px;
  background: var(--n50);
}

.onb-s__opt:has(.onb-s__opt-input:focus-visible) {
  outline: 2px solid var(--g);
  outline-offset: 2px;
}

/* The design draws four card shapes and they differ in TYPE as well as spacing,
   so the base card is only ever a starting point. Keyed off the variant rather
   than the column count: padding is a property of the shape, and timing being
   the only single-column screen was a coincidence, not a rule.
   See _setup_options.html.erb for what each variant is. */

.onb-s__opts.is-preset .onb-s__opt { padding: 18px; align-items: flex-start; }
.onb-s__opts.is-preset .onb-s__opt.is-on { padding: 17.5px; }

/* The compact row card: icon beside the label rather than above it. Used by
   specialty, position and market setting. */
.onb-s__opts.is-row .onb-s__opt {
  align-items: center;
  gap: 12px;
  padding: 16px 18px;
}

.onb-s__opts.is-row .onb-s__opt.is-on,
.onb-s__opts.is-row .onb-s__opt:has(.onb-s__opt-input:checked) {
  padding: 15.5px 17.5px;
}

/* Position, the one row group the design pads evenly. Scoped to the group
   wrapper because type.html.erb is its only user. */
.onb-s__group .onb-s__opts.is-row .onb-s__opt { padding: 18px; }

.onb-s__group .onb-s__opts.is-row .onb-s__opt.is-on,
.onb-s__group .onb-s__opts.is-row .onb-s__opt:has(.onb-s__opt-input:checked) { padding: 17.5px; }

/* :stack, the practice-type pair. Icon above the text rather than beside it, so
   two cards can carry the weight the eight-card grid used to. */
.onb-s__opts.is-stack .onb-s__opt {
  flex-direction: column;
  align-items: stretch;
  gap: 24px;
  padding: 18px;
}

.onb-s__opts.is-stack .onb-s__opt.is-on,
.onb-s__opts.is-stack .onb-s__opt:has(.onb-s__opt-input:checked) { padding: 17.5px; }

/* The compact row: the same card as .is-row at the smaller scale the design uses
   for a group revealed inside another answer. */
.onb-s__opts.is-row.is-compact .onb-s__opt { gap: 11px; padding: 14px 16px; }

.onb-s__opts.is-row.is-compact .onb-s__opt.is-on,
.onb-s__opts.is-row.is-compact .onb-s__opt:has(.onb-s__opt-input:checked) { padding: 13.5px 15.5px; }

.onb-s__opts.is-row.is-compact .onb-s__opt-label { font-size: 14.5px; }

/* ── The specialty disclosure ───────────────────────────────────────────────
   Closed by default. Opened by the controller, or forced open by the noscript
   rule in type.html.erb so the seven specialties are never unreachable.
   ────────────────────────────────────────────────────────────────────────── */

.onb-s__spec-stage { display: none; }
.onb-s__spec-stage.is-open { display: block; }

.onb-s__spec-stage .onb-s__opts { margin-top: 12px; }

.onb-s__spec-hint {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 18px 0 0;
  font-size: 13.5px;
  font-weight: 600;
  /* Green, because it is an instruction the seller has to act on, not a caption.
     Every other hint in the flow is grey and ignorable; this one is not. */
  color: var(--g);
  animation: onb-s-in 0.22s ease;
}

.onb-s__spec-hint[hidden] { display: none; }

/* :tile, priorities. A display-font title over a two-line explainer, so the
   label is 700 whether or not the card is picked. */
.onb-s__opts.is-tile .onb-s__opt { align-items: flex-start; gap: 14px; padding: 20px; }

.onb-s__opts.is-tile .onb-s__opt.is-on,
.onb-s__opts.is-tile .onb-s__opt:has(.onb-s__opt-input:checked) { padding: 19.5px; }

.onb-s__opts.is-tile .onb-s__opt-icon { margin-top: 2px; }

.onb-s__opts.is-tile .onb-s__opt-label {
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 16px;
  font-weight: 700;
}

.onb-s__opts.is-tile .onb-s__opt-desc {
  margin-top: 3px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--tx2);
}

/* :lead, timing. The icon TRAILS the text and the row centres on it. The
   ordering lives in the partial; only the spacing and type are here. */
.onb-s__opts.is-lead .onb-s__opt { align-items: center; gap: 18px; padding: 20px 22px; }

.onb-s__opts.is-lead .onb-s__opt.is-on,
.onb-s__opts.is-lead .onb-s__opt:has(.onb-s__opt-input:checked) { padding: 19.5px 21.5px; }

.onb-s__opts.is-lead .onb-s__opt-label {
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 17px;
  font-weight: 700;
}

.onb-s__opts.is-lead .onb-s__opt-desc {
  margin-top: 3px;
  font-size: 14.5px;
  color: var(--tx2);
}

/* Visually hidden but still focusable and still the source of :checked. */
/* 🚨 THIS IS WHY PICKING AN OPTION USED TO SHUNT THE WHOLE PAGE DOWN.
   The stepper is a full-viewport flex column whose CONTENT scrolls inside
   `.onb-s__scroll`; the document itself must never scroll, or the fixed-height
   footer lifts off the bottom of the window and leaves a void under it.

   This input is absolute with auto offsets, and `.onb-s__opt` was not positioned,
   so its containing block was the initial one and it resolved to its static
   position within the scroller's FULL content height: measured at top 876 in a
   745px window, extending the document to 877. That made the page scrollable by
   exactly the overflow. Clicking a radio focuses it, the browser scrolls the
   focused element into view, the page jumped those 132px, and the footer appeared
   to grow because 132px of dead ground opened up beneath it.

   Two halves to the fix and both matter: `position: relative` on `.onb-s__opt`
   gives it a containing block inside the card, and `top`/`left` pin it there
   rather than leaving it at a static position to be resolved. It stays
   focusable, so `.onb-s__opt:has(.onb-s__opt-input:focus-visible)` still draws
   the ring on the card. */
.onb-s__opt-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.onb-s__opt-icon {
  flex: none;
  display: flex;
  color: var(--ink);
}

.onb-s__opt-text {
  flex: 1;
  min-width: 0;
  display: block;
}

.onb-s__opt-label {
  display: block;
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}

.onb-s__opt.is-on .onb-s__opt-label,
.onb-s__opt:has(.onb-s__opt-input:checked) .onb-s__opt-label {
  font-weight: 700;
}

.onb-s__opt-desc {
  display: block;
  margin-top: 2px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--tx3);
}

/* ── Counters (Size) ─────────────────────────────────────────────────────── */

.onb-s__rows { margin-top: 2px; }

.onb-s__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 22px 0;
  border-top: 1px solid var(--n100);
}

.onb-s__row-label {
  font-size: 16.5px;
  font-weight: 600;
  color: var(--ink);
}

.onb-s__row-hint {
  margin-top: 1px;
  font-size: 14px;
  color: var(--tx3);
}

.onb-s__counter {
  flex: none;
  display: flex;
  align-items: center;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-input);
  overflow: hidden;
}

.onb-s__counter-btn {
  width: 52px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: var(--surf);
  color: var(--ink);
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.onb-s__counter-btn:hover { background: var(--n50); }
.onb-s__counter-btn:active { background: var(--n100); }

.onb-s__counter-input {
  width: 64px;
  height: 52px;
  border: 0;
  border-left: 1px solid var(--n100);
  border-right: 1px solid var(--n100);
  text-align: center;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 19px;
  font-weight: 700;
  color: var(--ink);
  background: var(--surf);
  /* The spinners duplicate the buttons either side of this field. */
  appearance: textfield;
  -moz-appearance: textfield;
}

.onb-s__counter-input::-webkit-inner-spin-button,
.onb-s__counter-input::-webkit-outer-spin-button {
  appearance: none;
  margin: 0;
}

/* ── Collections ─────────────────────────────────────────────────────────── */

.onb-s__money { margin-top: 8px; }

.onb-s__money-label {
  display: block;
  margin-bottom: 12px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--tx4);
}

.onb-s__money-field {
  position: relative;
  max-width: 420px;
}

.onb-s__money-prefix {
  position: absolute;
  left: 22px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: var(--e-num-md);
  font-weight: 700;
  color: var(--tx3);
  pointer-events: none;
}

.onb-s__money-input {
  width: 100%;
  height: 88px;
  padding: 0 22px 0 56px;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-tile);
  background: var(--surf);
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: var(--e-num-md);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
  outline: none;
}

/* The focus rule for this input lives with the search field's, above. The
   prototype declares a green ring here, but the two text inputs in the flow have
   to agree and the location field is the one the design actually renders. */

.onb-s__money-hint {
  margin-top: 12px;
  font-size: 14px;
  color: var(--tx3);
}

.onb-s__notes {
  display: grid;
  gap: 12px;
  max-width: 62ch;
  margin-top: 40px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--tx2);
}

.onb-s__notes strong {
  color: var(--ink);
  font-weight: 600;
}

/* ── Location ────────────────────────────────────────────────────────────── */

.onb-s__search {
  position: relative;
  margin-top: 2px;
}

.onb-s__search-icon {
  position: absolute;
  left: 18px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  color: var(--tx3);
  pointer-events: none;
}

.onb-s__search-input {
  width: 100%;
  height: 60px;
  padding: 0 18px 0 48px;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-input);
  font: inherit;
  font-size: 16px;
  color: var(--ink);
  background: var(--surf);
  outline: none;
}

/* No focus styling, deliberately. The design's search field keeps its resting
   border when focused: the caret is the affordance, and a green ring on a field
   that is about to sprout an autocomplete list below it competes with the list.
   Applies to the collections input too, so the two text fields behave alike.

   ⚠️ This also opts BOTH text inputs out of the app-wide keyboard focus ring
   (`input:focus-visible { outline: 2px solid var(--base-border-focus) }` in
   marketing/site.css). A text input always matches :focus-visible when focused,
   even from a mouse click, so there is no way to keep the ring for keyboard users
   only. The caret is the remaining focus indicator. Deliberate, and narrow: it is
   these two fields and nothing else. Every non-text control in the flow keeps its
   ring, including the option cards. */
.onb-s__search-input:focus,
.onb-s__search-input:focus-visible,
.onb-s__money-input:focus,
.onb-s__money-input:focus-visible {
  outline: none;
  border-color: var(--n200);
  box-shadow: none;
}

/* 🚨 The app-wide focus ring in marketing/site.css is
   `a, button, input, select, textarea :focus-visible { outline: 2px …;
   border-radius: 4px }`. That last declaration is the problem: it rewrites the
   SHAPE of whatever takes keyboard focus, so every rounded control in this flow
   squares off the moment it is focused. Seven of them do it: both text inputs,
   the forward CTA, the ghost button, and both halves of the counter.

   Each control's own radius is restored below. The ring itself is left alone
   everywhere it is wanted. `.onb-s__back` is deliberately absent: it is an
   underlined text link with no border or background, so only its outline's
   corners round, which is what the app's convention wants anyway. */
.onb-s__search-input:focus-visible { border-radius: var(--radius-input); }
.onb-s__money-input:focus-visible { border-radius: var(--radius-tile); }
.onb-s__next:focus-visible { border-radius: var(--radius-input); }
.onb-s__ghost:focus-visible { border-radius: var(--radius-input); }

/* Square, and it has to stay square: this sits inside a rounded, overflow-hidden
   group, so rounding one corner notches the dividers between them. */
.onb-s__counter-btn:focus-visible { border-radius: 0; }

/* The counter VALUE takes no focus ring at all — the design shows a stepper looking
   identical focused and unfocused, and the only interactive feedback on it is the
   button hover above. The app-wide ring squared the field off and boxed the number
   inside its own group, reading as two nested controls.
   Kept on the buttons deliberately: they have no caret, so a keyboard user needs to
   see which half is focused. The input has one, which is its focus indicator. */
.onb-s__counter-input:focus,
.onb-s__counter-input:focus-visible {
  outline: none;
  box-shadow: none;
  /* border-radius stays 0 for the same reason as the buttons above: the app-wide rule
     sets 4px, and this field's left/right divider edges sit flush inside the group, so
     rounding them notches the seams. Suppressing the outline does not suppress that. */
  border-radius: 0;
}

.onb-s__market { margin-top: 30px; }

/* The setting trio is the one grid the design tightens to 10px, and its glyphs
   are 19px rather than the 21px the specialty cards carry. */
.onb-s__market .onb-s__opts { gap: 10px; }

.onb-s__market-lede {
  margin-bottom: 12px;
  font-size: 15px;
  color: var(--tx2);
}

.onb-s__market-lede strong {
  color: var(--ink);
  font-weight: 600;
}

/* Two ledes, one shown at a time. The guess is the design's sentence; the ask
   replaces it when the place gave us nothing to infer from, because "This looks
   like a suburban market" has to be something we actually worked out. */
.onb-s__market-ask { display: none; }
.onb-s__market.is-unknown .onb-s__market-guess { display: none; }
.onb-s__market.is-unknown .onb-s__market-ask { display: inline; }

.onb-s__trust {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 24px;
  font-size: 13.5px;
  color: var(--tx3);
}

/* Who started this listing, on the location screen of an invited seller. A
   tinted block with a hairline on ALL four sides and an even radius, never an
   accent stripe down one edge (CLAUDE.md). As a plain tertiary paragraph it
   read as fine print and got skipped, which is the opposite of what a line
   explaining why the field is already filled is for.

   Sized with its neighbours in this file rather than a --type-* role; the
   location privacy spec records why this file deliberately matches itself. */
.onb-s__invite-note {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 0 0 20px;
  padding: 12px 14px;
  border: 1px solid var(--n200);
  border-radius: 12px;
  background: var(--g50);
  font-size: 14px;
  line-height: 1.5;
  color: var(--tx);
}

.onb-s__invite-note strong { font-weight: 600; color: var(--ink); }

/* ── The privacy confirmation card ──────────────────────────────────────────
   Rendered by seller_onboarding/_setup_privacy_card, on the broker's invite
   stepper today and the seller's location screen when that half lands.

   🚨 It lives HERE, not in app/assets/tailwind/components/. Both steppers wear
   layouts/onboarding.html.erb, which ships no Tailwind bundle and puts no
   `brokering-body-broker` class on <body>. A component file or a body-scoped
   rule is simply unreachable from these screens: the markup renders, and the
   card arrives unstyled. Everything below is scoped under .onb-s and written
   against this file's own ramp, so the dark block at the foot of the file flips
   it for both dark modes with nothing to restate.

   Sizes match their neighbours in this file rather than the --type-* roles; the
   header comment records why this sheet deliberately matches itself.
   ────────────────────────────────────────────────────────────────────────── */

.onb-s__proof {
  margin-top: 22px;
  padding: 18px;
  border: 1px solid var(--n200);
  border-radius: 14px;
  background: var(--surf);
}

/* The card is hidden until something is picked, and the controller toggles the
   attribute. Stated because a later `display:` on .onb-s__proof would
   out-specify the UA's `[hidden]` rule and leave an empty card on the screen. */
.onb-s__proof[hidden] { display: none; }

.onb-s__proof-heading {
  margin: 0 0 14px;
  font-size: 15.5px;
  font-weight: 600;
  color: var(--ink);
}

/* Side by side: the record on the left, what a buyer would see of it on the
   right. The point of the screen is the comparison, so the two halves sit level
   rather than stacked, and `align-items: start` keeps the map from stretching to
   match a two-line address. */
.onb-s__proof-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: 18px;
  align-items: start;
}

.onb-s__proof-id {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  min-width: 0;
  padding: 14px;
  border-radius: 12px;
  background: var(--n50);
}

.onb-s__proof-pin {
  display: flex;
  flex: none;
  margin-top: 1px;
  font-size: 17px;
  color: var(--gd);
}

.onb-s__proof-lines {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

/* A Google business name can be one unbroken string longer than the column.
   Without the break it pushes the grid wider than the card and the whole screen
   scrolls sideways on a phone. */
.onb-s__proof-name {
  font-size: 15.5px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.onb-s__proof-address {
  font-size: 14px;
  line-height: 1.45;
  color: var(--tx2);
  overflow-wrap: anywhere;
}

.onb-s__proof-meta {
  font-size: 13.5px;
  color: var(--tx3);
}

.onb-s__proof-view { min-width: 0; }

.onb-s__proof-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 10px;
}

.onb-s__proof-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 1px solid var(--n200);
  border-radius: var(--radius-pill);
  background: var(--surf);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--tx2);
  cursor: pointer;
}

/* A border step, never a fill. The resting chip sits on --surf inside the card
   and the same component has to work on a tinted panel, where any one hover fill
   disappears. */
.onb-s__proof-chip:hover { border-color: var(--n300); }

.onb-s__proof-chip svg { font-size: 14px; }

/* Selected is a tint, not a grey. */
.onb-s__proof-chip.is-on {
  border-color: var(--gd);
  background: var(--g50);
  font-weight: 600;
  color: var(--gd);
}

.onb-s__proof-map {
  height: 190px;
  border: 1px solid var(--n200);
  border-radius: 12px;
  background: var(--n50);
  overflow: hidden;
}

.onb-s__proof-caption {
  margin: 10px 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--tx3);
}

.onb-s__proof-caption[hidden] { display: none; }

.onb-s__proof-reset {
  margin-top: 14px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--gd);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.onb-s__proof-reset:hover { color: var(--gh); }

/* ── Payer mix ───────────────────────────────────────────────────────────── */

.onb-s__stack {
  display: flex;
  height: 12px;
  margin-top: 34px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  background: var(--n100);
}

/* An inline box ignores width. Every bar segment in this codebase needs an
   explicit display, and the overview meter learned that the hard way. */
.onb-s__stack-seg {
  display: block;
  height: 100%;
  transition: width 0.2s ease;
}

.onb-s__mixrows { margin-top: 10px; }

.onb-s__mixrow {
  display: grid;
  grid-template-columns: 170px 1fr 52px;
  gap: 20px;
  align-items: center;
  padding: 12px 0;
}

.onb-s__mixrow-label {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 15px;
  color: var(--ink);
}

.onb-s__swatch {
  display: block;
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 2px;
}

.onb-s__mixrow-pct {
  text-align: right;
  font-size: 15px;
  font-weight: 600;
  color: var(--tx2);
  font-variant-numeric: tabular-nums;
}

/* A real range input, styled, rather than the prototype's transparent input over
   drawn divs: that trick loses the keyboard affordance and the focus ring. */
.onb-s__slider {
  appearance: none;
  width: 100%;
  height: 22px;
  margin: 0;
  background: transparent;
  cursor: pointer;
}

.onb-s__slider::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: var(--radius-pill);
  background: linear-gradient(
    to right,
    var(--track, var(--g)) 0 var(--fill, 0%),
    var(--n100) var(--fill, 0%) 100%
  );
}

.onb-s__slider::-moz-range-track {
  height: 4px;
  border-radius: var(--radius-pill);
  background: linear-gradient(
    to right,
    var(--track, var(--g)) 0 var(--fill, 0%),
    var(--n100) var(--fill, 0%) 100%
  );
}

.onb-s__slider::-webkit-slider-thumb {
  appearance: none;
  width: 22px;
  height: 22px;
  margin-top: -9px;
  border-radius: var(--radius-pill);
  background: #fff;
  box-shadow: var(--shadow-card);
}

.onb-s__slider::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: var(--radius-pill);
  background: #fff;
  box-shadow: var(--shadow-card);
}

.onb-s__slider:focus-visible { outline: none; }

.onb-s__slider:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--g);
  outline-offset: 2px;
}

/* ── Review ──────────────────────────────────────────────────────────────── */

.onb-s__review { margin-top: -2px; }

.onb-s__rev-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 19px 0;
  border-top: 1px solid var(--n100);
}

.onb-s__rev-label {
  flex: none;
  width: 190px;
  font-size: 14.5px;
  color: var(--tx3);
}

.onb-s__rev-value {
  flex: 1;
  min-width: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

.onb-s__rev-edit {
  flex: none;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: underline;
}

/* ── A review screen that keeps its send button in the body ──────────────────
   The seller's review moves FORWARD to another screen, so the shell's link CTA
   is right for it. A review whose forward action is a POST cannot use a link,
   and there are two ways to answer that:

   * `cta_form` on _setup_shell. The screen renders the form, the shell's own
     footer CTA submits it by id, and the button ends up where every other step
     in the flow puts it. The BROKER INVITE stepper's review does this and no
     longer matches the rule below.
   * A real submit in the body, hiding the shell's CTA. The REFERRAL stepper's
     review still does this, because its button carries
     `data-referral-recipient-target` and a Stimulus target has to be a
     descendant of its controller's element — which is the send form in the
     body, not the shell's footer. Moving it means moving that controller's
     scope, and the send gate has its own system test.

   Keyed on `.onb-s__send` rather than on `.onb-s--review`, because all three
   flows' review screens carry that modifier and only the one with a body send
   block must lose its footer CTA.
   ────────────────────────────────────────────────────────────────────────── */

.onb-s--review:has(.onb-s__send) .onb-s__foot .onb-s__next { display: none; }

/* ── The invitation email, previewed ─────────────────────────────────────────
   A bordered card with the subject line above the body, so it reads the way a
   mail client stacks the two. Everything here is chrome: the email inside is a
   fixed light artefact with its own inline backgrounds, and it must render the
   same in all three modes because that is what the seller will actually open.
   The chrome around it takes the .onb-s ramp, so dark dimmed and dark high
   contrast come free with the rest of the stepper.
   ────────────────────────────────────────────────────────────────────────── */

.onb-s__mail {
  margin-top: 32px;
  border: 1px solid var(--n200);
  border-radius: var(--radius-input);
  overflow: hidden;
}

/* --n50, not --surf. The card body behind it is the email's own near-white, so
   a white header strip would leave only the hairline to separate the two and the
   subject would read as part of the message. --tx3 on --n50 measures 4.69:1 in
   light (see the ramp's own note above) and takes the dark block's mapping for
   both dark modes, so the eyebrow clears AA in all three. */
.onb-s__mail-head {
  padding: 12px 16px;
  border-bottom: 1px solid var(--n100);
  background: var(--n50);
}

.onb-s__mail-eyebrow {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tx3);
}

.onb-s__mail-subject {
  margin: 4px 0 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}

/* 🚨 This is the one place the page must NOT reach in. The email carries its
   styling as inline `style` attributes, which beat everything here for the
   properties they set; the leaks are the properties they DON'T set and inherit
   instead. Two of them are real, and both come from the marketing bundle:
   `h1, h2, h3 { font-family: var(--font-display) }` and typography.css's
   `h1 { font: var(--type-title) var(--font-sans) }` would render the email's
   headline in the app's display face rather than the Inter its own outer table
   sets, and `a { color: var(--text-accent) }` would recolour any link the
   template leaves unstyled. `inherit` hands both back to the email. Anything
   added to those bare element rules later lands here too, so keep this list
   next to them in mind rather than assuming inline styles cover everything. */
.onb-s__mail-body :is(h1, h2, h3, h4) { font-family: inherit; }
.onb-s__mail-body a { color: inherit; }
.onb-s__mail-body img { max-width: none; display: inline; }

/* The email's outer table paints its own page colour edge to edge. Horizontal
   scroll rather than reflow: a 600px email squeezed narrower is not the email. */
.onb-s__mail-body { overflow-x: auto; }

.onb-s__send {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  margin-top: 32px;
}

/* button_to wraps its submit in a form of its own, and a block form would
   stretch the button across the whole column. */
.onb-s__send form { margin: 0; }

.onb-s__send-note {
  max-width: 62ch;
  margin: 0;
  font-size: 14px;
  color: var(--tx3);
}

/* The same note standing on its own, on a screen whose send button has moved
   into the footer. Inside `.onb-s__send` the flex column's gap does this. */
.onb-s__body > .onb-s__send-note { margin-top: 32px; }

/* A prompt, not a refusal. The red .onb-s__error band says the send was REFUSED;
   this one says it needs confirming, so it is a tinted panel, evenly rounded,
   with a hairline on all four sides. */
.onb-s__confirm {
  margin-top: 24px;
  padding: 14px 16px;
  border: 1px solid var(--n100);
  border-radius: var(--radius-input);
  background: var(--g50);
}

.onb-s__confirm-title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}

.onb-s__confirm-body {
  margin: 6px 0 0;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--tx2);
}

/* ── Reflection and handoff ──────────────────────────────────────────────── */

/* Both screens sit in a box NARROWER than their column: reflection 640 inside
   720, handoff 660 inside 720. Narrowing the column itself would look the same
   in isolation and be wrong, because the column is centred, so a 640px column
   would start 40px further right than the design's text does. Cap the measure,
   leave the column alone. */
.onb-s--reflection .onb-s__title,
.onb-s--reflection .onb-s__sub,
.onb-s--reflection .onb-s__body { max-width: 640px; }

.onb-s--handoff .onb-s__title,
.onb-s--handoff .onb-s__body { max-width: 660px; }

/* Handoff is a statement, not a question, and the design gives it its own
   headline: bigger, and set tight rather than at the shared 1.55. */
.onb-s--handoff .onb-s__title {
  font-size: 36px;
  line-height: 1.14;
  letter-spacing: -0.028em;
}

.onb-s--handoff .onb-s__note {
  margin-top: 22px;
  line-height: 1.6;
}

/* The one sub the design caps, because it is the longest in the flow. */
.onb-s--priorities .onb-s__sub { max-width: 60ch; }

.onb-s__readback {
  margin: 30px 0 0;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 24px;
  font-weight: 600;
  line-height: 1.45;
  color: var(--gd);
  text-wrap: pretty;
}

.onb-s__ghost {
  display: inline-block;
  margin-top: 18px;
  padding: 11px 18px;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-input);
  background: var(--surf);
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  transition: border-color 0.16s ease;
}

.onb-s__ghost:hover { border-color: var(--ink); color: var(--ink); }

.onb-s__brief {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 20px 0;
  border-top: 1px solid var(--n100);
}

.onb-s__brief-icon {
  flex: none;
  display: flex;
  margin-top: 3px;
  color: var(--gd);
}

.onb-s__brief-text { flex: 1; min-width: 0; display: block; }

.onb-s__brief-title {
  display: block;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 17px;
  font-weight: 700;
  color: var(--ink);
}

.onb-s__brief-body {
  display: block;
  margin-top: 3px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--tx2);
}

/* ── Valuation reveal ────────────────────────────────────────────────────────
   The one screen in the flow that is centred. Everything below is the design's
   `valuation` screen; the numbers behind it are the real estimator's.
   ────────────────────────────────────────────────────────────────────────── */

.onb-s__reveal { text-align: center; }

.onb-s__reveal-lede {
  max-width: 52ch;
  margin: 0 auto;
  font-size: 16px;
  line-height: 1.6;
  color: var(--tx2);
  text-wrap: pretty;
}

.onb-s__reveal-range {
  margin: 22px 0 0;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 62px;
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.038em;
  color: var(--gd);
  /* Held invisible for the first fifth of a second, then faded up. The server
     renders the final figures here and onboarding_reveal counts them up from
     zero, but the controller cannot start until its module has loaded, and until
     then the answer is sitting on screen. This hides that window. It is an
     animation rather than a starting opacity so that with JS off the figures
     still appear: an animation always runs to its end state. */
  animation: onb-s-reveal-in 0.5s ease both;
}

@keyframes onb-s-reveal-in {
  0%, 36% { opacity: 0; }
  100% { opacity: 1; }
}

/* The midpoint supports the range rather than leading it. Not in the design,
   which led with one precise figure. See the partial's header for why. */
.onb-s__reveal-mid {
  margin: 10px 0 0;
  font-size: 15px;
  color: var(--tx3);
}

.onb-s__reveal-mid strong {
  color: var(--ink);
  font-weight: 600;
}

.onb-s__reveal-band {
  max-width: 480px;
  margin: 28px auto 0;
}

.onb-s__reveal-track {
  position: relative;
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--n100);
}

/* Inset equally from both ends, so the coloured span is centred on the midpoint
   and its WIDTH is the band's half_pct. A tighter estimate draws a shorter bar. */
.onb-s__reveal-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--inset, 33%);
  right: var(--inset, 33%);
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, #5fc7ac, #0b8770);
  transition: left 0.4s ease, right 0.4s ease;
}

.onb-s__reveal-ends {
  display: flex;
  justify-content: space-between;
  margin-top: 9px;
  font-size: 13px;
  color: var(--tx4);
}

.onb-s__reveal-drivers {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin: 26px 0 0;
  padding: 0;
  list-style: none;
}

.onb-s__reveal-driver {
  padding: 8px 14px;
  border: 1px solid var(--n100);
  border-radius: var(--radius-input);
  font-size: 13.5px;
  color: var(--tx2);
}

.onb-s__reveal-sharpen {
  max-width: 620px;
  margin: 34px auto 0;
  padding-top: 24px;
  border-top: 1px solid var(--n100);
  text-align: left;
}

.onb-s__reveal-sharpen-title {
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
}

.onb-s__reveal-sharpen-list {
  display: grid;
  gap: 12px;
  margin-top: 14px;
}

.onb-s__reveal-sharpener {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--tx);
}

.onb-s__reveal-sharpener-icon {
  flex: none;
  display: flex;
  margin-top: 4px;
  color: var(--g);
}

.onb-s__reveal-sharpener strong {
  color: var(--ink);
  font-weight: 600;
}

/* ── Welcome ─────────────────────────────────────────────────────────────── */

.onb-s__welcome {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: 64px;
  align-items: center;
}

.onb-s__welcome-copy .onb-s__eyebrow { margin-bottom: 14px; }

/* The welcome headline is its own size, not the shell's question heading: 44px at
   weight 800, wrapping to two lines at this column width. */
.onb-s__welcome-title {
  margin: 0;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 44px;
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--ink);
  max-width: 14ch;
}

.onb-s__welcome-lede {
  margin: 20px 0 0;
  font-size: 16.5px;
  line-height: 1.62;
  color: var(--tx2);
  text-wrap: pretty;
}

.onb-s__welcome-lede + .onb-s__welcome-lede { margin-top: 14px; }

/* The gradient is the fallback: if three.js never arrives, or the seller asked
   for reduced motion, this panel still looks deliberate. */
.onb-s__welcome-stage {
  height: 380px;
  border-radius: 20px;
  background: linear-gradient(150deg, #f7fbfa, #edf6f3);
  overflow: hidden;
}

.onb-s__welcome-stage canvas { display: block; }

/* ── Footer ──────────────────────────────────────────────────────────────── */

.onb-s__foot { flex: none; }

.onb-s__acts {
  display: flex;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.onb-s__act {
  flex: 1;
  height: 4px;
  background: var(--n100);
  overflow: hidden;
}

.onb-s__act-fill {
  display: block;
  width: var(--fill, 0%);
  height: 100%;
  background: var(--ink);
  transition: width 0.3s ease;
}

.onb-s__bar {
  height: 88px;
  /* The design's own gutter. Reserving 172px on the right to dodge the Userback
     feedback FAB, as this file used to, pushed the CTA a third of the way across
     the footer on all twelve screens. The FAB is lifted clear instead, above the
     88px footer, by the same rule the buyer stepper already uses (see
     marketing/pages/onboarding.css, the #userback_button_container block). */
  padding: 0 40px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.onb-s__bar-end {
  display: flex;
  align-items: center;
  gap: 22px;
}

.onb-s__back {
  padding: 10px 0;
  border: 0;
  background: transparent;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: underline;
  cursor: pointer;
}
/* Hover colour stated because these render on <a>: the global
   `a:hover { color: var(--brand-600) }` in colors_and_type.css outspecifies a single
   class, so without it the label/icon turns mint on hover. */
.onb-s__back:hover { color: var(--ink); }

.onb-s__back.is-hidden { visibility: hidden; }

.onb-s__skip {
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--tx3);
  text-decoration: underline;
  cursor: pointer;
}

.onb-s__next {
  padding: 15px 32px;
  border: 0;
  border-radius: var(--radius-input);
  background: var(--g);
  color: #fff;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 16px;
  font-weight: 650;
  /* Same reason as the title: a button does not inherit the body's line-height
     through the reset, so the design's 24.8px has to be stated. Without it the CTA
     is 6px shorter than the design's. */
  line-height: 1.55;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.18s ease;
}

/* The `color` here is load-bearing, not redundant. colors_and_type.css carries a
   global `a:hover { color: var(--brand-600) }` and this control renders on an <a>, so
   `a:hover` (0,1,1) outspecifies the class (0,1,0) and repaints the label on hover
   unless the hover state restates it. Remove it and the label goes invisible: --gh is #0b8770, and --brand-600 is #0B8770. */
.onb-s__next:hover { background: var(--gh); color: #fff; }
.onb-s__next:active { background: var(--gd); }

.onb-s__next:disabled,
.onb-s__next[disabled] {
  background: var(--n100);
  color: var(--tx4);
  cursor: not-allowed;
}

/* ── Responsive ──────────────────────────────────────────────────────────────
   Part of Done for seller surfaces, not an afterthought. Two things drive the
   breakpoints: the multi-column card grids, and the payer-mix row whose fixed
   170px label column is what actually overflows first.
   ────────────────────────────────────────────────────────────────────────── */

@media (max-width: 1023px) {
  .onb-s__welcome {
    grid-template-columns: minmax(0, 1fr);
    gap: 32px;
  }

  .onb-s__welcome-stage { height: 280px; }
}

@media (max-width: 767px) {
  .onb-s__scroll { padding: 4px 20px 32px; }
  .onb-s__bar { height: auto; padding: 16px 20px 24px; gap: 12px; }

  /* 🚨 TWO LONG LABELS IN ONE FOOTER, which only Add deal's read-back has: "Add deal and
     manage listing" beside "Add deal and send invite". Every other screen in every flow
     ends on one or two words ("Next", "Send invite"), so the row fits on a phone.

     Measured at 375px: the three controls wrap to three and four lines each and the CTA
     stands 104px tall. Stacking is the honest answer, and it puts the primary at the
     bottom, under the thumb.

     Scoped to the flow rather than to `.onb-s--review`, which the invite stepper's own
     read-back also carries: its CTA is "Send invite" and it must keep the one-row footer.
     The attribute is emitted by _setup_shell for every flow. */
  .onb-s--review[data-setup-shell-flow-value="ManualDeals::Flow"] .onb-s__bar {
    flex-wrap: wrap;
    row-gap: 10px;
  }

  .onb-s--review[data-setup-shell-flow-value="ManualDeals::Flow"] .onb-s__bar-end {
    flex-wrap: wrap;
    gap: 10px;
    width: 100%;
  }

  .onb-s--review[data-setup-shell-flow-value="ManualDeals::Flow"] .onb-s__bar-end .onb-s__skip,
  .onb-s--review[data-setup-shell-flow-value="ManualDeals::Flow"] .onb-s__bar-end .onb-s__next {
    width: 100%;
    text-align: center;
    justify-content: center;
  }

  .onb-s__title { font-size: 26px; }
  .onb-s__sub { font-size: 15px; }

  /* 62px is a desktop display size. On a phone the range wraps to two lines and
     swamps everything under it, so it comes down with the rest of the headings. */
  .onb-s__reveal-range { font-size: var(--e-num-xl); }
  .onb-s--handoff .onb-s__title { font-size: var(--e-num-xs); }

  /* Three-up specialty cards do not fit a phone; two-up with the compact row
     card still shows the icon and the full label. */
  .onb-s__opts.is-cols-2,
  .onb-s__opts.is-cols-3 { grid-template-columns: minmax(0, 1fr); }

  .onb-s__money-input { height: 72px; font-size: var(--e-num-xs); padding-left: 48px; }
  .onb-s__money-prefix { left: 18px; font-size: var(--e-num-xs); }

  /* The label moves above the slider, so the readout can keep its own column
     rather than being squeezed to nothing. */
  .onb-s__mixrow {
    grid-template-columns: 1fr auto;
    gap: 8px 12px;
  }

  .onb-s__mixrow-label { grid-column: 1; }
  .onb-s__mixrow-pct { grid-column: 2; }
  .onb-s__slider { grid-column: 1 / -1; }

  .onb-s__row {
    align-items: flex-start;
    flex-direction: column;
    gap: 14px;
  }

  /* The review row's fixed 190px label column plus a value plus Edit is more
     than a phone has. Stack the label over the value and keep Edit on the
     baseline. */
  .onb-s__rev-row {
    flex-wrap: wrap;
    gap: 4px 16px;
  }

  .onb-s__rev-label { width: 100%; }
  .onb-s__rev-value { flex: 1 1 auto; }

  .onb-s__readback { font-size: 20px; }

  /* Two columns inside a 335px card is two unreadable columns. The record goes
     above the map it is being compared against, which is the order the sentence
     reads in anyway. */
  .onb-s__proof-body { grid-template-columns: minmax(0, 1fr); }
  .onb-s__proof { padding: 16px; }
  .onb-s__proof-map { height: 170px; }
}

@media (prefers-reduced-motion: reduce) {
  .onb-s__act-fill,
  .onb-s__stack-seg,
  .onb-s__opt,
  .onb-s__reveal-fill,
  .onb-s__next { transition: none; }

  /* Screens arrive already in place. setup_shell and onboarding_reveal check the
     same query in JS, so the act bar and the counting figures stop too. */
  .onb-s__col,
  .onb-s--valuation .onb-s__col,
  .onb-s--handoff .onb-s__col { animation: none; }

  /* The count-up is off too (the controller checks the same query), so there is
     no gap left to cover and the figures must not be hidden even briefly. */
  .onb-s__reveal-range { animation: none; }
}


/* ── Icon sizing ─────────────────────────────────────────────────────────────
   IconHelper renders its SVGs at width/height: 1em, so an icon is sized by its
   box. A CSS width/height on the svg beats that attribute and keeps glyph sizing
   out of type entirely. Utility classes are not an option: this surface loads the
   marketing bundle, so w-[21px] does not exist and fails silently.
   ────────────────────────────────────────────────────────────────────────── */

.onb-s__opt-icon svg { width: 21px; height: 21px; }
/* Per card shape, because the design sizes the glyph to the card and not to the
   surface. Higher specificity than the 21px above, so order here does not matter. */
.onb-s__opts.is-tile .onb-s__opt-icon svg { width: 22px; height: 22px; }
.onb-s__opts.is-lead .onb-s__opt-icon svg { width: 24px; height: 24px; }
.onb-s__opts.is-stack .onb-s__opt-icon svg { width: 24px; height: 24px; }
.onb-s__opts.is-row.is-compact .onb-s__opt-icon svg { width: 19px; height: 19px; }
.onb-s__spec-hint svg { width: 15px; height: 15px; flex: none; }
.onb-s__market .onb-s__opt-icon svg { width: 19px; height: 19px; }
.onb-s__search-icon svg { width: 18px; height: 18px; }
.onb-s__counter-btn svg { width: 16px; height: 16px; }
.onb-s__invite-note svg { width: 16px; height: 16px; flex: 0 0 auto; margin-top: 2px; color: var(--gd); }
.onb-s__brief-icon svg { width: 22px; height: 22px; }
.onb-s__reveal-sharpener-icon svg { width: 16px; height: 16px; }
.onb-s__trust svg { width: 13px; height: 13px; }

/* ── Stepper nav ─────────────────────────────────────────────────────────────
   The shared onboarding layout puts its exit link in a CENTRE slot. The design
   right-aligns it as a pill. Scoped to the stepper's own body class so the buyer
   and broker onboarding flows that share this layout are untouched.
   ────────────────────────────────────────────────────────────────────────── */

/* 🚨 .onboarding-nav__inner is a GRID: `1fr auto 1fr`, with the exit link in the
   middle column. That is why three flex-based attempts here did nothing at all:
   justify-content and margin-left: auto are meaningless against a grid track.
   Give the logo an auto column, let the middle track take the slack, and
   right-align the link inside it. */
/* The design draws no rule under the top bar. The stepper is one uninterrupted
   white field from the logo down to the act strip, and a hairline here cuts the
   first screen in half. The shared nav keeps its border for every other flow. */
body.onboarding-body:has(.onb-s) .onboarding-nav {
  border-bottom: 0;
}

body.onboarding-body:has(.onb-s) .onboarding-nav__inner {
  grid-template-columns: auto 1fr auto;
}

body.onboarding-body:has(.onb-s) .onboarding-nav__center {
  justify-self: end;
}

.onboarding-body:has(.onb-s) .onboarding-nav__exit {
  padding: 9px 18px;
  border: 1px solid var(--n200, #dddddd);
  border-radius: var(--radius-pill);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink, #222222);
  text-decoration: none;
  transition: background-color 0.16s ease;
}

.onboarding-body:has(.onb-s) .onboarding-nav__exit:hover { background: var(--n50, #f7f7f7); }

/* Two `.form-group`s side by side — the seller screen's first/last name pair.
   Column gap only: the children keep their own 16px margin-bottom, so the
   vertical rhythm is identical whether the row is two columns or one. */
.onb-s__field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 16px;
}

@media (max-width: 767px) {
  .onb-s__field-row { grid-template-columns: minmax(0, 1fr); }
}

/* ── The recipient workbench (fields/_recipient_workbench) ────────────────────
   Implements `Referral Recipients Step.dc.html`: your network on the left, the
   shortlist the seller reads on the right.

   🚨 `.onb-s__*`, and in THIS sheet, for the reason fields/_recipient_slate's
   header records: the stepper wears the onboarding layout, which does not link
   components/referrals.css at all, so `.ref-*` names here would render nothing.

   Values are the design's own tokens (colors_and_type.css) resolved against the
   ramp this sheet already uses — --g is the same #0EA184 the design calls brand.
   ────────────────────────────────────────────────────────────────────────── */
.onb-s__bench {
  display: grid;
  grid-template-columns: 400px minmax(0, 1fr);
  gap: 32px;
  align-items: start;
}

@media (max-width: 1023px) {
  .onb-s__bench { grid-template-columns: minmax(0, 1fr); }
}

.onb-s__bench-pane {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--n100);
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .06);
}

.onb-s__bench-search {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 14px;
  padding: 0 14px;
  height: 44px;
  border: 1px solid var(--n200);
  border-radius: var(--radius-pill);
}

.onb-s__bench-search-icon { display: flex; color: var(--tx3); }
.onb-s__bench-search-icon svg { width: 16px; height: 16px; }

.onb-s__bench-search-input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  font-size: 14px;
  color: var(--ink);
  /* Safari draws its own decoration on a search input. */
  appearance: none;
}

.onb-s__bench-search-input::-webkit-search-cancel-button { appearance: none; }

/* The lookup's Add, on the search row itself. Sized for the 44px pill rather
   than reusing .onb-s__lookup-add, which is a full-height form button drawn for
   a stacked field. It is the only way to stage a brokerage that is not already
   in the saved network, so it is never conditionally rendered: it is present and
   disabled from first paint, and referral_slate_controller#render arms it. */
.onb-s__bench-lookup-add {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--gd);
  color: #fff;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 13px;
  font-weight: 650;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.onb-s__bench-lookup-add svg { width: 14px; height: 14px; }
.onb-s__bench-lookup-add:hover:not(:disabled) { background: #08594a; }

.onb-s__bench-lookup-add:disabled {
  background: var(--n100);
  color: var(--tx4);
  cursor: not-allowed;
}

.onb-s__bench-list { border-top: 1px solid var(--n100); max-height: 420px; overflow: auto; }

.onb-s__bench-overline {
  margin: 0;
  padding: 14px 16px 6px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--tx3);
}

.onb-s__bench-rows { margin: 0; padding: 0; list-style: none; }

.onb-s__bench-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
}

.onb-s__bench-row:hover { background: var(--n50); }

/* 🚨 The referrer screen borrows the PRACTICE screen's search field rather than the outbound
   pane's, so the two searches in Add deal look and behave alike, focus rule included. The pane
   has no padding of its own (it is `overflow: hidden` with its children flush), so the field
   needs its own inset here.

   🚨 EVEN ON ALL FOUR SIDES, and the bottom is the one that gets dropped. It was
   `14px 14px 0`, to buy back the height the list wanted, which sat the field flush on the
   divider below it while leaving 14px of air above: the same gap read as two different gaps
   on one control. `.onb-s__bench-search` above, the outbound pane's own search row, is
   `margin: 14px` for this reason, and a pane whose two halves inset differently is the thing
   both rules exist to avoid. Height comes out of the list cap instead. */
.onb-s__bench-pane > .onb-s__search { margin: 14px; }

/* Shorter than the outbound pane's 420px, because this list sits UNDER two option cards and
   Manage networks at its foot has to stay on screen with them. Sized so the whole pane, the
   cards above it and the stepper's own footer fit a 900px viewport.

   🚨 210px is three rows and HALF of a fourth, and the half is the point. A row is ~60px, so
   the obvious caps (248px, 234px) clip 7px off the fourth row: its name and location both
   still read, so a broker with ten partners sees what looks like a complete list of four and
   never scrolls. A row cut through the middle is the only thing here that says there is
   more. */
.onb-s__bench-list--short { max-height: 210px; }

/* 🚨 The row is a BUTTON on Add deal's `referrer` screen, and a div everywhere else, so these
   reset it rather than restyling it. A button is right there because pressing the chosen
   brokerage again has to clear it, and a radio group cannot be un-picked: that screen's honest
   answer set is "this firm", "that firm" and "nobody". */
button.onb-s__bench-row {
  width: 100%;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  cursor: pointer;
}

/* Selected reads the way a selected option card does in this bundle: the neutral tint plus a
   weight change, never a colour fill. See .onb-s__opt.is-on. */
.onb-s__bench-row.is-on { background: var(--n50); }
.onb-s__bench-row.is-on .onb-s__bench-name { font-weight: 800; }

button.onb-s__bench-row:focus-visible {
  outline: 2px solid var(--g);
  outline-offset: -2px;
}

/* The tick occupies its slot whether or not it is showing, so picking a row does not shuffle
   the name beside it. */
.onb-s__bench-tick {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  color: var(--g);
  visibility: hidden;
}

.onb-s__bench-row.is-on .onb-s__bench-tick { visibility: visible; }

/* 🚨 A SQUARE, never a pill. The redesign encodes entity kind in the SHAPE -- a person is
   a circle, a firm is a square -- and every network row here is a brokerage. It was a pill,
   which read as a person and, once real logos started rendering, clipped the wordmark most
   brokerages actually upload. Same rule as .brokering-ov-snapshot-logo and the switcher
   tiles. */
.onb-s__bench-avatar {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 9px;
  background: var(--n100);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 800;
  color: var(--tx2);
}

/* 🚨 THE `is-logo` RULE HAS TO BE RESTATED IN THIS BUNDLE. `brokerage_mark` adds
   `.brokering-mark is-logo` to the image, but .brokering-mark lives in the Tailwind app
   bundle and the stepper renders under layouts/onboarding, which loads the MARKETING
   bundle only. A rule written once in brokering.css therefore does nothing on this screen.
   Style the logo branch on the caller's own class, in whichever bundle that class lives. */
.onb-s__bench-avatar.is-logo {
  object-fit: contain;
  background: var(--base-bg-low, #fff);
}

.onb-s__bench-identity { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; }

.onb-s__bench-name {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.onb-s__bench-meta {
  font-size: 12px;
  color: var(--tx3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.onb-s__bench-add {
  flex: 0 0 auto;
  height: 32px;
  padding: 0 14px;
  border: 1px solid var(--n200);
  border-radius: var(--radius-input);
  background: #fff;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
}

.onb-s__bench-add:hover:not(:disabled) { border-color: var(--g); }

/* Added, not gone. A row that vanished on click would reshuffle the list under
   the cursor mid-click, which is how a broker adds the wrong brokerage. */
.onb-s__bench-add:disabled {
  cursor: default;
  border-color: rgba(14, 161, 132, .4);
  background: rgba(14, 161, 132, .08);
  color: var(--gd);
}

.onb-s__bench-nomatch,
.onb-s__bench-hint {
  margin: 0;
  padding: 12px 16px 18px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--tx3);
}

.onb-s__bench-hint { border-top: 1px solid var(--n100); }

.onb-s__bench-manage {
  height: 48px;
  padding: 0 16px;
  border-top: 1px solid var(--n100);
  background: #FCFCFC;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}

/* Restates --ink on hover: this is an <a> painted as a row, so the global
   `a:hover { color: var(--brand-600) }` would otherwise turn the label green
   while only the background was meant to move. */
.onb-s__bench-manage:hover { background: var(--n50); color: var(--ink); }
.onb-s__bench-manage span { display: flex; align-items: center; gap: 8px; }
.onb-s__bench-manage svg { color: var(--tx3); }

/* ── the shortlist ── */
.onb-s__bench-shortlist { display: flex; flex-direction: column; gap: 12px; }

.onb-s__bench-shortlist-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.onb-s__bench-shortlist-meta { display: flex; align-items: baseline; gap: 10px; }

.onb-s__bench-reorder { font-size: 13px; color: var(--tx3); }

.onb-s__bench-foot { margin: 0; font-size: 13px; color: var(--tx3); }

.onb-s__slate--empty { margin-top: 0; }

.onb-s__slate-grip { color: var(--n200); font-size: 14px; line-height: 1; flex: 0 0 auto; }

/* 🚨 The first slot is the SELLER'S first read, so it is the only filled circle.
   Rank is the broker's preference and this is the one place it is visible. */
.onb-s__slate-row:first-child .onb-s__slate-rank {
  background: var(--g);
  color: #fff;
}

.onb-s__slate-row--empty {
  border-style: dashed;
  background: #FCFCFC;
}

.onb-s__slate-row--empty .onb-s__slate-rank {
  background: var(--n100);
  color: var(--tx4);
}

.onb-s__slate-placeholder { font-size: 14px; color: var(--tx4); }

/* A message is optional, so the control is quiet until there is one. */
.onb-s__slate-note-btn {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--n100);
  border-radius: var(--radius-input);
  background: #fff;
  font-size: 12px;
  font-weight: 700;
  color: var(--tx3);
  cursor: pointer;
}

.onb-s__slate-note-btn:hover { border-color: var(--n200); }

.onb-s__slate-note-btn.is-set {
  border-color: rgba(14, 161, 132, .4);
  color: var(--gd);
}

.onb-s__slate-note { display: flex; flex-direction: column; gap: 6px; padding-left: 50px; }

.onb-s__slate-note-actions { display: flex; align-items: center; gap: 12px; }

.onb-s__slate-note-done,
.onb-s__slate-note-clear {
  height: 28px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--radius-input);
  background: transparent;
  font-size: 12px;
  font-weight: 700;
  color: var(--tx3);
  cursor: pointer;
}

.onb-s__slate-note-done:hover,
.onb-s__slate-note-clear:hover { color: var(--ink); }

/* ── The referral stepper's field groups ─────────────────────────────────────
   Brokering::Deals::ReferralStepsController renders the canonical referral field
   groups (brokering/deals/referrals/fields/*) on the ONBOARDING layout, which
   ships the marketing bundle and no Tailwind at all. Those groups' own styles
   live in app/assets/tailwind/components/{brokering,referrals}.css AND are
   scoped `:is(body.brokering-body-broker)`, so they are unreachable here twice
   over: the sheet is not linked, and the body class is not on the page.

   `.form-group` / `.form-label` / `.form-control` / `.form-hint` are already
   covered — marketing/pages/onboarding.css defines them and this layout links it
   unconditionally. What is missing is the lookup result and the terms group, so
   they are restated here against the stepper's own ramp, scoped under `.onb-s`
   so nothing else on the marketing bundle is touched.

   Written against the ramp, not against literals, so the dark block at the foot
   of this file carries them with everything else. (This paragraph used to say
   the sheet was light only. It was, and that was the bug: see that block.)
   ────────────────────────────────────────────────────────────────────────── */

/* 🚨 `fields/_recipient_broker` positions its mail icon with Tailwind utilities:
   `.relative` on a wrapper, `absolute left-3 top-1/2 -translate-y-1/2
   pointer-events-none` plus `w-3.5 h-3.5` on the icon, and `pl-9` on the input.
   marketing/pages/onboarding.css shims some Tailwind names but none of those, so
   without this the envelope renders as a stray inline glyph above the field and
   the input has no room reserved for it. Restated against the wrapper's own
   shape rather than as generic `.relative` / `.absolute` utilities, which would
   be a new global vocabulary on a bundle that deliberately has a small one. */
.onb-s .form-group > .relative { position: relative; }

.onb-s .form-group > .relative > svg {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  color: var(--tx4);
  pointer-events: none;
}

.onb-s .form-group > .relative > .form-control { padding-left: 36px; }

.onb-s .brokering-referral-lookup-result { margin-top: 10px; }

.onb-s .brokering-referral-lookup-match {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--g);
  border-radius: var(--radius-input);
  background: var(--g50);
}

.onb-s .brokering-referral-lookup-match-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: var(--g);
  color: #ffffff;
}

.onb-s .brokering-referral-lookup-match-icon svg { width: 16px; height: 16px; }

.onb-s .brokering-referral-lookup-match-name {
  display: block;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.onb-s .brokering-referral-lookup-match-hint {
  display: block;
  font-size: 13px;
  color: var(--tx2);
}

/* The resting and the no-match states. Dashed while nothing has been typed,
   solid once an address failed to resolve: a hint and a refusal should not look
   the same. Evenly rounded with a border on all four sides. */
.onb-s .brokering-referral-lookup-empty {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 10px 12px;
  border: 1px dashed var(--n200);
  border-radius: var(--radius-input);
  font-size: 13px;
  line-height: 1.5;
  color: var(--tx3);
}

.onb-s .brokering-referral-lookup-empty svg {
  width: 16px;
  height: 16px;
  flex: none;
  margin-top: 1px;
  color: var(--tx4);
}

.onb-s .brokering-referral-lookup-empty.is-error {
  border-style: solid;
  border-color: var(--err-bd);
  color: var(--tx2);
}

.onb-s .brokering-referral-lookup-empty.is-error svg { color: var(--err-icon); }

/* The terms group: the seller-consent promise and the 72-hour window. Stacked in
   the same 24px column the routing panel uses, because neither statement carries
   a margin of its own. */
.onb-s .ref-route-panel-col {
  display: flex;
  flex-direction: column;
  gap: 24px;
  margin-top: 28px;
}

.onb-s .ref-route-consent {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

.onb-s .ref-route-consent-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 3px;
  border-radius: 999px;
  background: var(--g50);
  color: var(--gd);
}

.onb-s .ref-route-consent-mark svg { width: 10px; height: 10px; }

.onb-s .ref-route-panel-note {
  margin: 0;
  max-width: 62ch;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--tx2);
}

/* ── Dark ────────────────────────────────────────────────────────────────────
   🚨 This sheet used to end without one, on the note that "the onboarding
   layout's marketing pages have no dark variants". That note was wrong about the
   consequence. layouts/onboarding.html.erb DOES put `dark`/`dimmed` on <html>
   from the signed-in user's theme, and marketing/pages/onboarding.css flips the
   page out from under this shell with `.dark .onboarding-body { background:
   var(--base-bg-base) }`. The shell's own ramp stayed light, so every headline
   rendered #222222 on a #1E1E1E page: a measured contrast ratio of 1.05:1 in
   Dark Dimmed and 1.17:1 in Dark High Contrast, on all ten broker-invite screens
   and all twelve seller ones. Not "no dark variant" — unreadable.

   The fix is the ramp, not the rules. Everything above resolves through the
   dozen private custom properties declared on `.onb-s`, so re-pointing those at
   the marketing dark tokens flips ~1,500 lines at once, and BOTH dark modes come
   free: `.dark` and `.dark.dimmed` already resolve --base-* differently in
   marketing/colors_and_type.css, so there is nothing to write twice.

   Light is untouched. Do not add a light rule inside this block.
   ────────────────────────────────────────────────────────────────────────── */

.dark .onb-s {
  --g: var(--bg-primary);
  --gh: var(--bg-primary-hover);
  /* Green as an accent GLYPH, which is what --gd is in five of its six uses. The
     sixth is the CTA's press state and is restated below, because a background
     and an icon cannot share one value once the page behind them is dark. */
  --gd: var(--text-primary);
  --g50: var(--bg-success-secondary);

  --ink: var(--base-text);
  --tx: var(--base-text-secondary);
  --tx2: var(--base-text-secondary);
  /* Both grey rungs move UP one against their light counterparts, rather than
     mapping rung-for-rung. Measured, not guessed: --tx3 at --base-text-tertiary
     reads 4.22:1 where it sits on a SELECTED card (the "You" line under the
     credited teammate on `message`), because that card is a step lighter than
     the page. A dark surface needs more separation than a light one, not the
     same amount. At the rungs below, the same line measures 7.2:1. */
  --tx3: var(--base-text-secondary);
  --tx4: var(--base-text-tertiary);

  /* The card/field surface lifts OFF the page rather than sinking into it:
     --base-bg-low is one step above --base-bg-base, so a card still reads as
     raised, and the selected state (--n50) is one step above that again, which
     preserves the light design's "selected is a shade nearer the eye". */
  --surf: var(--base-bg-low);
  --n50: var(--base-bg-high);
  --n100: var(--base-bg-high);
  --n200: var(--base-border-secondary);
  --n300: var(--base-border-primary);

  --err-bg: var(--bg-danger-secondary);
  --err-ink: var(--text-on-danger-secondary);
  --err-tx: var(--text-danger);
  --err-bd: var(--text-danger);
  --err-icon: var(--icon-on-danger-secondary);
}

/* The two places --g is a COLOUR rather than a fill. --bg-primary is a deep
   green chosen to sit under white text; as type on a dark page it is illegible,
   so these take the light green the token set already keeps for exactly this. */
.dark .onb-s__spec-hint,
.dark .onb-s__reveal-sharpener-icon { color: var(--text-primary); }

/* The CTA's press state, the one --gd use that is a fill. */
.dark .onb-s__next:active { background: var(--bg-primary-hover); }

/* A focus ring has to be seen against the surface it rings. --g is the CTA's own
   fill, which is close enough to the dark page to disappear. */
.dark .onb-s__opt:has(.onb-s__opt-input:focus-visible),
.dark .onb-s__slider:focus-visible::-webkit-slider-thumb { outline-color: var(--base-border-focus); }

/* 🚨 `.form-group` / `.form-label` / `.form-control` come from
   marketing/pages/onboarding.css, which this layout links unconditionally. They
   are LIGHT-ONLY there and cannot be fixed there: that file is shared with the
   buyer onboarding and the broker wizard, and a bare `.dark .form-control` would
   restyle both. `.form-control` is the worse half — it hardcodes `background:
   #fff` while taking `color: var(--base-text)`, so in dark a broker typed white
   on white and could not read what they had entered. Restated under `.onb-s`,
   the same containment the referral field groups above use. */
.dark .onb-s .form-label { color: var(--ink); }

.dark .onb-s .form-control {
  background: var(--surf);
  border-color: var(--n200);
  color: var(--ink);
}

/* The exit pill. Its rules further up match on `.onboarding-body:has(.onb-s)`,
   which is OUTSIDE .onb-s, so the ramp above does not reach it and its three
   `var(--n200, #dddddd)`-style fallbacks are what actually render — the fallback
   being the whole point of writing them that way. They resolve to the light
   values on a dark nav, and they out-specify onboarding.css's own
   `.dark .onboarding-body .onboarding-nav__exit`, so the pill needs saying here. */
.dark .onboarding-body:has(.onb-s) .onboarding-nav__exit {
  border-color: var(--base-border-secondary);
  color: var(--base-text-secondary);
}

.dark .onboarding-body:has(.onb-s) .onboarding-nav__exit:hover {
  background: var(--base-bg-high);
  color: var(--base-text);
}

/* The three.js fallback panel is a near-white gradient, which is a glaring slab
   on a dark page. Same two-stop shape, taken down to the surface ramp. */
.dark .onb-s__welcome-stage {
  background: linear-gradient(150deg, var(--base-bg-high), var(--base-bg-low));
}

/* ── The plan screen's fork ────────────────────────────────────────────────
   `plan` is the only screen with TWO ways forward: the CTA goes to review (the
   three-screen invite most brokers send) and the secondary walks into the six
   optional screens. The secondary rides on .onb-s__skip, which everywhere else means
   "decline this question", so beside a filled CTA it read as a throwaway and the
   six-screen jump looked like the stepper losing its place rather than a route the
   broker picked. Give it a real button box so the two read as two routes.

   Safe to key on the screen: `plan` is not skippable, so .onb-s__skip on it can only
   ever be the secondary action. Every other screen's Skip is untouched. */
[data-setup-shell-flow-value="SellerInvites::Flow"].onb-s--plan .onb-s__skip {
  padding: 15px 28px;
  border: 1px solid var(--n300, #d6d6d6);
  border-radius: 12px;
  color: var(--tx, #222);
  text-decoration: none;
  font-weight: 600;
}
[data-setup-shell-flow-value="SellerInvites::Flow"].onb-s--plan .onb-s__skip:hover {
  border-color: #0EA184;
  color: #0A6E5C;
}

/* ── The routing confirmation's figure ───────────────────────────────────────
   The one number on the screen, and the reason the chooser before it carries
   none: three dollar amounts side by side made that screen a price comparison.

   🚨 `.onb-s__figure`, NOT `.onb-s__proof`. That name belongs to the privacy
   proof card above, which itself had to be renamed off `.onb-s__confirm*` for
   exactly this kind of silent collision — the losing rule just quietly wins on
   a property nobody was looking at.

   A tinted block, evenly rounded on all four sides. NOT a stripe down one edge.
   Everything here reads the stepper's own ramp, so all three colour modes come
   free with the rest of the screen.
   ────────────────────────────────────────────────────────────────────────── */

.onb-s__figure {
  margin-top: 28px;
  padding: 20px 22px;
  border-radius: var(--radius-input);
  background: var(--n50);
}

.onb-s__figure-label {
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tx3);
}

.onb-s__figure-value {
  margin: 0;
  font-family: var(--font-display);
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.onb-s__figure-note {
  margin: 6px 0 0;
  font-size: 14px;
  color: var(--tx3);
}

/* The two reversibility facts, as the cards carried them. A hanging middot
   rather than a list marker, matching `.ref-route-fact` on the screen this
   replaces. */
.onb-s__facts {
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.onb-s__facts li {
  position: relative;
  padding-left: 16px;
  font-size: 15px;
  color: var(--tx2);
}

.onb-s__facts li::before {
  content: "·";
  position: absolute;
  left: 4px;
  color: var(--tx3);
}

/* ── The routing chooser's context banners ───────────────────────────────────
   Who introduced this practice, whether the decision is parked, and whether the
   seller's previous listing sets a precedent.

   🚨 These exist because the app's `.ref-banner` and `.affiliate-origin-notice`
   are in the TAILWIND bundle, and every stepper screen renders the onboarding
   layout, which links the marketing bundle instead. Reusing them here rendered
   bare paragraphs with no card and no tint. Anything moved onto a stepper screen
   needs its styling checked, not assumed.

   Tinted blocks, evenly rounded on all four sides. NOT stripes down one edge.
   ────────────────────────────────────────────────────────────────────────── */

/* 🚨 `--surf` with a hairline, NOT `--n50`. The stepper's page ground is already
   a near-white grey, so an `--n50` block on it is invisible — it rendered as a
   bare paragraph and looked like the styling had failed. Same card language the
   option rows use (`--surf` + `--n200`), so the notice reads as a quiet sibling
   of the choices rather than as loose text above them. */
.onb-s__notice {
  margin: 0 0 14px;
  padding: 14px 16px;
  border: 1px solid var(--n200);
  border-radius: var(--radius-input);
  background: var(--surf);
  font-size: 14px;
  line-height: 1.55;
  color: var(--tx2);
}

.onb-s__notice strong { color: var(--ink); font-weight: 600; }

.onb-s__precedent {
  margin: 0 0 18px;
  padding: 18px 20px;
  border-radius: var(--radius-input);
  background: var(--g50);
}

.onb-s__precedent-title {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
}

.onb-s__precedent-body {
  margin: 0 0 12px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--tx2);
}

/* 🚨 `--gd`, the DARKEST of the stepper's three greens, and measured rather than
   guessed. White on `--g` (#0ea184) is 3.26:1 and on `--gh` (#0b8770) is 4.46:1
   — both under the 4.5 AA floor, the second by four hundredths, which is exactly
   the kind of near-miss that gets waved through by eye. `--gd` (#0a6e5c) is
   6.18:1.
   
   Not the app's `.btn-primary`: that class lives in the Tailwind bundle and has
   no styling on this layout at all. */
.onb-s__precedent-action {
  padding: 9px 18px;
  border: 0;
  border-radius: 999px;
  background: var(--gd);
  color: #fff;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.18s ease;
}

.onb-s__precedent-action:hover { background: var(--gh); color: #fff; }

/* 🚨 `--g` in dark, not `--gd`. The dark block remaps `--gd` to
   `var(--text-primary)` because "green as an accent GLYPH is what --gd is in
   five of its six uses" — it is a TEXT colour there, and white on it measures
   1.79:1. `--g` is the background token in dark (`--bg-primary`), which is what
   `.onb-s__next` uses one band below. The same value cannot serve as an icon
   colour and a button fill once the page behind it is dark; that note is in the
   dark block already, and this is the sixth use it warns about. */
.dark .onb-s .onb-s__precedent-action { background: var(--g); }
.dark .onb-s .onb-s__precedent-action:hover { background: var(--gh); }

/* ══════════════════════════════════════════════════════════════════════════
   "Who sees what" — the location screen's privacy sheet
   ══════════════════════════════════════════════════════════════════════════
   🚨 STYLED FROM SCRATCH, because stepper screens render the onboarding layout
   and that links the MARKETING bundle, not app/assets/tailwind. `.modal`,
   `.slideover` and `.connection-request-modal` have no styling on this page: a
   dialog using them opens, greys the screen with its backdrop, and shows an
   invisible panel. Everything below is `.onb-s__*` for that reason.

   Everything reads the stepper's own ramp (--surf, --ink, --tx2, --n100), so all
   three colour modes come free with the rest of the screen. */

.onb-s__trust-link {
  margin-left: auto;
  flex: none;
  font-size: 14px;
  font-weight: 600;
  color: var(--gd);
  text-decoration: underline;
}

.onb-s__sheet {
  width: min(940px, calc(100vw - 32px));
  max-height: min(840px, calc(100dvh - 32px));
  padding: 0;
  border: 0;
  border-radius: 20px;
  background: var(--surf);
  color: var(--ink);
  overflow: hidden;
}

/* A dialog is display:none until open; the flex column has to wait for that or
   the closed sheet lays itself out and the page grows behind everything. */
.onb-s__sheet[open] { display: flex; flex-direction: column; }
.onb-s__sheet::backdrop { background: rgba(20, 24, 22, 0.38); }

.onb-s__sheet-head {
  flex: none;
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 24px 28px 18px;
}

.onb-s__sheet-title {
  margin: 0 0 5px;
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.onb-s__sheet-sub { margin: 0; font-size: 15px; line-height: 1.55; color: var(--tx2); }

/* 🚨 A BARE CROSS, top-right. This was a 34px filled disc (--n50, --n100 on hover),
   which read as a button competing with the sheet's own actions, and it sat immediately
   after the title text rather than in the corner: the heading block is `.min-w-0`, a
   Tailwind utility, and stepper screens link the MARKETING bundle and not
   app/assets/tailwind, so that class is inert here and never made the block flexible.
   `margin-left: auto` puts the cross in the corner without depending on it.

   No background and no border in any state, hover included. The colour shift is the
   whole affordance. */
.onb-s__sheet-close {
  flex: none;
  margin-left: auto;
  width: 34px;
  height: 34px;
  border: 0;
  background: transparent;
  color: var(--tx3);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.onb-s__sheet-close:hover,
.onb-s__sheet-close:focus-visible { background: transparent; color: var(--ink); }
.onb-s__sheet-close svg { width: 17px; height: 17px; }

.onb-s__sheet-body { flex: 1; overflow: auto; padding: 0 28px 8px; }

/* Right-aligned: "Got it" is the primary action and belongs in the corner, with the
   privacy-policy link immediately to its left. */
.onb-s__sheet-foot {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 16px;
  padding: 18px 28px 22px;
}

.onb-s__sheet-link { font-size: 14px; font-weight: 600; color: var(--gd); text-decoration: underline; }

/* ── The audience toggle ─────────────────────────────────────────────────── */

.onb-s__aud {
  display: inline-flex;
  gap: 6px;
  padding: 5px;
  border-radius: 999px;
  background: var(--n50);
  margin-bottom: 20px;
  max-width: 100%;
  overflow-x: auto;
}

.onb-s__aud-tab {
  flex: none;
  border: 0;
  border-radius: 999px;
  padding: 10px 18px;
  background: transparent;
  color: var(--tx2);
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.onb-s__aud-tab.is-on { background: var(--surf); color: var(--ink); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }

.onb-s__aud-grid {
  display: grid;
  grid-template-columns: 320px minmax(0, 1fr);
  gap: 24px;
  align-items: start;
  margin-bottom: 4px;
}

.onb-s__aud-card {
  border: 1px solid var(--n200);
  border-radius: 16px;
  overflow: hidden;
  background: var(--surf);
}

.onb-s__aud-map {
  position: relative;
  height: 168px;
  background: linear-gradient(135deg, var(--g50), var(--n100));
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 🚨 `--scale` is a PROPORTION, not a radius. The circle illustrates how much
   the mask loosens between audiences; quoting miles on it would invent a second
   radius for the product to keep in step with INTAKE_PROXIMITY_RADIUS. */
.onb-s__aud-circle {
  display: block;
  width: calc(116px * var(--scale, 1));
  height: calc(116px * var(--scale, 1));
  border-radius: 999px;
  background: rgba(14, 161, 132, 0.18);
  border: 2px solid var(--g);
  transition: width 0.25s ease-out, height 0.25s ease-out;
}

.onb-s__aud-mapnote {
  position: absolute;
  left: 14px;
  bottom: 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--tx2);
  background: var(--surf);
  border-radius: 999px;
  padding: 5px 11px;
}

.onb-s__aud-cardbody { padding: 18px 20px 20px; }

.onb-s__aud-cardtitle {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  color: var(--ink);
}

.onb-s__aud-cardtitle.is-revealed { color: var(--gd); }
.onb-s__aud-cardloc { margin: 0 0 14px; font-size: 14px; color: var(--tx2); }

.onb-s__aud-fields { display: flex; flex-direction: column; gap: 10px; margin: 0; }
.onb-s__aud-field { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.onb-s__aud-field dt { font-size: 14px; color: var(--tx3); }
.onb-s__aud-field dd { margin: 0; font-size: 14px; font-weight: 600; }
.onb-s__aud-field dd.is-masked { color: var(--tx3); }
.onb-s__aud-field dd.is-revealed { color: var(--gd); }

.onb-s__aud-note { border-radius: 14px; padding: 16px 18px; background: var(--n50); }
.onb-s__aud-note.is-revealing { background: var(--g50); }

.onb-s__aud-notetitle {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 5px;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}

.onb-s__aud-notetitle svg { width: 17px; height: 17px; flex: none; }
.onb-s__aud-notebody { margin: 0; font-size: 14px; line-height: 1.6; color: var(--tx2); }

/* ── The matrix ──────────────────────────────────────────────────────────── */

/* 🚨 The TABLE scrolls, the page does not. Four columns of copy do not fit a
   375px viewport and never will, so the overflow is given somewhere to go
   rather than being clipped (which `overflow-x: clip` would do silently). */
.onb-s__matrix-scroll { overflow-x: auto; margin: 20px 0; }

.onb-s__matrix { width: 100%; min-width: 520px; border-collapse: collapse; }
.onb-s__matrix-caption { text-align: left; padding-bottom: 10px; font-size: 13px; color: var(--tx3); }

.onb-s__matrix th,
.onb-s__matrix td { padding: 10px 8px; border-top: 1px solid var(--n100); }

.onb-s__matrix thead th {
  border-top: 0;
  padding-bottom: 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--tx3);
  text-align: center;
}

.onb-s__matrix thead th:first-child,
.onb-s__matrix tbody th { text-align: left; }
.onb-s__matrix tbody th { font-size: 14px; font-weight: 500; color: var(--ink); }
.onb-s__matrix td { text-align: center; }

.onb-s__matrix-pill {
  display: inline-flex;
  align-items: center;
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

.onb-s__matrix-pill.is-shown { background: var(--g50); color: var(--gd); }
.onb-s__matrix-pill.is-partial { background: var(--n100); color: var(--tx2); }
.onb-s__matrix-pill.is-hidden { background: var(--n50); color: var(--tx3); }

/* ── The three promises ──────────────────────────────────────────────────── */

.onb-s__promises { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; margin-bottom: 8px; }
.onb-s__promise { background: var(--n50); border-radius: 14px; padding: 16px 18px; }

.onb-s__promise-title {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
}

.onb-s__promise-body { margin: 0; font-size: 13px; line-height: 1.55; color: var(--tx2); }

@media (max-width: 900px) {
  .onb-s__aud-grid { grid-template-columns: minmax(0, 1fr); }
  .onb-s__promises { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
  .onb-s__sheet-head,
  .onb-s__sheet-foot { padding-left: 18px; padding-right: 18px; }
  .onb-s__sheet-body { padding-left: 18px; padding-right: 18px; }
  .onb-s__sheet-foot { flex-wrap: wrap; }
}

/* "Step 2 of 7", beside the forward CTA. The act segments above the bar show
   progress as a shape; this says it as a number. Quiet on purpose: it is
   orientation, not an instruction. */
.onb-s__count {
  font-size: 14px;
  color: var(--tx3);
  white-space: nowrap;
}

@media (max-width: 460px) {
  /* The label goes before the CTA does. On a narrow phone the button is the
     thing that has to stay reachable. */
  .onb-s__count { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   FORM FIELDS, THE PROPOSAL COMPOSER AND THE REFERRAL SLATE
   Added 2026-09-02 with `Routing::Flow`'s compose step.

   🚨 WHY THIS EXISTS AT ALL, because it looks like duplication and is not.
   The broker screens that moved into this shell brought their markup with them
   and left their stylesheet behind: `.ref-slate`, `.ref-slate-row`,
   `.ref-slate-add-row`, `.ref-overline`, `.btn`, `.connection-request-*` are all
   defined under `app/assets/tailwind/`, and the onboarding layout links the
   MARKETING bundle. Measured on the live recipient screen: NINE classes had no
   matching rule anywhere in the document, so the ranked slate rendered as a bare
   <ol> with bare buttons. That is not a taste problem and no amount of markup
   tidying fixes it.

   So the components are restated here in the stepper's own language and tokens.
   The rule from `broker_steps.css` and the privacy sheet holds: if a class is
   defined under app/assets/tailwind/, it does not exist on a stepper page.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── A labelled field region ───────────────────────────────────────────────
   One question per region, its label, its one-line hint, and its control. Used
   by the composer and the recipient screen so the two read as one form system. */
.onb-s__fieldset {
  margin-top: 32px;
}

.onb-s__fieldset:first-child {
  margin-top: 8px;
}

/* Label on the left, an optional escape hatch on the right ("Edit template").
   `align-items: start` so a two-line hint does not drag the link down with it. */
.onb-s__fhead {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.onb-s__flabel {
  display: block;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 17px;
  font-weight: 650;
  color: var(--ink);
}

.onb-s__fhint {
  margin-top: 4px;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--tx3);
}

/* A quiet secondary link in a field header. Text only: per the house rules a
   hover is a colour change, never a fill. */
.onb-s__flink {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  padding-top: 2px;
  font-size: 14px;
  font-weight: 600;
  color: var(--gd);
  text-decoration: none;
}

.onb-s__flink:hover { color: var(--gh); text-decoration: underline; }

/* ── Compose / Preview ─────────────────────────────────────────────────────
   Drives connection_request_form_controller, which toggles `is-active` and the
   panes' `hidden`. The controller is class-agnostic, so only the skin is here. */
.onb-s__tabs {
  display: inline-flex;
  gap: 4px;
  margin: 16px 0 10px;
  padding: 4px;
  border-radius: var(--radius-pill, 999px);
  background: var(--n50);
}

.onb-s__tab {
  padding: 7px 16px;
  border: 0;
  border-radius: var(--radius-pill, 999px);
  background: transparent;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--tx3);
  cursor: pointer;
}

.onb-s__tab:hover { color: var(--ink); }

.onb-s__tab.is-active {
  background: var(--surf);
  color: var(--ink);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
}

/* ── The rich-text frame ───────────────────────────────────────────────────
   🚨 MATCHED TO /brokering/profile/sections/connection, deliberately, because it
   is the same component and it looked like a different one. The toolbar SKIN
   (icons, flat button row, group hairlines) is shared code now —
   shared/trix_toolbar.css, linked by compose.html.erb — so only the FRAME lives
   here, and the frame is where the two surfaces genuinely differ: the profile
   editor has a whole page and grows to 55vh, this one shares a fixed-height
   stepper with a commission field and a footer.

   Copied from the reference rather than invented: a TRANSPARENT toolbar sitting
   on the page surface (the icons merge into it, GitHub-comment style, instead of
   sitting on a distinct grey band), the border on the editor alone, and the
   content scrolling inside the editor rather than growing the box. */
.onb-s__rte trix-toolbar {
  padding: 6px 0;
  border: 0;
  background: transparent;
}

.onb-s__rte trix-editor {
  display: block;
  /* 🚨 !important, and not for specificity: trix.css ships UNLAYERED
     `trix-editor { min-height: 5em }`, and an unlayered NORMAL declaration beats
     a layered one at any specificity. The reference editor carries the same
     comment for the same reason. */
  min-height: 175px !important;
  padding: 14px 16px;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-input);
  background: var(--surf);
  font-size: 15px;
  line-height: 1.62;
  color: var(--tx);
  /* Grow to the cap, then scroll internally. The toolbar is a SIBLING, so it
     stays pinned above the scroll rather than scrolling away with the text. */
  overflow-y: auto;
  resize: vertical;
}

.onb-s__rte trix-editor:focus {
  border-color: var(--g);
  outline: none;
}

.onb-s__rte trix-editor:empty::before { color: var(--tx4); }

/* ── The seller's-eye preview ──────────────────────────────────────────────── */
.onb-s__preview { margin-top: 4px; }

.onb-s__preview-eyebrow {
  margin-bottom: 10px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tx4);
}

/* A tinted block, evenly rounded, hairline on ALL sides. Not a stripe. */
.onb-s__preview-card {
  padding: 20px;
  border: 1px solid var(--n200);
  border-radius: var(--radius-tile);
  background: var(--n50);
}

.onb-s__preview-from {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--n200);
}

.onb-s__preview-from strong {
  display: block;
  font-size: 15px;
  font-weight: 650;
  color: var(--ink);
}

.onb-s__preview-sub {
  display: block;
  font-size: 13.5px;
  color: var(--tx3);
}

.onb-s__preview-avatar {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: 50%;
  background: var(--g50);
  font-size: 14px;
  font-weight: 700;
  color: var(--gd);
}

.onb-s__preview-body {
  padding-top: 14px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--tx);
}

/* ── A percentage input ────────────────────────────────────────────────────
   The suffix is a real element rather than a ::after so it can sit inside the
   border and be hidden from the accessibility tree. */
.onb-s__pct {
  position: relative;
  max-width: 200px;
  margin-top: 14px;
}

.onb-s__pct-input {
  width: 100%;
  height: 56px;
  padding: 0 44px 0 18px;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-input);
  background: var(--surf);
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 20px;
  font-weight: 650;
  color: var(--ink);
  outline: none;
}

.onb-s__pct-input:focus { border-color: var(--g); }

.onb-s__pct-suffix {
  position: absolute;
  right: 18px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 18px;
  font-weight: 650;
  color: var(--tx3);
  pointer-events: none;
}

/* A warning that is a tinted block with an even radius, NOT a left stripe. */
.onb-s__notice--warn {
  border-color: #e7d7a8;
  background: #fdf6e3;
  color: var(--tx);
}

/* ── The compose step: the EDITOR is what fits the viewport ────────────────
   🚨 REVERSED from the first attempt, on the requester's call. That version made
   the whole screen fill — `overflow: hidden` on the shell's scroller and a flex
   chain down to the editor — so the commission field was always on screen and the
   editor took whatever was left. At 900px tall "whatever was left" was 221px,
   which is a cramped box for the one piece of writing this screen exists for.

   The rule now: THE EDITOR IS SIZED TO THE VIEWPORT and the commission field sits
   below the fold, reached by an ordinary page scroll. Long text still scrolls
   inside the editor, so the box itself never grows.

   🚨 THE COMMISSION FIELD'S LABEL IS INSIDE THE SUBTRAHEND. ITS INPUT IS NOT.
   Three passes landed here and the middle two were both wrong in opposite
   directions: putting the whole field below the fold hid a required field the
   broker would never think to scroll for, and reserving room for all of it
   (label + hint + a 56px input, about 150px) took that space off the one thing the
   screen exists for. Seeing the LABEL is what makes the broker scroll; it does not
   need the input in view to know it is there.

   So the subtrahend covers the heading and the hint and stops. Measured on the real
   screen rather than guessed: at 900px tall the editor's top sits at 320 (nav 64 +
   the scroller's 8 + title/sub 132 + tab row 56 + toolbar 44 + margins) and the
   footer starts at 812. A `max()` floor keeps the editor usable on a short window,
   where the page simply scrolls as it always did. */
.onb-s--compose .onb-s__rte trix-editor {
  height: max(200px, calc(100dvh - 505px));
  max-height: none;
}

/* The tab strip and the template link share one row. Was two stacked blocks: a
   67px field heading, then the tabs. */
.onb-s__editor-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.onb-s__editor-bar .onb-s__tabs { margin: 0; }

/* The field keeps a real <label> for assistive tech now that no visible one
   names the editor. */
.onb-s__sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* 🚨 THE PREVIEW PANE IS THE SAME LENGTH AS THE COMPOSE PANE, not merely capped.
   It is a sibling in normal flow, so with no height of its own it rendered at the
   FULL height of the pitch, taller than the editor it replaced, and overlapped the
   commission field below it.

   Capping it was the first fix and it was not enough: `max-height` lets a short
   pitch shrink the card, so the two tabs were different lengths and everything
   below jumped as the broker switched between them. It is a fixed `height` with
   its own scroll now, and the arithmetic is derived from the editor's rather than
   guessed alongside it.

   The +12 is the difference between what sits ABOVE each: the compose pane has the
   44px Trix toolbar, the preview pane has its 32px "what the seller sees" eyebrow.
   Giving the card those 12px back makes the two panes end on the same line. */
.onb-s--compose .onb-s__preview-card {
  height: max(212px, calc(100dvh - 493px));
  overflow-y: auto;
}

/* ── The brokerage lookup: field + Add, on one row ─────────────────────────
   The email box and the button that acts on it. They were two rows with a
   counter between them, which put the action furthest from the thing it acts
   on and made the second row read as another field. */
.onb-s__lookup {
  display: flex;
  align-items: stretch;
  gap: 10px;
  margin-top: 14px;
}

.onb-s__lookup-field {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
}

.onb-s__lookup-icon {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  color: var(--tx4);
  pointer-events: none;
}

.onb-s__lookup-icon svg { width: 17px; height: 17px; }

.onb-s__lookup-input {
  width: 100%;
  height: 52px;
  padding: 0 16px 0 44px;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-input);
  background: var(--surf);
  font: inherit;
  font-size: 15px;
  color: var(--ink);
  outline: none;
}

.onb-s__lookup-input::placeholder { color: var(--tx4); }
.onb-s__lookup-input:focus { border-color: var(--g); }

/* 🚨 The DISABLED state is the whole point of this control: it is live only once
   the lookup has resolved a brokerage, so it doubles as the readout for whether
   there is anything to add. --gd rather than --g, because white on --g is 3.26:1
   and this button carries a word rather than an icon. */
.onb-s__lookup-add {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  padding: 0 20px;
  border: 0;
  border-radius: var(--radius-input);
  background: var(--gd);
  color: #fff;
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 15px;
  font-weight: 650;
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.onb-s__lookup-add:hover:not(:disabled) { background: #08594a; }

.onb-s__lookup-add:disabled {
  background: var(--n100);
  color: var(--tx4);
  cursor: not-allowed;
}

/* ── The shortlist ────────────────────────────────────────────────────────── */

/* "2 of 4". Beside the heading, so the noun it counts is already on screen. */
.onb-s__slate-count {
  flex: none;
  padding: 4px 12px;
  border-radius: var(--radius-pill, 999px);
  background: var(--n50);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--tx2);
  white-space: nowrap;
}

.onb-s__slate-empty {
  margin-top: 14px;
  padding: 20px;
  border: 1px dashed var(--n200);
  border-radius: var(--radius-tile);
  font-size: 14.5px;
  color: var(--tx3);
  text-align: center;
}

.onb-s__slate {
  display: grid;
  gap: 10px;
  margin: 14px 0 0;
  padding: 0;
  list-style: none;
}

/* A card per brokerage, hairline on all four sides and evenly rounded. */
.onb-s__slate-row {
  padding: 14px 16px;
  border: 1px solid var(--n200);
  border-radius: var(--radius-tile);
  background: var(--surf);
}

.onb-s__slate-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* The rank IS the ranking, and the seller reads it in this order, so it gets a
   real badge rather than a list marker. Filled by referral-slate#render. */
.onb-s__slate-rank {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: none;
  border-radius: 50%;
  background: var(--g50);
  font-family: var(--font-display, "Figtree", Inter, sans-serif);
  font-size: 13px;
  font-weight: 700;
  color: var(--gd);
}

/* The shortlist's own copy of the network row's mark, so the brokerage a broker just
   staged is the same face on both sides of the screen. Mirrored by
   referral_slate_controller#rowHtml. */
.onb-s__slate-mark {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--n100);
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 800;
  color: var(--tx2);
}

.onb-s__slate-mark.is-logo {
  object-fit: contain;
  background: var(--base-bg-low, #fff);
}

.onb-s__slate-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.onb-s__slate-flag {
  flex: none;
  padding: 3px 9px;
  border-radius: var(--radius-pill, 999px);
  background: var(--g50);
  font-size: 12px;
  font-weight: 600;
  color: var(--gd);
}

.onb-s__slate-actions {
  display: inline-flex;
  gap: 4px;
  flex: none;
}

/* Hover is a BORDER step, never a fill: these sit on a white card and any grey
   fill would read as disabled next to the genuinely disabled end-of-list arrow. */
.onb-s__slate-btn {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 1px solid var(--n200);
  border-radius: 8px;
  background: transparent;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  color: var(--tx2);
  cursor: pointer;
}

.onb-s__slate-btn:hover:not(:disabled) { border-color: var(--tx3); color: var(--ink); }

.onb-s__slate-btn:disabled { color: var(--n300); cursor: not-allowed; }

.onb-s__slate-btn--remove:hover:not(:disabled) {
  border-color: var(--err-bd);
  color: var(--err-tx);
}

.onb-s__slate-intro {
  width: 100%;
  margin-top: 10px;
  padding: 10px 12px;
  border: 1.5px solid var(--n200);
  border-radius: var(--radius-input);
  background: var(--surf);
  font: inherit;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--tx);
  resize: vertical;
  outline: none;
}

.onb-s__slate-intro::placeholder { color: var(--tx4); }
.onb-s__slate-intro:focus { border-color: var(--g); }

.onb-s__slate-limit {
  margin-top: 12px;
  font-size: 14px;
  color: var(--tx3);
}

@media (max-width: 560px) {
  /* The Add button drops under the field rather than squeezing it. */
  .onb-s__lookup { flex-wrap: wrap; }
  .onb-s__lookup-field { flex-basis: 100%; }
  .onb-s__lookup-add { width: 100%; height: 48px; justify-content: center; }

  /* 🚨 The row head carries FIVE things — rank, name, the top-pick flag and
     three buttons — and at 375 they need about 420px. Measured: the card, its
     head, the action group, the remove button and the textarea all reported
     right edges past the viewport. Wrapping the actions onto their own line is
     what fits; truncating the name further would leave a brokerage identified by
     four characters, which is the one thing on the row that has to stay
     readable.

     `flex: 1 1 60%` is what forces the wrap: `min-width: 0` alone lets the name
     shrink indefinitely, so the row never wraps and overflows instead. */
  .onb-s__slate-name { flex: 1 1 60%; }
  .onb-s__slate-head { flex-wrap: wrap; row-gap: 10px; }
  .onb-s__slate-actions { margin-left: auto; }

  /* 🚨 Trix builds its own toolbar and packs fourteen buttons into it, which
     needs about 430px; measured at 375 the history group hung off the right
     edge. It SCROLLS rather than wraps: wrapping doubles the toolbar's height
     above an editor that is already the tallest thing on the screen, and the
     button order is Trix's, so a second row would break mid-group. Same rule the
     rest of the app follows for wide content — scroll inside your own box, never
     widen the page. */
  .onb-s__rte trix-toolbar { overflow-x: auto; }
  .onb-s__rte trix-toolbar .trix-button-row { flex-wrap: nowrap; width: max-content; }
}
