/* global React, window */
/* =========================================================
   STUDY CHECKOUT — the screen where the approach stops being
   something that happened and becomes something you chose.

   It is one viewport and it does not scroll. Left, three sliders:
   Timeline, Confidence, Budget. Right, the consequence: who answers,
   how the sample splits between Digital Twins and recruited shoppers,
   what it costs, and the button. Move a slider and the whole right
   side re-derives — nothing on this page is typed and nothing on the
   right is authored.

   TONE: this is read by a merchant, not a researcher. The page leads
   with sentences and keeps the arithmetic underneath them. No
   percentage is ever the headline — a percentage is an answer to a
   question nobody asked.

   FW.BACKTEST is deliberately not read here. Its paired/sameCall/
   meanGapPts values are a proposed schema awaiting measurement, not
   results, so no accuracy figure is asserted anywhere on this page.

   COPY RULE: "Digital Twins" is a proper noun and is capitalised
   everywhere it appears — labels, captions, tab names, chart legend.
   ========================================================= */
const { useState: ckS } = React;

/* ---------------------------------------------------------
   Timeline. Eight stops rather than three presets, because "three
   days" and "two weeks" are different decisions and a merchant
   should not have to round to the nearest one. The rush premium
   falls as the runway grows; the first two stops are too fast to
   recruit a real person into, so they are Digital Twins or nothing.
   --------------------------------------------------------- */
const CK_SAY_TWIN = "Nothing to recruit, so nothing to wait for.";
const CK_SAY_TIGHT = "Just enough runway to put real shoppers in the mix.";
const CK_SAY_OK = "Comfortable for recruiting, with only a small premium for the pace.";
const CK_SAY_CALM = "The same study, without paying anything to rush it.";

const CK_TIMELINE = [
  { lbl: "Today",    when: "in about 1 hour",  days: 0.04, rush: 1.60, peopleOK: false, say: CK_SAY_TWIN },
  { lbl: "Tomorrow", when: "Fri 28 Aug",       days: 1,   rush: 1.45, peopleOK: false, say: CK_SAY_TWIN },
  { lbl: "2 days",   when: "Sat 29 Aug",       days: 2,   rush: 1.32, peopleOK: true,  say: CK_SAY_TIGHT },
  { lbl: "3 days",   when: "Sun 30 Aug",       days: 3,   rush: 1.22, peopleOK: true,  say: CK_SAY_TIGHT },
  { lbl: "5 days",   when: "Tue 1 Sep",        days: 5,   rush: 1.14, peopleOK: true,  say: CK_SAY_OK },
  { lbl: "1 week",   when: "Thu 3 Sep",        days: 7,   rush: 1.08, peopleOK: true,  say: CK_SAY_OK },
  { lbl: "10 days",  when: "Sun 6 Sep",        days: 10,  rush: 1.03, peopleOK: true,  say: CK_SAY_CALM },
  { lbl: "2 weeks",  when: "Thu 10 Sep",       days: 14,  rush: 1.00, peopleOK: true,  say: CK_SAY_CALM }
];

/* Confidence stays three, because it genuinely is three: this is not a
   dial with hidden intermediate values, it is a choice between three
   shapes of deliverable. The names say what the work is for rather than
   how good it is - a gut check you act on alone, discovery you brief a
   team with, research that survives someone pushing back. "Quick read"
   and "Airtight" graded the output; these grade the decision.

   The three labels are NOT authored here. They are the options a merchant
   already answered upstream, copied verbatim from the intake `conf`
   question (agent-thread.jsx: "How much confidence do you need?"), so the
   checkout reflects their answer back in the words they chose rather than
   renaming it under them. Change them there, not here.

   `floor` is the earliest TIMELINE stop this rigor can honestly be
   delivered at with NO twins allowed - not "how much work it is," but
   "does it need a human." Directional and Solid both sit inside the twin
   ceiling (capCf below), so 100% twins can answer either one, and twins
   are instant: both floor at 0. Only Defensible forces real shoppers into
   the mix, and recruiting them is the only thing on this page that
   genuinely can't happen today. An earlier pass floored Solid at 2 days,
   ported straight from cohesive-makerlabs' DELIVERY_TIERS hours (which
   was ALWAYS part-human at every tier, unlike this ceiling model) - that
   stale floor silently refused to let the Timeline slider drag a
   Solid-confidence study all the way to Digital Twins. */
const CK_CONFIDENCE = [
  { lbl: "Directional · gut check", n: 120, base: 25, floor: 0,
    say: "Enough to point you the right way.",
    get: ["A clear steer on which way to go",
          "Your two biggest questions answered",
          "Short enough to read in one sitting"] },
  { lbl: "Solid · balanced discovery", n: 200, base: 50, floor: 0,
    say: "Enough to make the call and brief your team.",
    get: ["All four of your questions answered",
          "Every group compared side by side",
          "Shopper quotes behind each finding",
          "A summary you can forward as-is"] },
  { lbl: "Defensible · deep research", n: 320, base: 200, floor: 3,
    say: "Enough to hold up when someone pushes back.",
    get: ["All four of your questions answered",
          "Every group compared side by side",
          "Shopper quotes behind each finding",
          "The numbers behind every claim, checked",
          "A methods page for the buy meeting"] }
];

/* `floor` is the earliest timeline stop the mix can be delivered at, and
   `pin` means the mix has no waiting in it at all - a twin-only study is
   ready today and no runway you add buys anything, so the slider says so
   instead of pretending the date is yours to move.

   `capCf` is the provenance ceiling, ported from cohesive-makerlabs
   escalation.ts: a twin prediction is Medium-grade evidence at 120
   respondents and Medium-grade evidence at 320. Sample size does not
   convert a model into a shopper, so the rigor dial stops short of
   Defensible while the mix is twin-only. You climb by asking people. */
const CK_MODES = [
  { id: "twins", lbl: "Digital Twins", share: 100, extraDays: 0, floor: 0, pin: true, capCf: 1,
    line: "Modelled shoppers built from 90,000+ past studies. Nothing to recruit, so nothing to wait for." },
  { id: "mixed", lbl: "Mixed", share: 55, extraDays: 2, floor: 2,
    line: "Digital Twins do the volume and real shoppers check the calls that cost money. Most studies land here." },
  { id: "people", lbl: "Real people", share: 0, extraDays: 4, floor: 3,
    line: "Every answer comes from a real shopper. Slowest and dearest, and nobody can argue with it." }
];

/* Twelve stops, so the budget can be set rather than picked from a
   handful of round numbers that never match what you actually have. */
const CK_BUDGETS = [250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000, 6000, 8000, 10000];

/* Segment colours are not invented here, and they are not shown here
   either — the marks came off the rows. The palette is kept on the
   records anyway because it is the mock's existing audience palette
   (survey-studio-sales-demo/AudienceShelf.jsx) and anything that later
   needs to draw these groups should draw them the same way. */
/* Four groups, not six. Six filled the rail without the last two saying
   anything the first four had not — this list exists to be read at a
   glance and edited, not to prove how many cuts we can run. What the four
   have to cover between them is the shape of the decision: two markets we
   know, one behavioural group that cuts across them, and one market we
   have never asked, which is the only row that moves the timeline and the
   only reason the flag below the total exists. German-speaking Europe and
   trend-led switchers were the two that repeated a shape already on
   screen, so they are the two that came out.

   `place` is the short market name, not the country list. Spelled out,
   these ran to three lines in a 344px rail. */
const CK_SEGMENTS = [
  { id: "na", color: "#3a8518", name: "North American shoppers", place: "US & Canada", known: true },
  { id: "uki", color: "#6d4cd9", name: "UK & Ireland shoppers", place: "UK & Ireland", known: true },
  { id: "loyalist", color: "#0f9b8e", name: "Your core customer", place: "All markets", known: true },
  { id: "nordics", color: "#8c5800", name: "Nordic shoppers", place: "Nordics", known: false }
];

const ckFmt = n => Math.round(n).toLocaleString();
const ckShort = n => n >= 1000 ? (n / 1000) + "k" : String(n);

const ckMode = id => CK_MODES.filter(m => m.id === id)[0];

/* The earliest timeline stop a given (rigor, mix) pair can honestly be
   delivered at. Rigor sets one floor - defensible research is ten days of
   work whoever answers it - and the mix sets another, because recruiting
   anybody takes days no matter how shallow the study. The later of the
   two is the real answer. A twin-only study has neither floor, so it is
   pinned to today rather than given a date the merchant can drag. */
function ckFloor(st) {
  const mode = ckMode(st.mode);
  return mode.pin ? mode.floor : Math.max(mode.floor, CK_CONFIDENCE[st.cf].floor);
}

/* Every cross-slider invariant, in one place, applied to every change.

   Left to themselves the three sliders describe studies nobody can run:
   defensible research delivered this afternoon, a twin-only panel you
   wait two weeks for, 320 Digital Twins sold as evidence that holds up in
   a buy meeting. The old app (cohesive-makerlabs) never had that problem
   because depth DROVE the timeline and the mix rather than sitting beside
   them. This restores that chain while keeping three sliders: no setter
   writes state directly, so an impossible state is unreachable rather
   than reachable-and-then-warned-about. */
function ckClamp(st, touched) {
  const next = Object.assign({}, st);

  /* Every control here is a first-class input: whichever one the user
     just touched is honored, and the OTHER two move to stay consistent -
     never the touched control's own request getting silently discarded.
     Nothing on this screen is ever disabled; this is what makes that
     true without also making it lie: a tab you can click but that snaps
     back the instant you release it is locked in every way that
     matters, whether or not the button itself has `disabled` on it. */
  if (touched === "mode") {
    /* A tab click is a real choice, same standing as a slider drag. If
       the mode just picked needs recruiting and Timeline is sitting
       somewhere that can't support it, Timeline moves forward to the
       earliest day that mode allows - the pick itself is never undone. */
    if (!ckMode(next.mode).pin && !CK_TIMELINE[next.tl].peopleOK) {
      next.tl = CK_TIMELINE.findIndex(t => t.peopleOK);
    }
  } else if (touched === "tl" && ckMode(next.mode).pin && CK_TIMELINE[next.tl].peopleOK) {
    /* Timeline just proved there is time to recruit, so the twins-only
       pin - which only ever existed because there wasn't - is no longer
       earned. The mix follows Timeline out of it. */
    next.mode = "mixed";
  } else if (!CK_TIMELINE[next.tl].peopleOK) {
    /* Hard law, the fallback once neither of the above applies: this
       fast, twins are the only option. */
    next.mode = "twins";
  } else if (touched === "cf") {
    /* Hard law: twin predictions cap at Solid. Confidence just asked for
       more than that, so - and only because Confidence is the thing that
       moved - the audience mix follows it into real shoppers. */
    const cap = ckMode(next.mode).capCf;
    if (cap != null && next.cf > cap) next.mode = "mixed";
  }

  const mode = ckMode(next.mode);
  if (mode.capCf != null && next.cf > mode.capCf) next.cf = mode.capCf;
  next.tl = mode.pin ? mode.floor : Math.max(next.tl, ckFloor(next));
  return next;
}

/* One function derives the whole right-hand side. Digital Twins cost a
   tenth of a recruited response; a group we have never asked cannot be
   answered from history, so it needs recruiting and that costs days. */
function ckQuote(st) {
  const tl = CK_TIMELINE[st.tl];
  const cf = CK_CONFIDENCE[st.cf];
  const mode = CK_MODES.filter(m => m.id === st.mode)[0];
  const rows = CK_SEGMENTS.map(seg => {
    const n = cf.n;
    const twinN = Math.round(n * mode.share / 100);
    const humanN = n - twinN;
    return { seg: seg, off: st.off.indexOf(seg.id) >= 0, n: n, twinN: twinN, humanN: humanN,
      credits: Math.round(humanN * tl.rush + twinN * 0.1),
      recruit: !seg.known && humanN > 0 };
  });
  const live = rows.filter(r => !r.off);
  const recruit = live.some(r => r.recruit);
  const sum = k => live.reduce((a, r) => a + r[k], 0);
  return { rows: rows, live: live, mode: mode, tl: tl, cf: cf, recruit: recruit,
    n: sum("n"), twins: sum("twinN"), humans: sum("humanN"),
    credits: sum("credits") + cf.base,
    days: tl.days + (recruit ? mode.extraDays : 0),
    budget: CK_BUDGETS[st.bg] };
}

/* ---------------------------------------------------------
   The slider. A styled track over a transparent native range, so the
   browser keeps drag, click-to-jump, arrow keys and the slider role
   while none of its chrome reaches the screen.
   --------------------------------------------------------- */
function CKSlider({ k, value, hint, min, max, at, stops, onPick, children, floor, ceil }) {
  const last = stops - 1;
  const pct = last > 0 ? (at / last) * 100 : 0;
  const lo = floor || 0;
  const hi = ceil == null ? last : ceil;
  /* The stops outside [lo, hi] are not merely rejected on release - the
     thumb never reaches them, so the dial reads as a ceiling rather than
     as a control that keeps snapping back. */
  const bounded = i => Math.max(lo, Math.min(hi, i));
  /* The unreachable tail, drawn once for whichever end is out of bounds,
     so the merchant can see the range they have rather than discovering
     it by pushing. */
  const deadL = last > 0 ? (lo / last) * 100 : 0;
  const deadR = last > 0 ? 100 - (hi / last) * 100 : 0;
  /* Two columns rather than one stacked block: the control (label, track,
     ends) on the left where a thumb wants room to travel, the consequence
     (value, hint, the say-line) on the right where a sentence wants room
     to breathe instead of stretching edge-to-edge under a thin track. The
     old single-column layout used the card's full ~770px width for a
     handle that only ever needed to travel it, and then wrapped a
     one-sentence explanation across that same width, which is the wrong
     thing to be wide. Collapses back to one column under 720px, the same
     breakpoint the "what you'll get back" grid already uses. */
  return (
    <div className="ck-sl">
      {/* The label sits on its own full-width line above the two columns,
          not stacked inside the left one - stacked, it pushed the track
          down past where the value text starts, so the row read as two
          things at different heights instead of one aligned pair. */}
      <span className="ck-sl__k">{k}</span>
      <div className="ck-sl__row">
        <div className="ck-sl__ctrl">
          <div className="ck-sl__t">
            <span className="ck-sl__bed" />
            {deadL > 0 && <span className="ck-sl__dead" style={{ left: 0, width: deadL + "%" }} />}
            {deadR > 0 && <span className="ck-sl__dead" style={{ right: 0, width: deadR + "%" }} />}
            <span className="ck-sl__fill" style={{ width: pct + "%" }} />
            <input className="ck-sl__r" type="range" min="0" max={last} step="1" value={at}
              aria-label={k} aria-valuetext={typeof value === "string" ? value : String(at)}
              onChange={e => onPick(bounded(+e.target.value))} />
            <span className="ck-sl__h" style={{ left: pct + "%" }} />
          </div>
          {min != null && <div className="ck-sl__ends"><span>{min}</span><span>{max}</span></div>}
        </div>
        <div className="ck-sl__info">
          <span className="ck-sl__v">{value}{hint && <em>{hint}</em>}</span>
          {children}
        </div>
      </div>
    </div>
  );
}

/* ---------------------------------------------------------
   The screen
   --------------------------------------------------------- */
function StudyCheckout({ name, busy, onCancel, onConfirm }) {
  const [st, setSt] = ckS({ tl: 3, cf: 1, bg: 5, mode: "mixed", off: [] });
  const set = (patch, touched) => setSt(s => ckClamp(Object.assign({}, s, patch), touched));

  const q = ckQuote(st);
  const over = q.credits - q.budget;
  const blocked = over > 0 || q.live.length === 0;
  const twinPct = q.n ? Math.round(q.twins / q.n * 100) : 0;

  const pickTl = i => set({ tl: i }, "tl");
  /* The slider's own reachable floor is Confidence's requirement ONLY -
     never the current mode's. mode.floor used to bound this too, which
     meant dragging Timeline toward Today silently stopped at Mixed's own
     2-day minimum: the mode you happened to be in was vetoing the drag
     before ckClamp ever got a chance to move the mode out of the way.
     Confidence's floor is a fact about the rigor you picked; the mode's
     floor is a fact about the mode ckClamp settles on AFTER the drag, so
     it belongs in ckClamp, not here. */
  const floor = CK_CONFIDENCE[st.cf].floor;
  /* The ceiling now tracks the timeline, not whatever mix happens to be
     selected: as long as there is time to recruit, Confidence can reach
     Defensible on its own and the mix follows it (see ckClamp). It is
     only when the timeline itself has ruled out recruiting - Today or
     Tomorrow - that Confidence has nowhere to pull the mix TO, and the
     dial genuinely stops. */
  const cfCeil = CK_TIMELINE[st.tl].peopleOK ? null : CK_MODES[0].capCf;
  const capped = cfCeil != null && st.cf >= cfCeil;
  const toggle = id => set({ off: st.off.indexOf(id) >= 0 ? st.off.filter(x => x !== id) : st.off.concat(id) });
  const raise = () => set({ bg: Math.min(st.bg + 1, CK_BUDGETS.length - 1) });

  const ready = q.days < 1 ? "In about 2 hours"
    : q.recruit ? "In about " + Math.round(q.days) + " days"
    : q.tl.when;

  return (
    <div className="ck-page" data-screen-label="Study checkout">
      <div className="ck-bar">
        <span className="ck-bar__logo"><img className="brand-mark" src="brand/tecovas-mark.png" alt="Tecovas" /></span>
        <span className="ck-bar__k">Review &amp; launch</span>
        <span className="ck-bar__nm">{name}</span>
        <span className="ck-bar__sp" />
        <button className="ck-bar__x" onClick={busy ? null : onCancel} disabled={busy} aria-label="Back to the study">
          <i className="ti ti-x" />
        </button>
      </div>

      <div className="ck-wrap">
        {/* ---- left: the three things you actually decide ---- */}
        <div className="ck-main">
          <section className="ck-card">
            <div className="ck-card__hd">
              <h2>Set up your study</h2>
              <span className="ck-card__n">Timeline · Confidence · Budget</span>
            </div>
            <div className="ck-sls">
              {/* When a group has to be recruited the real date slips past the
                  one you set, and the hint has to say which is which — a bare
                  "about 5 days" beside a slider reading "3 days" looks wrong. */}
              {/* Always draggable, including at Today under Digital Twins -
                  a slider you can see but can't touch is locked no matter
                  how it's styled. Dragging it off Today is exactly how the
                  mix is meant to follow it back out of twins-only (see
                  ckClamp); it used to sit disabled here instead, which drew
                  a control that looked broken rather than one with nowhere
                  further to go on its own. The floor is Confidence's own
                  requirement only, not the current mode's, for the same
                  reason: the mode follows the drag, the drag doesn't wait
                  on the mode. */}
              <CKSlider k="Timeline" value={q.tl.lbl}
                hint={q.recruit ? Math.round(q.days) + " days with recruiting" : q.tl.when}
                min={CK_TIMELINE[floor].lbl} max="2 weeks"
                stops={CK_TIMELINE.length} at={st.tl} floor={floor}
                onPick={pickTl}>
                {q.mode.pin
                  ? <p className="ck-sl__say">{q.tl.say} Digital Twins answer straight away - drag for more runway.</p>
                  : st.tl > floor
                    ? <p className="ck-sl__say">{q.tl.say}</p>
                    : <p className="ck-sl__say">{q.cf.lbl} needs real shoppers in the mix, and recruiting
                        them takes this long, whichever mode you pick.{" "}
                        <a onClick={() => set({ cf: st.cf - 1 }, "cf")}>Ease the rigor</a> to go sooner.</p>}
              </CKSlider>

              {/* The ceiling is the point. A twin prediction is the same class of
                  evidence at 120 respondents and at 320, so buying sample does
                  not turn it into something you can defend in a buy meeting -
                  the dial stops, and the way past it is to ask people, not to
                  ask more twins. Ported from cohesive-makerlabs escalation.ts,
                  where twin-sourced findings cap at Medium by construction. */}
              <CKSlider k="Confidence" value={q.cf.lbl} hint={q.cf.n + " per group"}
                min="Directional" max="Defensible" stops={CK_CONFIDENCE.length} at={st.cf}
                ceil={cfCeil} onPick={i => set({ cf: i }, "cf")}>
                {capped
                  ? <p className="ck-sl__say">There is no time to recruit anyone this fast, so it is twins only, and
                      twin predictions cap at Solid.{" "}
                      <a onClick={() => set({ tl: 2, cf: 2 }, "cf")}>Allow more time</a> to reach Defensible.</p>
                  : <p className="ck-sl__say">{q.cf.say}</p>}
              </CKSlider>

              <CKSlider k="Budget" value={ckFmt(q.budget)} hint="credits"
                min="250" max="10k" stops={CK_BUDGETS.length} at={st.bg} onPick={i => set({ bg: i })}>
                {over > 0
                  ? <p className="ck-sl__say is-warn">This study needs <b>{ckFmt(q.credits)}</b>.{" "}
                      <a onClick={raise}>Allow more</a>
                      {st.mode !== "twins" && <React.Fragment> or{" "}
                        <a onClick={() => set({ mode: st.mode === "people" ? "mixed" : "twins" }, "mode")}>use more Digital Twins</a>
                      </React.Fragment>}.</p>
                  : <p className="ck-sl__say">This study uses <b>{ckFmt(q.credits)}</b>.</p>}
              </CKSlider>
            </div>

            {/* Scope only: what lands on your desk, never a promise about
                what it says. It moves with Confidence, which is the whole
                reason that slider is worth having. */}
            <div className="ck-get">
              <div className="ck-get__k">What you&rsquo;ll get back</div>
              <ul key={st.cf}>
                {q.cf.get.map(g => (
                  <li key={g}><i className="ti ti-check" /><span>{g}</span></li>
                ))}
              </ul>
            </div>
          </section>
        </div>

        {/* ---- right: the consequence ---- */}
        <aside className="ck-side">
          <div className="ck-sum">
            <div className="ck-seg" role="tablist" aria-label="Who answers">
              {/* Always clickable, including Mixed/Real people while Timeline
                  sits at Today or Tomorrow - picking one moves Timeline
                  forward to the earliest day it can support (see ckClamp),
                  rather than the tab disabling and refusing the click. A tab
                  a merchant can see but can't press reads as broken, not as
                  guidance. */}
              {CK_MODES.map(m => (
                <button key={m.id} role="tab" aria-selected={m.id === st.mode}
                  className={m.id === st.mode ? "is-on" : ""}
                  onClick={() => set({ mode: m.id }, "mode")}>{m.lbl}</button>
              ))}
            </div>

            <div className="ck-sum__k">Who we&rsquo;ll ask <em>{ckFmt(q.n)} people</em></div>
            <div className="ck-grps">
              {q.rows.map(r => (
                <div className={"ck-grp" + (r.off ? " is-off" : "")} key={r.seg.id}>
                  <span className="ck-grp__nm">{r.seg.name}</span>
                  <span className="ck-grp__n">{ckFmt(r.n)}</span>
                  <button className="ck-grp__x" onClick={() => toggle(r.seg.id)}
                    title={r.off ? "Put back" : "Remove"}
                    aria-label={(r.off ? "Put back " : "Remove ") + r.seg.name}>{r.off ? "+" : "×"}</button>
                </div>
              ))}
            </div>

            {/* explore-intake's own split bar, hatch and legend. Same fact
                as the intake rail, so it is the same picture and there is
                one definition of it. */}
            <div className="ck-split">
              <div className="i4-split">
                <i className="i4-split__t" style={{ width: twinPct + "%" }} />
                <i className="i4-split__h" style={{ width: (100 - twinPct) + "%" }} />
              </div>
              <div className="i4-legend">
                <span><i className="i4-key i4-key--t" />Digital Twins<b>{ckFmt(q.twins)}</b></span>
                <span><i className="i4-key i4-key--h" />Recruited shoppers<b>{ckFmt(q.humans)}</b></span>
              </div>
            </div>

            <div className="ck-sum__rows">
              <div className="ck-sum__r"><span>Results ready</span><b>{ready}</b></div>
            </div>
            <div className={"ck-sum__tot" + (over > 0 ? " is-over" : "")}>
              <span>Credits</span><b>{ckFmt(q.credits)}</b>
            </div>
            {over <= 0 && <div className="ck-sum__left">{ckFmt(-over)} left for a follow-up</div>}

            {q.recruit && st.mode !== "twins" ? (
              <div className="ck-warn ck-sum__flag">
                <i className="ti ti-alert-triangle" />
                <span>We&rsquo;ve never asked Nordic shoppers before, so we need a few extra days to
                  find them. <a onClick={() => toggle("nordics")}>Leave them out</a> for a sooner date.</span>
              </div>
            ) : over > 0 ? (
              <div className="ck-warn ck-sum__flag">
                <i className="ti ti-alert-triangle" />
                <span>This is over the {ckFmt(q.budget)} you allowed.{" "}
                  <a onClick={raise}>Allow more</a> or remove a group.</span>
              </div>
            ) : null}

            {/* The landing screen reports what actually went into field, so it is
                handed the sliders' answer rather than the tier's recommendation —
                the two only agree if nobody moved a dial. */}
            <button className="ck-go" disabled={busy || blocked}
              onClick={() => onConfirm && onConfirm({
                total: q.n, name: q.mode.lbl, back: ready,
                who: q.twins && q.humans
                  ? ckFmt(q.twins) + " Digital Twins, " + ckFmt(q.humans) + " recruited shoppers"
                  : q.humans ? ckFmt(q.humans) + " recruited shoppers"
                  : ckFmt(q.twins) + " Digital Twins"
              })}>
              {busy ? <React.Fragment><span className="sf-spin" />Sending it out…</React.Fragment>
                : q.live.length === 0 ? "Add a group to continue"
                : over > 0 ? "Over what you allowed"
                : <React.Fragment>Launch study<i className="ti ti-arrow-right" /></React.Fragment>}
            </button>
            <button className="ck-back" onClick={onCancel} disabled={busy}>Keep editing</button>
            <div className="ck-sum__fine">Credits are used when the study starts, not now.</div>
          </div>
        </aside>
      </div>
    </div>
  );
}

Object.assign(window, { StudyCheckout: StudyCheckout, ckQuote: ckQuote, CK_SEGMENTS: CK_SEGMENTS });
