/* Seenfold, the campaign builder: /build/1 .. /build/4.
 *
 * THIS IS THE OLD "START A CAMPAIGN" WIZARD'S STYLESHEET, PORTED.
 *
 * The wizard that is live at /start-campaign (content/start-campaign.html) is the
 * checkout this flow replaces, and a buyer who has seen one and then the other
 * must not be able to tell that anything under them changed. So the class
 * vocabulary here is that page's vocabulary, rule for rule: `.obh` for the
 * wordmark bar, `.obsteps` for the step rail, `.obgrid` for the two columns,
 * `.wiz` for the card, `.obside` / `.obprev` for the order summary, `.plats`,
 * `.seg`, `.field`, `.hint` for the controls inside it.
 *
 * That is not only a look. app.css already knows these names: it paints `.wiz` in
 * the dark theme, sets the placeholder colour on `.wiz .field`, and gives
 * `.pv-row .k`, `.pv-total .k`, `.pv-total-sub` and `.vhint` their weight.
 * Renaming the parts would have quietly dropped all of it.
 *
 * WHAT IS NOT PORTED, because the product no longer has it: the reach `.plan`
 * rows (a stepper sells reach now), the footage link rows, the hours field, the
 * logo uploader and the delivery-window segment. What is added in their place is
 * `.numstep` (the views stepper), `.plat-sub` (the rate under a term chip) and
 * `.chipx` (removing a Never chip the buyer typed), all written in the same house
 * style as the controls they sit beside.
 *
 * Loaded by app/build/layout.tsx and by nothing else. Every colour is an app.css
 * token, so the dark theme costs nothing.
 */

/* ------------------------------------------------------------------ shell */

/* The site nav is rendered by the root layout, which this lane does not own. A
   checkout has its own one-line masthead, so the site's is suppressed for as long
   as a builder root is on the page. Keyed on `.obpage` rather than written
   unconditionally because React keeps a hoisted stylesheet in the document after
   the route that asked for it unmounts: an unconditional rule would go on hiding
   the nav on whatever page the buyer navigates to next. Same shape, and the same
   reasoning, as campaign.css's `body:has(.sfc)`. */
body:has(.obpage) .masthead,
body:has(.obpage) .sfoot { display: none !important; }

/* Nothing in the builder scrolls sideways, at any width. */
body:has(.obpage) { overflow-x: hidden; }

.obpage {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  /* Named once and subtracted from .obmain below, so the first screen still fits
     without growing a scrollbar. */
  --ob-support-h: 34px;
}

/* The bar keeps app.css's nav chrome (white, hairline) and only states what is
   different here: the wordmark alone, centred, and NOT sticky. Pinning the
   wordmark while the step rail directly under it scrolled away pulled the two
   halves of one masthead apart the moment you moved. `top` has to go with
   `position` or the offset stays on an element that no longer uses it. */
/* No rule under the wordmark: the support line sits right beneath it and the
   two read as one header, so a line between them cut the header in half.
   !important, deliberately: app.css draws that rule on the site nav from half a
   dozen rules keyed on theme, scroll state and card mode, and this header is
   the one place on the site where none of them should win. */
.obh { border-bottom: 0 !important; }
/* The header scrolls away with the page. app.css makes every .appnav sticky at
   top 0, and that rule loads after this sheet, so a bare .obh loses to it: the
   header then pinned itself over the top of the sticky order summary and cut
   its title off. The full class chain outranks it. */
header.appnav.mktnav.obh { background: var(--white); box-shadow: none; position: static; top: auto; }
.obh-in { justify-content: center; position: relative; }

/* Under the bar rather than in it. The bar is one row built to hold one centred
   wordmark; a second line inside it would sit beside the mark as a competing link
   in a checkout. New tab on purpose: a same-tab booking page would take the buyer
   off a part-filled form. */
.obsupport {
  height: var(--ob-support-h); display: flex; align-items: center; justify-content: center;
  gap: 6px; font-size: 13px; color: var(--ink-3); padding: 0 16px; text-align: center;
}
.obsupport a { color: var(--brand); font-weight: 600; border-bottom: 1px solid var(--brand-line); }
.obsupport a:hover { border-bottom-color: var(--brand); }

/* Top-aligned, not centred: the card's height changes with the step, and centring
   moved the whole page up and down every time you pressed Continue. 55px is the
   bar above, 54px of row plus its hairline. min-height rather than height, so a
   step taller than the viewport grows the page instead of being clipped. */
.obmain { min-height: calc(100vh - 55px - var(--ob-support-h)); display: flex; align-items: flex-start; }
.obmain > .wrap { width: 100%; padding-top: clamp(18px, 2.2vh, 28px); padding-bottom: clamp(24px, 4vh, 48px); }
.obinner { max-width: 980px; margin: 0 auto; }

/* ------------------------------------------------------------- the step rail */

.obsteps { display: flex; align-items: center; gap: 8px; overflow-x: auto; padding-bottom: 14px; margin-bottom: clamp(18px, 2.4vh, 32px); border-bottom: 1px solid var(--line); }
.obstep { display: flex; align-items: center; gap: 8px; font-size: 13.5px; color: var(--ink-3); white-space: nowrap; flex: 0 0 auto; transition: color .25s ease; }
.obconn { flex: 1 1 auto; min-width: 16px; height: 2px; border-radius: 2px; background: var(--line); position: relative; overflow: hidden; }
/* The rail fills in the brand purple: progress is a selected state, and on this
   site a selected state is purple. */
/* --obfill is how much of the connector after the ACTIVE dot is filled, set
   inline by the rail for a step that has a sequence inside it (Start's five
   questions fill it a fifth at a time). Unset, it is 0. */
.obconn::after { content: ""; position: absolute; inset: 0; background: var(--action); transform: scaleX(var(--obfill, 0)); transform-origin: left center; }
.obconn.done::after { transform: scaleX(1); }
.obstep .n { position: relative; width: 22px; height: 22px; border-radius: 50%; border: 1.5px solid var(--line-2); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; transition: border-color .28s ease, background .28s ease, color .28s ease; }
/* obnn / obck / oblb rather than nn / ck / lb: app.css already owns a .ck and a
   .lb, and the bare names inherit padding and a stray bordered pill. */
.obstep .n i { font-style: normal; position: absolute; inset: 0; padding: 0; display: flex; align-items: center; justify-content: center; }
.obstep .n .obck { opacity: 0; transform: scale(0.4); }
.obstep.done .n .obnn { opacity: 0; transform: scale(0.6); }
.obstep.done .n .obck { opacity: 1; transform: scale(1); }
.obstep .oblb { border: 0; border-radius: 0; overflow: visible; display: inline; }
.obstep.active { color: var(--ink); font-weight: 600; }
/* --action-ink, not --white: --white is the card surface token and flips to a
   dark grey in the dark theme, which would put dark type on the purple dot. */
.obstep.active .n { border-color: var(--action); background: var(--action); color: var(--action-ink); }
.obstep.done { color: var(--ink); }
.obstep.done .n { border-color: var(--action); background: var(--action); color: var(--action-ink); }
.obstep.done:hover .n { opacity: 0.8; }
/* A completed step is a way back, so it is a real link rather than a div with a
   click handler on it. It carries none of a link's chrome. */
.obsteplink { display: flex; align-items: center; gap: 8px; background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; text-decoration: none; }
.obsteplink:focus-visible { outline: 2px solid var(--action); outline-offset: 3px; border-radius: 6px; }

/* --------------------------------------------------------------- the layout */

.obgrid { display: grid; grid-template-columns: 1fr 340px; gap: 40px; align-items: start; }
/* minmax(0,...) not 1fr: a plain 1fr floors at the column's min-content width, and
   the steps min out near 468px, which pushed the whole page sideways on a phone. */
@media (max-width: 860px) { .obgrid { grid-template-columns: minmax(0, 1fr); gap: 28px; } }
/* 20px of ground under the top of the viewport. Nothing is pinned above it. */
.obside { position: sticky; top: 20px; }
.obform { min-width: 0; }
.pv-peek { display: none; }

/* Back sits above the card rather than inside it, so every step opens on its own
   question. It goes invisible on step 1 and keeps its space, or the card jumps. */
.obback { min-height: 26px; display: flex; align-items: center; gap: 12px; margin: 0 0 10px; }
.wiz-back { display: inline-flex; align-items: center; background: none; border: none; color: var(--ink-3); font-family: var(--sans); font-size: 14px; cursor: pointer; padding: 2px 0; margin: 0; text-decoration: none; }
.wiz-back:hover { color: var(--ink); }
.obcount { margin-left: auto; font-size: 12.5px; color: var(--ink-3); }

/* ---- phones: the order summary is a sheet, not a footer ---------------------
   The price moves every time you answer something, so it is the one number that
   must never require a scroll. The total is always on screen and the breakdown is
   one tap away. No duplicate total: the real .pv-money block is re-ordered up
   beside the handle rather than a second copy being mirrored into it, so there is
   nothing that can drift out of sync with the figure being charged. */
@media (max-width: 860px) {
  /* The SURFACE is .obside, not .obprev: the payment marks and the guarantee line
     are its siblings, and painting only .obprev left them floating transparently
     over the form. top: auto is load-bearing -- the desktop rule is
     `sticky; top: 20px`, and top survives the switch to fixed, so with bottom: 0
     also set the sheet was anchored at both edges and stretched to the full
     height of the screen. */
  .obside { position: fixed; top: auto; left: 0; right: 0; bottom: 0; z-index: 45; margin: 0;
    background: var(--white); border-top: 1px solid var(--line);
    border-radius: 16px 16px 0 0; box-shadow: 0 -8px 28px -18px rgba(0,0,0,.55);
    max-height: 82vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
    padding: 14px 18px max(14px, env(safe-area-inset-bottom)); }
  /* .obside .obprev, not .obprev: the base rule is declared further down this
     file, so a bare class here loses to it on source order alone. */
  .obside .obprev { display: flex; flex-direction: column; margin: 0; padding: 0;
    background: none; border: 0; border-radius: 0; box-shadow: none; max-height: none; }
  .obside .payrow, .obside .bh-guar { margin-top: 14px; }
  .pv-peek { display: flex; align-items: center; justify-content: space-between; gap: 12px;
    order: -2; width: 100%; min-height: 44px; padding: 0 0 6px; margin: 0;
    background: none; border: 0; cursor: pointer; color: var(--ink-3);
    font-family: var(--sans); font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; }
  .pv-chev { width: 18px; height: 18px; flex: 0 0 auto; transition: transform .2s; }
  .obside.is-open .pv-chev { transform: rotate(180deg); }
  /* the money block rides up next to the handle so it is what stays on screen */
  .obside .obprev > .pv-money { order: -1; margin: 0; }
  /* .pv-total's top margin is the gap to the itemised rows above it in the
     desktop card. Collapsed, those rows are display:none, so it would be a fifth
     of a phone screen of nothing at the top of a bar that only shows a total. */
  .obside .obprev > .pv-money .pv-total { margin-top: 0; }
  .obside.is-open .obprev > .pv-money .pv-total { margin-top: 22px; }
  .obside .obprev > .pv-money .pv-views { margin-top: 0; }
  /* Collapsed, the bar shows one figure and it stays the total. */
  .obside:not(.is-open) .pv-views { display: none; }
  /* Before a reach is picked there is no total, and the block holds the rate
     instead. Collapsed, that rate is the bar's figure, set as a line rather than
     the purple block, or the bar would show nothing but its handle. */
  .obside:not(.is-open) .pv-money:not(:has(.pv-total)) .pv-views {
    display: flex; align-items: baseline; gap: 8px; margin: 0; padding: 0; background: none; border-radius: 0; }
  .obside:not(.is-open) .pv-money:not(:has(.pv-total)) .pv-views-n { font-size: 22px; color: var(--ink); line-height: 1.1; }
  .obside:not(.is-open) .pv-money:not(:has(.pv-total)) .pv-total-sub { display: block; margin: 0; color: var(--ink-3); font-size: 12.5px; font-weight: 500; }
  .obside:not(.is-open) .pv-money:not(:has(.pv-total)) .pv-subplats { display: none; }
  /* The card's own "Order summary" heading goes: the handle above it already says
     that, so opening the sheet printed it twice. */
  .obside .obprev > h4 { display: none; }
  /* Collapsed, the sub-line under the total goes and the rate takes the corner:
     that is the number that answers "is this a good deal" while you are still
     choosing. Stripped of the panel it wears in the open sheet. */
  .obside:not(.is-open) .pv-total-sub { display: none; }
  .obside:not(.is-open) .pv-cpm-val { display: flex; order: 0; margin: 2px 0 0;
    background: none; border: 0; border-radius: 0; padding: 0;
    font-size: 14px; font-weight: 600; }
  .obside:not(.is-open) .pv-cpm-val .pv-unit { font-size: 12px; }
  .pv-total-sub { text-align: right; }
  .obside:not(.is-open) { padding-top: 10px; padding-bottom: max(10px, env(safe-area-inset-bottom)); }
  /* Collapsed: the handle, the total, the pay button when it is offered, and the
     two lines that belong to that button. A disabled Fund & Start with its reason
     folded away inside the sheet, or a failed charge whose message is one tap
     out of sight, is the one thing this bar must not do. */
  .obside:not(.is-open) .obprev >
    :not(.pv-peek):not(.pv-money):not(.pv-cpm-val):not(.side-fund):not(.pv-reason):not(.bld-err) { display: none; }
  .obside:not(.is-open) .payrow, .obside:not(.is-open) .bh-guar { display: none; }
  /* The sheet covers the foot of the page, so the flow needs room to clear it.
     --sheet-h is measured and written by BuildShell, so the reserve tracks
     whatever the sheet actually is, open or shut, on any step. */
  .obmain { padding-bottom: calc(var(--sheet-h, 133px) + 28px); }
}

/* ------------------------------------------------------------ order summary */

.obprev { border: 1px solid var(--line); border-radius: 16px; background: var(--white); padding: 24px; }
/* Full strength, not the grey the other small-caps labels use: it is the card's
   title rather than a label inside it. */
.obprev h4 { font-size: 11.5px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink); margin: 0 0 16px; }
/* The rate build-up. Every add-on that moves the CPM gets its own line, so the
   headline below is never a number that appeared from nowhere. Space, not rules:
   three hairlines inside one small card read as more structure than a five-line
   summary has. */
.pv-build { margin: 0 0 22px; }
.pv-build:empty { display: none; }
.pv-bline { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 12.5px; color: var(--ink-3); padding: 3px 0; }
.pv-bline b { color: var(--ink-2); font-weight: 600; font-variant-numeric: tabular-nums; }
/* The base rate is a fact; the perks are things you chose, so they carry the
   accent. Purple here is what ties a "+$0.40" to the toggle that put it there. */
.pv-bline.perk b { color: var(--action); }
/* The rate is a unit price, a thing you check, while the reach is the thing being
   bought: a card gets one highlight and the views block below holds it. */
.pv-cpm-val {
  font-size: 23px; font-weight: 700; letter-spacing: -0.025em; color: var(--ink); line-height: 1;
  display: flex; align-items: baseline; gap: 7px;
}
.pv-cpm-val .pv-unit { font-size: 14px; font-weight: 500; letter-spacing: 0; color: var(--ink-3); }
.pv-num { display: inline-block; font-variant-numeric: tabular-nums; }
.pv-rows { margin-top: 22px; }
.pv-rows:empty { margin-top: 0; }
.pv-row { display: flex; justify-content: space-between; gap: 14px; font-size: 13.5px; line-height: 1.5; padding: 6px 0; }
.pv-row .k { color: var(--ink-3); flex: 0 0 auto; }
/* The campaign name is whatever the buyer typed, in a column about 30 characters
   wide. One line, clipped, rather than a wrap that pushes the total off the card. */
.pv-row .v { color: var(--ink); font-weight: 500; text-align: right; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The value sits in its own block so it can move on its own; it carries the
   clip so a long campaign name still ends in an ellipsis rather than a cut. */
.pv-set { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Solid --action, and --action flat: #9873F0, the one purple the site fills with.
   A filled block outranks a tinted one, which is the right order for the number
   the card is selling. Every .btn-primary already puts white on this exact
   purple; a bespoke purple in the one place the eye lands is the worse failure.
   The 44px figure clears the 3:1 large-text floor on it. */
.pv-views { margin-top: 30px; background: var(--action); border-radius: 14px; padding: 15px 16px 13px; }
.pv-views-n { font-size: 44px; font-weight: 700; letter-spacing: -0.03em; line-height: 1.06; color: #fff; }
/* Stepped by digit count, because this number grows by orders of magnitude and
   the column does not. The steps land all three near the same MEASURED width. */
.pv-views-n.len-10 { font-size: 40px; }
.pv-views-n.len-11 { font-size: 35px; }
/* White, no alpha, and a nudge in size and weight: on #9873F0 this line measures
   3.47:1, under the AA floor for text this size, and the brand colour is fixed,
   so size and weight are the only levers left. */
.pv-views .pv-total-sub { color: #fff; font-size: 13px; font-weight: 600; }
.pv-views .pv-subplats { color: #fff; }
/* The old page's `.is-est` pair is not ported. It swapped the reach figure for
   the rate during the stretch before a package had been picked, and this builder
   has no such stretch: the reach is carried in from the onboarding answers and is
   a real number from the first frame. */
.pv-total { display: flex; justify-content: space-between; align-items: baseline; margin-top: 20px; }
.pv-total .k { font-size: 13.5px; color: var(--ink-3); }
.pv-total .v { font-size: 23px; font-weight: 700; letter-spacing: -0.025em; color: var(--ink); }
/* Left on the rail: it is a sentence, not a value, and every label in the rows
   above begins at the left margin. The sheet keeps it right, where it sits
   directly under the total in a collapsed two-line block. */
.pv-total-sub { font-size: 12.5px; color: var(--ink-2); margin-top: 4px; line-height: 1.45; text-align: left;
  display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* PADDING, not margin: .obprev is display:block, so a bottom margin collapses
   against the note's own top margin and the larger of the two wins. */
.pv-money { padding-bottom: 8px; }
/* The marks hang on the card's right edge rather than trailing the sentence, and
   they draw in currentColor so the white line makes them white with it. */
.pv-subplats { display: inline-flex; align-items: center; gap: 9px; margin-left: 0; flex: 0 0 auto; align-self: center; }
.pv-subplats svg { width: 1.15em; height: 1.15em; display: block; flex: 0 0 auto; }
/* The note under the rail on step 1: the plan came from the answers, and the next
   screen is where it changes. */
.pv-note { margin: 12px 0 0; font-size: 12.5px; color: var(--ink-3); line-height: 1.45; }
.side-fund { width: 100%; margin-top: 20px; }
/* The line under Fund & Start is centred under the button it belongs to. */
.pv-foot { text-align: center; }
.side-fund[hidden] { display: none; }
/* A quote in flight softens the numbers by a hair. It does not replace them with
   a spinner: somebody pressing plus four times should read four prices. */
.is-pending { opacity: 0.55; transition: opacity .12s; }

/* Payment-method badges: the official card and wallet marks. They keep their
   brand colours in both themes, which is the point. Seven marks have to clear the
   rail in one row or the last one wraps alone, so it is a seven-column grid
   rather than a wrapping flex row: all seven scale together as the column narrows
   instead of dropping Afterpay onto a second row on its own. */
.payrow { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; align-items: center; max-width: 296px; margin: 22px auto 0; }
.payrow img { width: 100%; height: auto; display: block; border-radius: 4px; }
/* Three things stacked under the order card, so each step down needs its own air:
   card to marks, marks to the promise, promise to the Stripe line. */
.bh-guar { margin-top: 18px; font-size: 12.5px; color: var(--ink-3); text-align: center; line-height: 1.5; }
.bh-guar b { display: block; color: var(--ink); font-weight: 600; font-size: 13.5px; }
.bh-guar span { display: block; margin-top: 6px; }

/* ------------------------------------------------- Start: one question at a time */

/* The Start step has no order rail: nothing is priced yet. Its card takes the
   whole width of the step instead. */
.obgrid-solo { grid-template-columns: minmax(0, 1fr); }
/* The card hugs its one question. The checkout's floor on .wiz-body is there so
   four forms of different length share a height; one question in a row of cards
   does not need it, and the floor only left a band of nothing under the cards. */
.obgrid-solo .wiz-body { min-height: 0; }
/* The options as a block, not a strip: two columns for a four-answer question
   (a 2 x 2), three for the five-answer one (3 + 2). Each is the two-line chip
   given real room: the answer on top, what it means underneath, in a card tall
   enough that the panel reads as a place to choose rather than a row of tags. */
.sfq-opts { display: grid; grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr)); gap: 12px; }
/* Five cards: the same two-by-two block as a four-answer question, and the
   fifth across the full width beneath it. The first four keep the widths every
   other question has, and the last reads as one more row rather than a row
   with a hole in it. */
.sfq-opts.sfq-5 .plat:nth-child(5) { grid-column: 1 / -1; }
/* `.wiz .sfq-opts .plat`, three classes deep, because `.wiz .plat-2l` further
   down sets the ordinary two-line chip to 14px and would otherwise win here.
   The answer is the card's headline, so it is bold whether or not it is chosen;
   the chip rule's weight change on check is for a row of tags, not a card. */
.wiz .sfq-opts .plat { display: flex; flex-direction: row; align-items: center; justify-content: flex-start; gap: 16px; padding: 22px 24px; min-height: 118px; font-size: 18px; font-weight: 600; letter-spacing: -0.01em; border-radius: 14px; }
.wiz .sfq-opts .plat-sub { white-space: normal; margin-top: 6px; font-size: 14.5px; font-weight: 400; line-height: 1.45; }
/* A platform's mark sits on the line with its name, all three at one size. */
.sfq-plat { display: inline-flex; align-items: center; gap: 9px; }
.wiz .sfq-opts .plat .sfq-plat svg { width: 22px; height: 22px; flex: 0 0 auto; display: block; }
/* THE BRAND AT REST. A page that is purple only after a click is grey until
   then. So every card carries a mark in the brand's soft line colour before it
   is chosen: a ring for a single choice, a rounded square for a multiple one.
   Chosen, the mark fills with the brand and the card tints, as before. Hover
   brings the card's own edge up to the soft line colour. That is the whole
   budget: a mark and an edge, never a wash. */
/* The ring is drawn at --sfq-ring, the brand at just over half strength: the
   soft line colour (26%) read as grey at 2px, and full strength on five unchosen
   cards read as five things already chosen. */
/* ONE MARK EVERYWHERE. Every choice on the checkout, single or multiple, on a
   Start card or a plan row, is the same rounded square: the brand ring at rest,
   filled with a tick when chosen. A ring-and-dot radio next to a square tick
   was two languages for one act. */
.sfq-mark { --sfq-ring: color-mix(in srgb, var(--brand) 55%, transparent); flex: 0 0 auto; width: 22px; height: 22px; border-radius: 7px; border: 2px solid var(--sfq-ring); background: var(--white); position: relative; transition: border-color .15s, background .15s; }
.wiz .sfq-opts .plat:hover { border-color: var(--brand-line); }
.wiz .sfq-opts .plat:hover .sfq-mark { border-color: var(--action); }
.wiz .sfq-opts .plat:has(input:checked) .sfq-mark { border-color: var(--action); background: var(--action); }
.sfq-mark::after { content: ""; position: absolute; inset: 5px; border-radius: 50%; background: var(--white); transform: scale(0); }
.sfq-mark.is-check::after { inset: auto; left: 6px; top: 2px; width: 5px; height: 10px; border-radius: 0; background: none; border: solid var(--white); border-width: 0 2px 2px 0; transform: rotate(45deg) scale(0); }
.wiz .sfq-opts .plat:has(input:checked) .sfq-mark::after { transform: scale(1); }
.wiz .sfq-opts .plat:has(input:checked) .sfq-mark.is-check::after { transform: rotate(45deg) scale(1); }
@media (max-width: 560px) {
  .sfq-opts, .sfq-opts.sfq-5 { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .sfq-opts.sfq-5 .plat:nth-child(5) { grid-column: auto; }
  /* One row shape for every question on a phone: the mark, the answer, and what
     it means under it. Compact enough that four answers fit on one screen. */
  /* `.obpage` in front because app.css, which loads after this sheet, lays the
     five-answer question out as inline rows at the same specificity. */
  .obpage .wiz .sfq-opts .plat { min-height: 0; padding: 13px 16px; gap: 14px; font-size: 16px; }
  .obpage .wiz .sfq-opts .plat-sub { display: block; margin: 2px 0 0; font-size: 13.5px; }
}

/* ---------------------------------------------- the wizard card (the buy box) */

.wiz { border: 1px solid var(--line); border-radius: 18px; background: var(--white); padding: 24px 26px 22px; box-shadow: 0 1px 3px rgba(0,0,0,.04); }
@media (max-width: 560px) { .wiz { padding: 22px 18px; border-radius: 16px; } }
.wiz-body { min-height: 268px; }

/* A crisp edge, not a halo. app.css focuses a field with a 3px spread of --focus,
   which around a 54px field is a soft band wider than the field's own border.
   This is 1px of solid --action outside a 1px --action border: 2px of one colour,
   the same on every side of every field here. */
.wiz .field input:focus,
.wiz .field textarea:focus { border-color: var(--action); box-shadow: 0 0 0 1px var(--action); }
/* Continue and Fund keep the brand colour even before the step is valid: grey
   reads as "this cannot be paid for", which is not what an unfilled field means.
   The cursor is the tell. */
.wiz-next[disabled], .side-fund[disabled] { background: var(--action); color: var(--action-ink); cursor: not-allowed; }

.wiz-q { font-size: 21px; letter-spacing: -0.02em; line-height: 1.2; margin: 0 0 6px; color: var(--ink); }
/* A heading with no sub-line under it needs its own gap to the first field, or
   the label reads as a sub-line of the title rather than as the field's name. */
.wiz-step > .wiz-q:not(:has(+ .wiz-sub)) { margin-bottom: 20px; }
.wiz-sub { font-size: 14px; color: var(--ink-2); line-height: 1.5; margin: 0 0 16px; }
/* A section heading is 12px above the control it names, so the pair read as one
   object; sections are 30px apart, which is what makes the break visible.
   The old page's headings were loose divs and the 30px lived on
   `.brand-q ~ .brand-q`. Here each section is a real <fieldset> so the heading
   can be its <legend> and actually name the group, which means the gap belongs
   to the section rather than to the heading. Same distance either way. */
/* display:block because the stepper's heading is a <label> rather than a legend:
   a label is inline, and the control it names ended up beside it. */
.brand-q { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink); margin-bottom: 12px; }
.wiz-group { border: 0; padding: 0; margin: 0; min-width: 0; }
/* A legend is not a normal block: it floats into the fieldset's border and
   carries browser padding of its own. Both go, or the heading sits inset from
   every label under it. */
.wiz-group > legend { display: block; float: none; width: 100%; padding: 0; }
.wiz-step > .field + .wiz-group,
.wiz-step > .wiz-pair + .wiz-group,
.wiz-step > .wiz-group + .wiz-group,
.wiz-step > .wiz-group + .field,
.wiz-step > .wiz-group + .wiz-pair,
.wiz-step > .field + .field,
.wiz-step > .field + .wiz-pair,
.wiz-step > .wiz-pair + .field { margin-top: 30px; }
/* Two controls that are not sections of their own: the link that opens the notes
   box, and the promise the buyer ticks. They belong to the card rather than
   starting a new question, so they get less air than a section does. */
.wiz-step > * + .wiz-more { margin-top: 18px; }
.wiz-step > * + .chk { margin-top: 24px; }
/* The hint that belongs to the whole section sits between the heading and the
   control, so it takes the heading's gap and gives the control a smaller one. */
/* The line under a toggle's title, the old wizard's way: a plain sentence with
   the part that matters in the brand's weight and colour (.hint-hi). */
.wiz .hint.seg-hint { margin: -4px 0 9px; font-size: 13px; color: var(--ink-2); }
/* Two fields on one line: the handle and what to push, the name and the email. */
.wiz-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.wiz-pair .field + .field { margin-top: 0; }
@media (max-width: 560px) { .wiz-pair { grid-template-columns: 1fr; gap: 18px; } }
.wiz-div { height: 1px; background: var(--line); margin: 18px 0 16px; }
.hint-hi { color: var(--brand); font-weight: 600; }
.vhint { font-size: 12px; color: var(--ink-3); margin-top: 6px; line-height: 1.45; }
/* Ink only, no box: .bld-err's panel is for a failed submit at the foot of a
   card, and one of those beside a 220px field would outweigh the field. */
.hint.bad { color: #b42318; }
/* And the field itself says so, or the sentence under it is the only sign and it
   reads as a note rather than as a correction. */
.wiz .field input.is-bad,
.wiz .field textarea.is-bad { border-color: #b42318; }
.wiz .field input.is-bad:focus { border-color: #b42318; box-shadow: 0 0 0 1px #b42318; }
.field-optional { margin-left: 7px; font-weight: 500; color: var(--ink-3); }

.wiz-nav { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; }
.wiz-next { width: 100%; }
.wiz-next[hidden] { display: none; }
.wiz-nav:has(.wiz-next[hidden]) { display: none; }
/* Why the button cannot be pressed, written beside it. A disabled control that
   does not say what it is waiting for is the same as a broken one. */
.wiz-reason { margin: 0; font-size: 12.5px; color: var(--ink-3); line-height: 1.45; text-align: center; }

/* Reveals the notes box. Purple, because it is an action, and inline, because it
   is one line of opt-in under a card of required questions. */
.wiz-more { display: inline-block; background: none; border: none; padding: 4px 0; font-family: var(--sans); font-size: 13.5px; font-weight: 600; color: var(--action); cursor: pointer; }
.wiz-more:hover { text-decoration: underline; }

/* ----------------------------------------------------------------- the chips */

.plats { display: flex; gap: 8px; flex-wrap: wrap; }
/* Full strength at a lighter weight, not a grey: five greyed chips and one live
   one read as five unavailable options rather than as one chosen. The picked chip
   stands out by weight, border and fill instead. */
.plat { cursor: pointer; user-select: none; border: 1.5px solid var(--line); border-radius: 10px; padding: 9px 15px; font-size: 14px; font-weight: 400; color: var(--ink); background: var(--white); transition: border-color .15s, color .15s, background .15s, transform .12s ease; display: inline-flex; align-items: center; gap: 8px; position: relative; }
/* THE SIZE THE OLD WIZARD'S CHIPS WERE. app.css also declares `.plat`, at 11px
   and grey, for the little platform tags on the site's cards, and it lands
   after this sheet, so the bare rule above loses and the chips came out as
   tags. The old page had its own copy of these rules in the document, after
   app.css, and so drew them at 14px in a 44px-tall chip. Same here, at a
   specificity app.css cannot reach. The toggles and the field get the same
   treatment so the three controls on a step are one size. */
.wiz .plat { font-size: 14.5px; font-weight: 500; color: var(--ink); letter-spacing: 0; min-height: 44px; padding: 9px 16px; }
.wiz .plat:has(input:checked) { font-weight: 600; }
.wiz .seg label { min-height: 44px; font-size: 14.5px; display: flex; align-items: center; justify-content: center; gap: 8px; }
.wiz .field input[type="text"], .wiz .field input[type="email"], .wiz .field textarea { font-size: 16px; padding: 12px 14px; }
.wiz .chk { font-size: 14.5px; align-items: center; }
/* The checkbox is the same rounded square as every other mark on the checkout,
   drawn by hand: the native one takes its own hover tint from the browser, a
   second purple that is not the brand's. */
.wiz .chk input { appearance: none; -webkit-appearance: none; margin: 0; width: 22px; height: 22px; border-radius: 7px; border: 2px solid color-mix(in srgb, var(--brand) 55%, transparent); background: var(--white); position: relative; cursor: pointer; flex: 0 0 auto; transition: border-color .15s, background .15s; }
.wiz .chk:hover input { border-color: var(--action); }
.wiz .chk input:checked { border-color: var(--action); background: var(--action); }
.wiz .chk input:checked::after { content: ""; position: absolute; left: 6px; top: 2px; width: 5px; height: 10px; border: solid var(--white); border-width: 0 2px 2px 0; transform: rotate(45deg); }
.wiz .chk input:focus-visible { outline: 2px solid var(--action); outline-offset: 2px; }
.plat:active { transform: scale(0.97); }
.plat.on,
.plat:has(input:checked) { border-color: var(--action); color: var(--ink); background: var(--brand-soft); font-weight: 600; }
.plat svg { width: 16px; height: 16px; flex: 0 0 auto; display: block; }
/* The input is clipped rather than display:none, which would take it out of the
   tab order and off the accessibility tree. Arrow keys, "3 of 5" and a checked
   state that cannot drift out of sync with the paint all come from it. */
.plat input { position: absolute; opacity: 0; pointer-events: none; }
.plat:has(input:focus-visible) { outline: 2px solid var(--action); outline-offset: 2px; }
.plat.is-disabled { opacity: 0.5; cursor: not-allowed; }
/* The rate under a term chip. A second line inside the chip, so the chip becomes
   a two-row column rather than growing sideways. */
.plat-sub { display: block; margin-top: 2px; font-size: 12px; font-weight: 500; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.plat:has(input:checked) .plat-sub { color: var(--action); }
.plat-2l { flex-direction: column; align-items: flex-start; gap: 0; padding: 9px 14px; }
/* app.css also declares `.plat`, at 11px and grey, for the little platform tags
   on the site's cards, and it lands after this sheet. On a one-line chip that is
   the size the checkout has always shown. On a two-line chip it put the answer
   ("Podcast") at 11px under a 12px sub-line, so the two-line chip names its own
   size, at a specificity app.css's rule cannot reach. */
.wiz .plat-2l { font-size: 14px; color: var(--ink); letter-spacing: 0; }

/* A chip the buyer typed is the only kind that can be removed, so it is the only
   kind that carries an x. */
.chipwrap { position: relative; display: inline-flex; }
.chipx { position: absolute; top: -7px; right: -7px; width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--line-2); background: var(--white); color: var(--ink-3); font-size: 13px; line-height: 1; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; }
.chipx:hover { color: var(--ink); border-color: var(--ink-3); }
.plat-add { border-style: dashed; color: var(--ink-3); font-weight: 500; }
.plat-add.on { border-style: solid; border-color: var(--action); color: var(--action); background: var(--brand-soft); }
/* The row under the Never chips while "+ Add your own" is on: the old wizard's
   .reach-custom, a field beside its note, with an Add button for the mouse. The
   button stretches to the field's height so the two read as one control. */
.plats-custom { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.plats-custom .field { margin: 0; flex: 1 1 220px; max-width: 340px; }
.plats-custom .btn { flex: 0 0 auto; align-self: stretch; padding: 0 18px; border-radius: 10px; }
.plats-custom .hint { margin: 0; flex-basis: 100%; }

/* On a phone a flex row breaks where the labels happen to fall, so the five
   audience chips split 3 + 2 with a ragged hole and neither row looked chosen. A
   grid gives every chip the same width and the row an edge to line up on. Five
   into three does not divide, so the last two sit under the first two. */
@media (max-width: 560px) {
  .plats-audience, .plats-plat { display: grid; gap: 8px; overflow-x: visible; }
  .plats-audience { grid-template-columns: repeat(6, 1fr); }
  .plats-audience .plat { grid-column: span 2; }
  .plats-audience .plat:nth-child(4) { grid-column: 1 / span 2; }
  .plats-audience .plat:nth-child(5) { grid-column: 3 / span 2; }
  .plats-plat { grid-template-columns: repeat(3, 1fr); }
  .plats-audience .plat, .plats-plat .plat { justify-content: center; text-align: center; }
  /* the platform chips carry an icon as well as a word, so they trade some
     horizontal padding for the room */
  .plats-plat .plat { padding-left: 6px; padding-right: 6px; gap: 6px; font-size: 13px; }
  .plats-term { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
  .plats-term .plat { padding-left: 8px; padding-right: 8px; justify-content: center; text-align: center; }
  .plats-term .plat-2l { align-items: center; }
}

/* -------------------------------------------------------- the paid segments */

.seg { display: flex; gap: 8px; max-width: 220px; }
/* app.css draws `.seg button`; these options are <label>s wrapping a real radio,
   so the same block is restated for a label. Same numbers, so the two kinds of
   segment on the site stay one control. */
.seg label { flex: 1; font-family: var(--sans); font-size: 14px; padding: 10px 0; border: 1px solid var(--line); border-radius: 9px; background: var(--white); cursor: pointer; color: var(--ink-2); text-align: center; position: relative; user-select: none; }
.seg label:hover { border-color: var(--ink-3); }
.seg input { position: absolute; opacity: 0; pointer-events: none; }
.seg button, .seg label { transition: border-color .15s, color .15s, background .15s, transform .12s ease; }
.seg button:active, .seg label:active { transform: scale(0.97); }
.seg button.on,
.seg label:has(input:checked) { border-color: var(--action); color: var(--ink); background: var(--brand-soft); }
.seg label:has(input:focus-visible) { outline: 2px solid var(--action); outline-offset: 2px; }
/* Two options, side by side, because one of them costs money and a switch has an
   implied default and a state you have to infer from which side the knob is on. */
.paidseg { max-width: none; }
.paidseg button,
/* flex-basis 0, not auto: the two halves split the row equally instead of
   sizing to their words, so "No logo" is as wide as "Yes, add my logo" and the
   logo toggle and the Tier 1 toggle line up down the page. */
.paidseg label { flex: 1 1 0; min-width: 0; padding: 11px 14px; display: inline-flex; align-items: baseline; justify-content: center; gap: 7px; }
.segamt { font-style: normal; font-size: 12px; font-weight: 600; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.seg button.on .segamt,
.seg label:has(input:checked) .segamt { color: var(--action); }
/* Kept side by side on a phone: stacking two exclusive options reads as two
   separate switches rather than one either/or, and it costs a whole row of height
   on the tightest card in the flow. The label wraps inside its own button
   instead, with the price under it. */
@media (max-width: 480px) {
  .paidseg { flex-direction: row; }
  .paidseg button,
  .paidseg label { flex: 1 1 0; min-width: 0; flex-direction: column; align-items: center;
    justify-content: center; gap: 2px; min-height: 44px; padding: 8px 10px;
    text-align: center; line-height: 1.25; }
  .paidseg .segamt { margin-left: 0; }
}

/* ------------------------------------------------------- the views stepper */

/* The one control the old wizard did not have: it sold reach as four fixed
   packages, and this sells any number on a ladder. Built out of the same parts as
   a chip so it belongs to the row of controls above it: a 1.5px --line edge, a
   12px radius, --brand-soft and --action for the live state. */
.numstep { display: inline-flex; align-items: stretch; border: 1.5px solid var(--line); border-radius: 12px; background: var(--white); overflow: hidden; }
.numstep-btn { width: 52px; flex: 0 0 auto; border: 0; background: none; color: var(--ink); font-family: var(--sans); font-size: 21px; line-height: 1; cursor: pointer; transition: background .15s, color .15s; }
.numstep-btn:hover:not(:disabled) { background: var(--brand-soft); color: var(--action); }
.numstep-btn:disabled { color: var(--line-2); cursor: not-allowed; }
.numstep-btn:focus-visible { outline: 2px solid var(--action); outline-offset: -3px; }
.numstep-val { width: 190px; flex: 0 0 auto; border: 0; border-left: 1.5px solid var(--line); border-right: 1.5px solid var(--line); background: none; padding: 13px 8px; text-align: center; font-family: var(--sans); font-size: 22px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); font-variant-numeric: tabular-nums; outline: none; }
.numstep:focus-within { border-color: var(--action); }
.numstep:focus-within .numstep-val { border-color: var(--action); }
/* Kept in the tree with its height reserved even when it is empty: a note that
   appears at the floor must not shove the term chips down the page the moment
   somebody reaches it. */
.numstep-note { min-height: 0; margin-top: 8px; }

/* The three terms as the old wizard's tier list: a radio, the term, the rate and
   the discount on the left, the monthly bill on the right, the recommended one
   with a badge on its corner. Ported rule for rule from content/start-campaign.html
   so the two steps read as one wizard. */
.plans { display: flex; flex-direction: column; gap: 10px; }
.plan { display: flex; align-items: center; gap: 14px; border: 1.5px solid var(--line); border-radius: 13px; padding: 15px 18px; cursor: pointer; background: var(--white); position: relative; transition: border-color .15s, background .15s, transform .12s ease; }
.plan:hover { border-color: var(--brand-line); }
.plan:active { transform: scale(0.995); }
.plan.on { border-color: var(--action); background: var(--brand-soft); }
.plan.featured { border-color: var(--ink-3); }
.plan.featured.on { border-color: var(--action); }
.plan input { position: absolute; opacity: 0; pointer-events: none; }
.plan:has(input:focus-visible) { outline: 2px solid var(--action); outline-offset: 2px; }
/* The plan row's mark is the same rounded square as the Start cards'. */
.plan-radio { width: 22px; height: 22px; border-radius: 7px; border: 2px solid color-mix(in srgb, var(--brand) 55%, transparent); background: var(--white); flex: 0 0 auto; position: relative; transition: border-color .15s, background .15s; }
.plan:hover .plan-radio { border-color: var(--action); }
.plan.on .plan-radio { border-color: var(--action); background: var(--action); }
.plan.on .plan-radio::after { content: ""; position: absolute; left: 6px; top: 2px; width: 5px; height: 10px; border: solid var(--white); border-width: 0 2px 2px 0; transform: rotate(45deg); animation: tickin .18s ease-out; }
@keyframes radiopop { from { transform: scale(0); } to { transform: scale(1); } }
.plan-main { flex: 1; min-width: 0; }
.plan-name { font-size: 11px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--ink-3); }
.plan-views { font-size: 18px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); margin-top: 2px; }
.plan-cpm { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }
.plan-right { text-align: right; flex: 0 0 auto; }
.plan-price { font-size: 18px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); display: inline-block; font-variant-numeric: tabular-nums; }
.plan-save { font-size: 11.5px; color: var(--ink-2); margin-top: 2px; }
.plan-badge,
.plat-badge { position: absolute; top: -9px; right: 16px; background: var(--action); color: var(--action-ink); font-size: 10px; letter-spacing: 0.07em; text-transform: uppercase; font-weight: 600; padding: 3px 9px; border-radius: 20px; }
/* On a chip the tag is centred on the top edge, since the chip has no right-hand
   figure for it to sit beside. The term row gets a little headroom for it. */
.plat-badge { right: auto; left: 50%; transform: translateX(-50%); white-space: nowrap; }
.wiz .plats-term { padding-top: 9px; }
.plans + .hint { margin-top: 10px; }
/* The typed field under the rows when Custom is chosen: the old wizard's custom
   row opened a real field under the tiers rather than turning the tier into one. */
.plan-custom { margin-top: 12px; }
.plan-custom .wiz-group > .brand-q { font-size: 12.5px; color: var(--ink-2); }
@media (prefers-reduced-motion: reduce) { .plan.on .plan-radio::after { animation: none; } }
@media (max-width: 560px) {
  .numstep { display: flex; width: 100%; }
  .numstep-val { width: auto; flex: 1 1 auto; }
}

/* --------------------------------------------------------- review and fund */

/* The figure is the reach, not the price: this is the screen where somebody
   decides to pay, and what they are buying is the views. Purple, because the
   card's ink is spent on the Total row, which is the one figure here that is
   money. On white at 44px bold it is 3.47:1, over the 3:1 large-text floor. */
.rev-views-n { font-size: 44px; font-weight: 700; letter-spacing: -0.03em; color: var(--action); line-height: 1; display: inline-block; font-variant-numeric: tabular-nums; margin-top: 6px; }
.rev-sub { font-size: 15px; color: var(--ink-2); margin-top: 8px; }
/* A hairline between the rows draws them, so the eye does not have to work out
   which value belongs to which label across a very wide gap. */
.rev-lines { margin: 22px 0 4px; display: flex; flex-direction: column; }
.rev-line { display: flex; justify-content: space-between; gap: 14px; font-size: 13.5px; padding: 8px 0; }
.rev-line:first-child { padding-top: 0; }
.rev-line + .rev-line { border-top: 1px solid var(--line); }
.rev-line .k { color: var(--ink-3); flex: 0 0 auto; }
.rev-line .v { color: var(--ink); font-weight: 500; text-align: right; min-width: 0; }
.rev-line.rev-total .v { font-size: 19px; font-weight: 700; letter-spacing: -0.02em; }
.rev-line.rev-total .k { align-self: center; }
.rev-plan, .rev-rate { color: var(--brand); font-weight: 600; }
.rev-disc { color: var(--ink-3); font-weight: 500; }
.rev-guar { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 14px; font-size: 12.5px; color: var(--ink-3); line-height: 1.5; }
.rev-promise { margin: 10px 0 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-2); }
/* The gap between the priced half of this step and the fields below it. */
.rev-promise + .wiz-group { margin-top: 30px; }
.rev-contact { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 14px; }
.rev-contact .field { margin: 0; }
.rev-contact .field + .field { margin-top: 0; }
.rev-contact .field.wide { grid-column: 1 / -1; }
@media (max-width: 560px) { .rev-contact { grid-template-columns: 1fr; } }
.rev-link { color: var(--action); font-weight: 600; border-bottom: 1px solid var(--brand-line); }
.rev-link:hover { border-bottom-color: var(--action); }

/* A failed submit, at the foot of the card. */
.bld-err { margin-top: 12px; font-size: 13px; color: #b42318; background: #fef3f2; border: 1px solid #fecdca; border-radius: 9px; padding: 10px 12px; }
/* The return from a cancelled Stripe session. Same panel shape as the error, in
   the house grey, because nothing went wrong: the buyer chose not to pay yet. */
.bld-note { margin: 0 0 18px; font-size: 13px; color: var(--ink-2); background: var(--bg); border: 1px solid var(--line); border-radius: 9px; padding: 10px 12px; line-height: 1.5; }
.bld-note b { color: var(--ink); font-weight: 600; }

/* The success panel. The builder hands off to Stripe and comes back on /app, so
   nothing under /build renders this today; the rules are here so that the panel
   which says a campaign is booked looks like the card that took the money,
   wherever the funnel decides to put it. */
.bld-done { border: 1px solid var(--line); border-radius: 18px; background: var(--white); padding: 40px 28px; text-align: center; }
.bld-done h3 { font-size: 22px; letter-spacing: -0.02em; margin: 0 0 10px; }
.bld-done p { font-size: 15px; line-height: 1.6; color: var(--ink-2); margin: 0 auto; max-width: 460px; }
/* Three things, not one 60-word paragraph: the fact, the promise, the receipt. */
.bld-done .done-guar { margin-top: 14px; font-size: 13.5px; color: var(--ink-3); }
.bld-done .done-ref { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: 13px; color: var(--ink-3); font-variant-numeric: tabular-nums; max-width: 340px; }
.done-mark { width: 52px; height: 52px; margin: 0 auto 20px; }
.done-mark svg { width: 100%; height: 100%; display: block; fill: none; stroke: var(--action); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.done-mark circle { opacity: 0.2; }

/* ------------------------------------------------------------- the skeleton */

/* What the server renders and what the first client frame renders: the draft is
   in localStorage, which the server cannot see, so both have to agree on
   something. A shape, not a spinner. */
.wiz-sk { background: color-mix(in srgb, var(--ink) 7%, transparent); border-radius: 10px; }
.wiz-sk-h1 { height: 26px; width: 62%; margin-bottom: 22px; }
.wiz-sk-field { height: 54px; margin-bottom: 18px; }
.wiz-sk-chips { height: 40px; width: 78%; }
.wiz-sk-line { height: 16px; margin-bottom: 12px; }
.wiz-sk-cta { height: 46px; margin-top: 18px; }
.wiz-sronly { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* --------------------------------------------------------------- phone card */

/* The card takes the site gutter's place. Measured at a 398px viewport, where
   this flow had 292px of usable column: the 34px site gutter plus the card's own
   padding and border stack up to 53px a side, so 27% of the screen was margin and
   every step read as squeezed. The card's border already draws the edge the
   gutter was standing in for. */
@media (max-width: 560px) {
  .obmain > .wrap { padding-left: 18px; padding-right: 18px; padding-top: 4px; }
  .obh .appnav-in { height: 52px; }
  .obpage { --ob-support-h: 30px; }
  .obsupport { font-size: 12.5px; }
  .obsteps { padding-bottom: 8px; margin-bottom: 10px; }
  .obback { min-height: 20px; margin-bottom: 14px; }
  .obback .wiz-back { font-size: 12.5px; }
  /* app.css's mobile nav block scores three classes to .obh-in's one and left the
     mark jammed against the left edge. Matching that specificity puts it back in
     the middle. 24px because this is a checkout: the mark is here to say whose
     payment page this is, not to sell. */
  .appnav.mktnav.obh .appnav-in { justify-content: center; }
  .appnav.mktnav.obh .brand { font-size: 24px; }
  /* 44px on every control a person has to hit while paying. The text links keep
     their visual size and gain the height as padding, so nothing looks bigger, it
     is just hittable. */
  .wiz .plat, .wiz .seg button, .wiz .seg label { min-height: 44px; }
  .wiz-more { display: inline-flex; align-items: center; min-height: 44px; }
  /* The footage box is drawn by hand at 22px (`.wiz .chk input`), and its tick
     is placed for that size, so it is left out of the phone's 18px. */
  .wiz input[type=checkbox]:not(.chk input) { width: 18px; height: 18px; flex: 0 0 auto; }
  /* Below 16px iOS zooms the page in on focus and does not zoom back out, so
     tapping a field threw the reader into a magnified page mid-checkout. This is
     a behaviour fix, not a style preference, so it wins outright. */
  .obmain input, .obmain textarea, .obmain select { font-size: 16px !important; }
  /* The placeholder is exempt: iOS decides whether to zoom from the input's own
     font-size. At the field's size and weight an example read like a value
     already entered. */
  .obmain input::placeholder, .obmain textarea::placeholder {
    font-size: 12.5px; font-weight: 400; color: var(--ink-3); opacity: 1; }
  .wiz { padding: 18px 16px; }
  /* The title stays left, as on a desktop, over the fields it labels: a centred
     title over left-aligned fields read as two layouts on one card, and the
     phone should be the desktop narrower, not a different page. */
  .wiz-q { font-size: 19px; }
  .wiz-sub { font-size: 13.5px; margin-bottom: 18px; }
  .wiz .wiz-step > .wiz-q:not(:has(+ .wiz-sub)) { margin-bottom: 18px; }
  .brand-q { font-size: 13px; margin-bottom: 4px; }
  .hint { font-size: 12px; line-height: 1.4; }
  .wiz .field input, .wiz .field textarea { padding: 10px 12px; }
  /* At this width white space alone stops reading as separation: the card becomes
     one long column where the headings do all the work of saying where one thing
     ends and the next begins. `~ .brand-q` draws the rule above every section
     except the first, so the top of the card stays open. */
  .wiz-step > .field + .wiz-group,
  .wiz-step > .wiz-pair + .wiz-group,
  .wiz-step > .wiz-group + .wiz-group,
  .wiz-step > .wiz-group + .field,
  .wiz-step > .wiz-group + .wiz-pair,
  .wiz-step > .field + .field,
  .wiz-step > .field + .wiz-pair,
  .wiz-step > .wiz-pair + .field { margin-top: 26px; padding-top: 24px; border-top: 1px solid var(--line); }
  /* A fieldset draws its legend ON its top border, so the rule above ran behind
     the question's own label (invisible) and all 24px of padding landed between
     the label and its controls. Floated full width, the legend is ordinary
     content: the rule is drawn above it and the padding sits above it too. */
  .wiz-group > legend { float: left; width: 100%; margin-bottom: 10px; }
  .wiz-group > legend ~ * { clear: both; }
}

/* Below 560 the rail's labels turn a four-step bar into a sideways scroller. Keep
   only the current step labelled; the rest collapse to their numbered dots. */
@media (max-width: 560px) {
  .obsteps { overflow-x: visible; gap: 6px; }
  .obstep:not(.active) .oblb { display: none; }
  .obstep:not(.active) { gap: 0; }
  .obstep:not(.active) .obsteplink { gap: 0; }
  .obconn { min-width: 10px; }
}

/* ------------------------------------------------------------------ motion */

/* All of it behind one query, so with reduced motion asked for none of this is
   defined at all and the wizard is a plain swap. */
@media (prefers-reduced-motion: no-preference) {
  .wiz-step { animation: wizin .3s cubic-bezier(.22,.68,.32,1) both; }
  @keyframes wizin { from { opacity: 0; transform: translate3d(18px, 0, 0); } to { opacity: 1; transform: none; } }
  /* Vertical on a phone: since Continue scrolls back to the top, a card sliding
     in from the right while the window travels up reads as one diagonal drift.
     Vertical agrees with the scroll instead of crossing it. */
  @media (max-width: 560px) {
    @keyframes wizin { from { opacity: 0; transform: translate3d(0, 12px, 0); } to { opacity: 1; transform: none; } }
  }
  .obconn::after { transition: transform .45s cubic-bezier(.4, 0, .2, 1); }
  .obstep .n i { transition: opacity .2s ease, transform .34s cubic-bezier(.34, 1.56, .64, 1); }
  .plat:has(input:checked), .seg button.on { animation: chippop .26s cubic-bezier(.34, 1.56, .64, 1); }
  @keyframes chippop { 0% { transform: scale(0.95); } 60% { transform: scale(1.025); } 100% { transform: scale(1); } }
  .pv-total .v { transition: font-size .26s cubic-bezier(.22,.68,.32,1), color .26s ease; }
  /* The per-length steps cross 10M and 100M mid-count, so without this the figure
     snaps a few points wider while the digits are still gliding. */
  .pv-views-n { transition: font-size .26s cubic-bezier(.22,.68,.32,1); }
  /* The order rail assembles. A value that has just been set rises 5px into
     place and holds the accent for a beat before settling to ink, so the eye is
     drawn to the one row the last answer changed. A platform mark pops in beside
     the reach when its chip is ticked. Both are enter animations on elements
     React keys on the value, so a change is a remount and a remount is the cue. */
  .pv-set { animation: pvset .38s cubic-bezier(.22,.68,.32,1) both; }
  .pv-set.is-new { animation: pvset .38s cubic-bezier(.22,.68,.32,1) both, pvink 1.1s ease-out both; }
  @keyframes pvset { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
  @keyframes pvink { 0%, 45% { color: var(--action); } 100% { color: var(--ink); } }
  /* The old wizard's pop on a figure that has just started to roll: 4% and
     back over 340ms, from the left so the digits grow out of their own margin.
     Once per roll, from rest; a re-aimed roll does not pop again. */
  .numpulse { animation: numpulse .34s ease; transform-origin: left center; }
  @keyframes numpulse { 0% { transform: scale(1); } 35% { transform: scale(1.04); } 100% { transform: scale(1); } }
  /* START'S MOTION IS QUIET. The checkout's chips pop on an overshoot curve and
     its questions slide in from the right; on cards this size, with a new
     question arriving 220ms after the tap, that read as a bounce. So on Start:
     no pop on the chosen card, the mark fills on a plain ease-out, the tick
     fades in already upright (a scale keyframe without the rotation in it was
     what made it turn), and the next question fades up 6px instead of sliding. */
  .wiz .sfq-opts .plat:has(input:checked) { animation: none; }
  .wiz .sfq-opts .plat:has(input:checked) .sfq-mark::after { animation: markin .18s ease-out; }
  .wiz .sfq-opts .plat:has(input:checked) .sfq-mark.is-check::after { animation: tickin .18s ease-out; }
  @keyframes markin { from { transform: scale(.4); opacity: 0; } to { transform: scale(1); opacity: 1; } }
  @keyframes tickin { from { transform: rotate(45deg) scale(.6); opacity: 0; } to { transform: rotate(45deg) scale(1); opacity: 1; } }
  .obgrid-solo .wiz-step { animation: sfqin .24s ease-out both; }
  @keyframes sfqin { from { opacity: 0; transform: translate3d(0, 6px, 0); } to { opacity: 1; transform: none; } }
  .pv-subplats svg { animation: pvmark .36s cubic-bezier(.34, 1.56, .64, 1) both; }
  @keyframes pvmark { from { opacity: 0; transform: scale(.5); } to { opacity: 1; transform: none; } }
  /* The one larger motion, on the last step only: the reach block locks in. A
     short settle from 96% and a ring of the brand purple that swells and fades,
     once, on arrival. The ring is --action at low alpha (#9873F0). */
  .is-reveal .pv-views { animation: pvlock .9s cubic-bezier(.22,.68,.32,1) both; }
  @keyframes pvlock {
    0% { transform: scale(.96); box-shadow: 0 0 0 0 rgba(152,115,240,0); }
    45% { transform: scale(1.015); box-shadow: 0 0 0 9px rgba(152,115,240,.22); }
    100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(152,115,240,0); }
  }
  .bld-done .done-mark circle { stroke-dasharray: 152; stroke-dashoffset: 152; animation: drawmark .62s ease forwards; }
  .bld-done .done-mark path { stroke-dasharray: 42; stroke-dashoffset: 42; animation: drawmark .42s .34s ease forwards; }
  @keyframes drawmark { to { stroke-dashoffset: 0; } }
}

/* The views field: the reach is the biggest decision on the page, so its number
   is set big and bold, in a field wide enough for eight digits and no wider. */
.views-field { max-width: 260px; }
.views-field input { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; padding: 11px 16px; }
.views-field + .hint { margin-top: 8px; }
/* The term chips: three equal columns spanning the same width as the plan rows
   above them, and tall enough to sit beside those rows as an equal. The length
   is the chip's headline, centred, with the saving under it. */
.wiz .plats-term { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.wiz .plats-term .plat { min-height: 68px; padding: 12px 16px; font-size: 16px; justify-content: center; text-align: center; border-radius: 13px; }
.wiz .plats-term .plat-2l { align-items: center; }
.wiz .plats-term .plat-sub { font-size: 13px; margin-top: 3px; }
/* Three chips across a phone are about 105px each: at the desktop's 16px the
   length and the saving both wrapped. One line each, a size down. */
@media (max-width: 560px) {
  .wiz .plats-term { gap: 8px; }
  .wiz .plats-term .plat { min-height: 60px; padding: 10px 6px; font-size: 14.5px; white-space: nowrap; }
  .wiz .plats-term .plat-sub { font-size: 12px; white-space: nowrap; }
}
  .plan-views .field { max-width: none; }
}

/* ===== the checkout on a phone, 2026-09-12 =====================================

   1. THE SUPPORT LINE LOOKED LEFT OF THE WORDMARK, AND WAS NOT.
   Both are centred on the page: measured at 390, 402 and 430 the brand box and
   the support line share a centre to the pixel. The mark hangs off the left of
   the lockup, though, so the word "Seenfold" centres 24px to the RIGHT of the
   box that holds it, and the eye centres on the word. Against that, a line
   centred on the page reads as shifted. So the line is optically centred on the
   word instead: the mark's own advance, halved. This is the ordinary correction
   for setting text under a lockup whose mark is on one side.

   2. THE ORDER SUMMARY ATE AN EIGHTH OF THE SCREEN BEFORE IT WAS OPENED.
   Collapsed it drew two rows, a label with a chevron over the price, 98px of a
   844px screen, permanently over the form. It is one row now: the price where
   the label was, the chevron at the end, and the button stretched across the
   whole bar so the tap target is the bar rather than the chevron. Everything
   the sheet is for is unchanged -- the live price never needs a scroll, and the
   breakdown is still one tap away -- it just costs 40 fewer pixels to say so. */
/* Doubled on purpose: extra padding on one side moves a centred box's centre by
   HALF of it, so 2 x 24px of padding is a 24px shift. */
.obsupport { padding-left: calc(16px + 2 * var(--ob-mark-nudge, 24px)); padding-right: 16px; }

@media (max-width: 860px) {
  .obside:not(.is-open) { position: fixed; }
  .obside:not(.is-open) .obprev { position: relative; flex-direction: row; align-items: center; gap: 12px; }
  /* The handle covers the bar rather than sitting in it, so the whole sheet is
     the button. Above the price in z-order and transparent, so the figure under
     it still reads. */
  .obside:not(.is-open) .pv-peek {
    position: absolute; inset: 0; z-index: 2; width: auto; padding: 0;
    min-height: 0; justify-content: flex-end;
  }
  .obside:not(.is-open) .pv-peek-lab { display: none; }
  .obside:not(.is-open) .obprev > .pv-money { flex: 1 1 auto; min-width: 0; margin: 0; }
  /* Room for the chevron the handle parks at the end. */
  .obside:not(.is-open) .obprev { padding-right: 26px; }
}
