/* ASCX — Landing: the two data-capture flows.
   1. Checkout — ordering the box IS creating the account. Multi-step sheet,
      opened from the cart drawer.
   2. Free signup — name, email, phone only. Opened from the nav and the
      free-resources band above the final CTA.

   Both submit through window.ASCXSignup (landing/signup.js), which is the one
   configurable integration point. No payment details are collected here, ever:
   /api/orders returns a Stripe Checkout URL and the visitor is handed to
   Stripe's own hosted page to pay.

   Reads the DS bundle + window.LV + window.ASCXCart.
   Exposes window.ASCXFlows (open/close) and window.LVX (components). text/babel. */
(function () {
const NS = window.ASCXDesignSystem_77a68b;
const { Button, Input, Eyebrow } = NS;
const LV = window.LV;
const SIGNUP = window.ASCXSignup;

const money = (n) => '$' + (Number.isInteger(n) ? n : n.toFixed(2));

/* ---------------- flow store (which sheet is open) ---------------- */
const flows = {
  view: null,                       // null | 'checkout' | 'free'
  listeners: new Set(),
  subscribe(fn) { this.listeners.add(fn); return () => this.listeners.delete(fn); },
  emit() { this.listeners.forEach((fn) => fn()); },
  open(v) { this.view = v; this.emit(); },
  openCheckout() { this.open('checkout'); },
  openFree() { this.open('free'); },
  close() { this.view = null; this.emit(); },
};
window.ASCXFlows = flows;

function useFlows() {
  const [, force] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => flows.subscribe(force), []);
  return flows;
}
function useCart() {
  const [, force] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => window.ASCXCart.subscribe(force), []);
  return window.ASCXCart;
}

/* ---------------- in-progress drafts (survive a refresh) ---------------- */
const DRAFT_KEY = 'ascx_checkout_draft_v1';
const LEAD_DRAFT_KEY = 'ascx_lead_draft_v1';
/* Set just before we hand off to Stripe, read when Stripe sends the visitor
   back, so the return can be told apart from someone typing the URL in. */
const PENDING_KEY = 'ascx_pending_checkout_v1';

function loadDraft(key, empty) {
  try {
    const s = JSON.parse(localStorage.getItem(key));
    if (s && typeof s === 'object') return Object.assign({}, empty, s);
  } catch (e) {}
  return Object.assign({}, empty);
}
function saveDraft(key, v) { try { localStorage.setItem(key, JSON.stringify(v)); } catch (e) {} }
function clearDraft(key) { try { localStorage.removeItem(key); } catch (e) {} }

/* ---------------- validation ---------------- */
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
const digitsOf = (s) => String(s || '').replace(/[^0-9]/g, '');
const filled = (v) => !!(v && String(v).trim());

function checkContact(f) {
  const e = {};
  if (!filled(f.fullName) || f.fullName.trim().length < 2) e.fullName = 'Enter your full name.';
  if (!EMAIL_RE.test(String(f.email).trim())) e.email = 'Enter an email address we can reach you on.';
  if (digitsOf(f.phone).length < 10) e.phone = 'Enter a phone number with at least 10 digits.';
  /* On a gift the buyer above stays the paying customer; these identify the
     person who will actually run the thirty days. Their email is optional —
     leaving it blank keeps the surprise, and the box activates from its own
     Day 00 card instead. */
  if (f.gift) {
    if (!filled(f.recipientName) || f.recipientName.trim().length < 2) e.recipientName = 'Enter their full name.';
    if (filled(f.recipientEmail) && !EMAIL_RE.test(String(f.recipientEmail).trim())) e.recipientEmail = 'That email does not look right.';
  }
  return e;
}

function validateStep(step, f) {
  const e = {};
  if (step === 0) {
    if (f.gift !== true && f.gift !== false) e.gift = 'Pick one to continue.';
  }
  if (step === 1) {
    if (!filled(f.gender)) e.gender = 'Pick one to continue.';
  }
  if (step === 2) {
    if (!f.sweatshirtSize) e.sweatshirtSize = 'Choose a sweatshirt size.';
    if (!f.zipSize) e.zipSize = 'Choose a quarter-zip size.';
  }
  if (step === 3) {
    Object.assign(e, checkContact(f));
    if (!filled(f.line1)) e.line1 = 'Enter your street address.';
    if (!filled(f.city)) e.city = 'Enter your city.';
    if (!filled(f.region)) e.region = 'Enter your state or region.';
    if (!filled(f.postalCode) || f.postalCode.trim().length < 3) e.postalCode = 'Enter your postal code.';
    if (!filled(f.country)) e.country = 'Enter your country.';
  }
  if (step === 4) {
    if (!f.terms) e.terms = 'Agree to the terms to reserve your pre-order.';
  }
  return e;
}

const focusField = (key) => {
  window.requestAnimationFrame(() => {
    const el = document.getElementById(key);
    if (el && el.focus) el.focus();
  });
};

/* ---------------- shared sheet chrome ---------------- */
function Sheet({ onClose, labelledBy, small, children }) {
  const panel = React.useRef(null);
  React.useEffect(() => {
    const prev = document.activeElement;
    document.body.style.overflow = 'hidden';
    const onKey = (e) => {
      if (e.key === 'Escape') { e.stopPropagation(); onClose(); return; }
      if (e.key !== 'Tab' || !panel.current) return;
      const f = panel.current.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])');
      if (!f.length) return;
      const first = f[0], last = f[f.length - 1];
      if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
      else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
    };
    document.addEventListener('keydown', onKey, true);
    if (panel.current) {
      const first = panel.current.querySelector('input') || panel.current.querySelector('.ck-step.on') || panel.current.querySelector('button');
      if (first) first.focus();
    }
    return () => {
      document.removeEventListener('keydown', onKey, true);
      document.body.style.overflow = '';
      if (prev && prev.focus) prev.focus();
    };
  }, []);
  return (
    <div className="ck-overlay" onMouseDown={onClose}>
      <div className={'ck-panel' + (small ? ' sm' : '')} ref={panel} role="dialog" aria-modal="true" aria-labelledby={labelledBy}
        onMouseDown={(e) => e.stopPropagation()}>
        <button className="ck-close" type="button" onClick={onClose} aria-label="Close">×</button>
        {children}
      </div>
    </div>
  );
}

/* ---------------- form primitives ---------------- */
function Field({ id, label, value, onChange, err, hint, type, autoComplete, optional, inputMode, placeholder }) {
  const describedBy = err ? id + '-err' : (hint ? id + '-hint' : undefined);
  return (
    <div className="ck-field">
      <label className="ck-label" htmlFor={id}>
        {label}{optional ? <span className="ck-opt"> optional</span> : <span className="ck-req"> *</span>}
      </label>
      <Input
        id={id} name={id} className="ck-in" type={type || 'text'} value={value}
        autoComplete={autoComplete} inputMode={inputMode} placeholder={placeholder}
        onChange={(e) => onChange(e.target.value)}
        aria-invalid={err ? 'true' : undefined}
        aria-describedby={describedBy}
        aria-required={optional ? undefined : 'true'}
        /* font-size is set by .ck-in — 16px on phones (below that iOS zooms
           the page on focus), back to 14px from 768px up. */
        style={{ padding: '13px 14px', letterSpacing: '0.01em' }}
      />
      {hint && !err && <div className="ck-hint" id={id + '-hint'}>{hint}</div>}
      {err && <div className="ck-err" id={id + '-err'} role="alert">{err}</div>}
    </div>
  );
}

function SizeRow({ id, legend, hint, value, onChange, err }) {
  return (
    <fieldset className="ck-fs">
      <legend className="ck-label">{legend}<span className="ck-req"> *</span></legend>
      {hint && <div className="ck-hint">{hint}</div>}
      <div className="ck-sizes">
        {LV.FIT_SIZES.map((s, i) => (
          <button key={s} id={i === 0 ? id : undefined} type="button"
            className={'ck-size' + (value === s ? ' on' : '')}
            aria-pressed={value === s} aria-label={legend + ' ' + s}
            onClick={() => onChange(s)}>{s}</button>
        ))}
      </div>
      {err && <div className="ck-err" role="alert">{err}</div>}
    </fieldset>
  );
}

function CheckRow({ id, checked, onChange, label, note, err }) {
  return (
    <div className="ck-check">
      <input className="ck-cb" type="checkbox" id={id} checked={checked}
        onChange={(e) => onChange(e.target.checked)}
        aria-invalid={err ? 'true' : undefined} aria-describedby={err ? id + '-err' : undefined} />
      <label htmlFor={id}>
        <span className="ck-check-t">{label}</span>
        {note && <span className="ck-check-d">{note}</span>}
        {err && <span className="ck-err" id={id + '-err'} role="alert">{err}</span>}
      </label>
    </div>
  );
}

function ErrorBanner({ children }) {
  return <div className="ck-banner" role="alert">{children}</div>;
}

/* ---------------- order summary ---------------- */
function Summary({ items, subtotal, shipping, total, freeShip, savings }) {
  return (
    <div className="ck-side">
      <div className="ck-side-k">Your pre-order</div>
      <div className="ck-lines">
        {items.map((it) => (
          <div className="ck-line" key={it.key}>
            <div>
              <div className="ck-line-n">{it.name}</div>
              <div className="ck-line-m">
                {it.kind === 'box' ? (it.sub || 'Box + platform') : (it.size === 'OS' ? 'One size' : 'Size ' + it.size)} · Qty {it.qty}
              </div>
            </div>
            <div className="ck-line-p">
              {money(it.price * it.qty)}
              {it.list > it.price && <span className="ck-line-l">{money(it.list * it.qty)}</span>}
            </div>
          </div>
        ))}
      </div>
      {/* same order as the cart drawer: struck full price, red saving, then
          what you actually pay */}
      <div className="ck-tot">
        {savings > 0 && (
          <React.Fragment>
            <div className="ck-tot-r ct-was"><span>Full price</span><span>{money(subtotal + savings)}</span></div>
            <div className="ck-tot-r ct-save"><span>You save</span><span>&ndash;{money(savings)}</span></div>
          </React.Fragment>
        )}
        <div className="ck-tot-r ct-pay"><span>Subtotal</span><span>{money(subtotal)}</span></div>
        <div className="ck-tot-r"><span>Shipping</span><span>{shipping === 0 ? (freeShip ? 'Free' : '—') : money(shipping)}</span></div>
        <div className="ck-tot-r ck-tot-t"><span>Total</span><span>{money(total)}</span></div>
      </div>
    </div>
  );
}

/* ---------------- checkout ---------------- */
/* Five short steps, quick taps first. Three button-only questions build
   momentum before anyone is asked to type, which is where checkouts lose
   people. */
const STEPS = ['Who', 'About', 'Sizes', 'Details', 'Review'];
const LAST = STEPS.length - 1;

const EMPTY_ORDER = {
  fullName: '', email: '', phone: '',
  line1: '', line2: '', city: '', region: '', postalCode: '', country: 'United States',
  /* null, not false — the first step is a real question and must be answered
     rather than defaulted past. */
  gift: null, gender: '', recipientName: '', recipientEmail: '',
  sweatshirtSize: '', zipSize: '',
  newsletter: true, terms: false,
};

function buildOrderPayload(f, cart) {
  return {
    type: 'order',
    source: 'ascx-landing',
    submittedAt: new Date().toISOString(),
    customer: {
      fullName: f.fullName.trim(),
      email: f.email.trim(),
      phone: f.phone.trim(),
    },
    shipping: {
      line1: f.line1.trim(),
      line2: f.line2.trim(),
      city: f.city.trim(),
      region: f.region.trim(),
      postalCode: f.postalCode.trim(),
      country: f.country.trim(),
    },
    /* `customer` is always whoever is paying, so the receipt and the later
       payment link never follow the gift. `recipient` is who runs the climb. */
    gift: !!f.gift,
    recipient: f.gift
      ? { fullName: f.recipientName.trim(), email: filled(f.recipientEmail) ? f.recipientEmail.trim() : null }
      : null,
    sizes: { sweatshirt: f.sweatshirtSize, quarterZip: f.zipSize },
    /* Whoever runs the thirty days — the recipient on a gift. Drives the cut
       of the earned pieces, so it belongs with the sizes. */
    gender: f.gender || null,
    /* Whoever referred them, held by track.js from a ?ref= link. */
    referredBy: (typeof window.ASCXRef === 'function' && window.ASCXRef()) || null,
    items: cart.list().map((it) => ({
      sku: it.id,
      kind: it.kind,
      name: it.name,
      size: it.kind === 'box' ? null : it.size,
      unitPrice: it.price,
      unitList: it.list || it.price,
      quantity: it.qty,
    })),
    totals: {
      currency: 'USD',
      subtotal: Number(cart.subtotal().toFixed(2)),
      shipping: Number(cart.shipFee().toFixed(2)),
      total: Number(cart.total().toFixed(2)),
      savings: Number(cart.savings().toFixed(2)),
    },
    consent: { newsletter: !!f.newsletter, terms: !!f.terms },
    meta: SIGNUP.meta({ cohort: LV.LAUNCH.cohort }),
  };
}

function Checkout({ onClose }) {
  const cart = useCart();
  /* The typed fields resume from the draft — re-entering an address is the
     friction worth saving. The three tap answers deliberately do NOT: they
     advance on selection, so a restored one would render step 1 looking
     already answered with no Continue to press, and the only way forward
     would be to tap the same option again. Starting them blank also means a
     red border only ever appears after a real tap, which is what going Back
     should show. */
  const [f, setF] = React.useState(() => Object.assign(
    loadDraft(DRAFT_KEY, EMPTY_ORDER),
    { gift: null, gender: '', sweatshirtSize: '', zipSize: '' }
  ));
  const [step, setStep] = React.useState(0);
  const [reached, setReached] = React.useState(0);
  const [errs, setErrs] = React.useState({});
  const [busy, setBusy] = React.useState(false);
  const [fail, setFail] = React.useState(null);
  const [done, setDone] = React.useState(null);

  React.useEffect(() => { saveDraft(DRAFT_KEY, f); }, [f]);

  const set = (k) => (v) => { setF((p) => Object.assign({}, p, { [k]: v })); setErrs((p) => (p[k] ? Object.assign({}, p, { [k]: null }) : p)); };
  const err = (k) => errs[k] || null;

  const items = cart.list();
  const empty = items.length === 0;

  /* The three button-only steps advance on tap. Nothing else is on those
     screens, so a Continue button would be a second tap that asks nothing.
     A short beat lets the selected state register before the step changes. */
  const advance = React.useCallback((n) => {
    window.setTimeout(() => {
      setStep((cur) => { setReached((r) => Math.max(r, cur + 1)); return cur + 1; });
    }, 180);
  }, []);

  const next = () => {
    const e = validateStep(step, f);
    setErrs(e);
    const keys = Object.keys(e);
    if (keys.length) { focusField(keys[0]); return; }
    const n = step + 1;
    setStep(n);
    setReached((r) => Math.max(r, n));
  };

  const submit = () => {
    const e = validateStep(LAST, f);
    setErrs(e);
    const keys = Object.keys(e);
    if (keys.length) { focusField(keys[0]); return; }
    const payload = buildOrderPayload(f, cart);
    setBusy(true);
    setFail(null);
    SIGNUP.submitOrder(payload).then((res) => {
      /* A real backend answers with a Stripe Checkout URL — hand the visitor
         straight to Stripe's hosted page, where the card is entered. The cart
         and draft are deliberately NOT cleared here: payment has not happened
         yet, and they must survive a cancel or a back button. */
      if (res.checkoutUrl) {
        try { localStorage.setItem(PENDING_KEY, JSON.stringify({ id: res.id, orderNo: res.orderNo || null, ref: res.referralCode || null, email: payload.customer.email, at: Date.now() })); } catch (e) {}
        window.location.assign(res.checkoutUrl);
        return;
      }
      /* No API configured: the static demo path. Nothing was charged. */
      setBusy(false);
      setDone({ email: payload.customer.email, total: payload.totals.total, mode: res.mode, gift: payload.gift, recipient: payload.recipient });
      clearDraft(DRAFT_KEY);
      cart.complete();
    }).catch((e2) => {
      setBusy(false);
      setFail((e2 && e2.message) || 'Something went wrong. Try again.');
    });
  };

  /* ---- confirmation ---- */
  if (done) {
    return (
      <Sheet onClose={onClose} labelledBy="ck-done-t" small>
        <div className="ck-done">
          <div className="ck-done-mark">
            <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="2" strokeLinecap="square"><path d="M4 12l5 5L20 6" /></svg>
          </div>
          <div className="ck-done-t" id="ck-done-t">Pre-order reserved</div>
          <p className="ck-done-d">
            {done.gift ? (
              <React.Fragment>
                A place in {LV.LAUNCH.cohort} is locked for <b>{done.recipient && done.recipient.fullName}</b>. We sent your confirmation to <b>{done.email}</b>.
              </React.Fragment>
            ) : (
              <React.Fragment>
                Your place in {LV.LAUNCH.cohort} is locked. We sent a confirmation to <b>{done.email}</b>. That email also carries the link that sets up your account, so there is no password to remember.
              </React.Fragment>
            )}
          </p>
          <ul className="ck-done-list">
            {done.gift ? (
              <React.Fragment>
                <li><span className="sq" />The box ships to them end of year and arrives before the holidays.</li>
                <li><span className="sq" />They set up their account from the Day 00 card inside{done.recipient && done.recipient.email ? ', and we will email them once it ships' : ''}.</li>
                <li><span className="sq" />{LV.LAUNCH.cohort} pulls Day 01 together on {LV.LAUNCH.startLong}.</li>
              </React.Fragment>
            ) : (
              <React.Fragment>
                <li><span className="sq" />Your box ships end of year and arrives before the holidays.</li>
                <li><span className="sq" />Set up over the break: sign the Standard, set your goals, send a Challenge Link.</li>
                <li><span className="sq" />{LV.LAUNCH.cohort} pulls Day 01 together on {LV.LAUNCH.startLong}.</li>
              </React.Fragment>
            )}
          </ul>
          {/* Only reachable with no ASCX_API_URL configured, i.e. the static
              demo. A live site redirects to Stripe instead of landing here. */}
          <div className="ck-pay">
            <div className="ck-pay-k">Demo mode</div>
            <div className="ck-pay-d">
              This site has no payment backend configured, so nothing was charged and {money(done.total)} was not taken. The order was queued in this browser only.
            </div>
          </div>
          <Button variant="primary" fullWidth onClick={onClose}>Back to the page</Button>
        </div>
      </Sheet>
    );
  }

  /* ---- empty guard ---- */
  if (empty) {
    return (
      <Sheet onClose={onClose} labelledBy="ck-empty-t" small>
        <div className="ck-done">
          <div className="ck-done-t" id="ck-empty-t">Your pre-order is empty</div>
          <p className="ck-done-d">Add the Full Experience box to reserve your place in {LV.LAUNCH.cohort}, then come back.</p>
          <Button variant="primary" fullWidth onClick={() => { onClose(); const el = document.getElementById('pricing'); if (el) el.scrollIntoView({ behavior: 'smooth' }); }}>Pre-order the box · $99</Button>
        </div>
      </Sheet>
    );
  }

  return (
    <Sheet onClose={onClose} labelledBy="ck-title">
      <div className="ck-head">
        <div className="ck-ey">{LV.LAUNCH.cohort} · Pre-order</div>
        <div className="ck-title" id="ck-title">Reserve your place</div>
      </div>

      {/* A bar rather than five labelled chips: it shows how little is left at a
          glance, and five chips do not fit a phone without shrinking to noise.
          Completed segments stay clickable so nothing is a one-way door. */}
      <div className="ck-prog" role="group" aria-label={`Step ${step + 1} of ${STEPS.length}`}>
        <div className="ck-prog-bar">
          {STEPS.map((s, i) => (
            <button key={s} type="button"
              className={'ck-prog-seg' + (i < step ? ' did' : '') + (i === step ? ' on' : '')}
              disabled={i > reached}
              aria-label={`Step ${i + 1}: ${s}`}
              aria-current={i === step ? 'step' : undefined}
              onClick={() => { if (i <= reached) setStep(i); }} />
          ))}
        </div>
        <div className="ck-prog-k">
          <span>{STEPS[step]}</span>
          <span className="ck-prog-n">Step {step + 1} of {STEPS.length}</span>
        </div>
      </div>

      {/* the order summary only rides alongside Review — the cart drawer has
          already shown the pricing, and repeating it on account/shipping/fit
          just narrows those steps for no new information */}
      <div className="ck-body">
        <form className={'ck-form' + (step < LAST ? ' ck-form-solo' : '')} noValidate onSubmit={(e) => { e.preventDefault(); if (step < LAST) next(); else submit(); }}>
          {/* ---- 1. Who is it for ---- */}
          {step === 0 && (
            <React.Fragment>
              <div className="ck-step-h">Who is this for?</div>
              <div className="ck-pick two">
                {[[false, 'Me'], [true, 'A gift']].map(([v, t]) => (
                  <button key={t} type="button" className={'ck-pick-o' + (f.gift === v ? ' on' : '')}
                    aria-pressed={f.gift === v} onClick={() => { set('gift')(v); advance(); }}>
                    <span className="ck-pick-t">{t}</span>
                  </button>
                ))}
              </div>
            </React.Fragment>
          )}

          {/* ---- 2. Gender of whoever runs it ---- */}
          {step === 1 && (
            <React.Fragment>
              <div className="ck-step-h">{f.gift ? 'Is your friend…' : 'You are…'}</div>
              <div className="ck-pick two">
                {['Male', 'Female'].map((g) => (
                  <button key={g} type="button" className={'ck-pick-o' + (f.gender === g ? ' on' : '')}
                    aria-pressed={f.gender === g} onClick={() => { set('gender')(g); advance(); }}>
                    <span className="ck-pick-t">{g}</span>
                  </button>
                ))}
              </div>
            </React.Fragment>
          )}

          {/* ---- 3. The two sized earned pieces ---- */}
          {step === 2 && (
            <React.Fragment>
              <div className="ck-step-h">{f.gift ? 'Their sizes' : 'Your sizes'}</div>
              {/* Advances once BOTH are chosen — advancing on the first would
                  strand the second question. */}
              <SizeRow id="sweatshirtSize" legend="Sweatshirt" value={f.sweatshirtSize} err={err('sweatshirtSize')}
                onChange={(v) => { set('sweatshirtSize')(v); if (f.zipSize) advance(); }} />
              <SizeRow id="zipSize" legend="Quarter-zip" value={f.zipSize} err={err('zipSize')}
                onChange={(v) => { set('zipSize')(v); if (f.sweatshirtSize) advance(); }} />
            </React.Fragment>
          )}

          {/* ---- 4. Everything that needs typing, in one pass ---- */}
          {step === 3 && (
            <React.Fragment>
              <div className="ck-step-h">Your details</div>
              <p className="ck-step-d">
                {f.gift
                  ? 'Yours for the receipt. The box ships to them.'
                  : 'This becomes your ASCX account. No password to invent today.'}
              </p>
              <Field id="fullName" label={f.gift ? 'Your full name' : 'Full name'} value={f.fullName} onChange={set('fullName')} err={err('fullName')} autoComplete="name" />
              <Field id="email" label={f.gift ? 'Your email' : 'Email'} type="email" value={f.email} onChange={set('email')} err={err('email')} autoComplete="email" inputMode="email" />
              <Field id="phone" label={f.gift ? 'Your phone' : 'Phone'} type="tel" value={f.phone} onChange={set('phone')} err={err('phone')} autoComplete="tel" inputMode="tel" />

              {f.gift && (
                <React.Fragment>
                  <div className="ck-sub-h">Who it&rsquo;s for</div>
                  <Field id="recipientName" label="Their full name" value={f.recipientName} onChange={set('recipientName')} err={err('recipientName')} autoComplete="off" />
                  <Field id="recipientEmail" label="Their email" type="email" value={f.recipientEmail} onChange={set('recipientEmail')} err={err('recipientEmail')} autoComplete="off" inputMode="email" optional
                    hint="Leave blank to keep it a surprise." />
                </React.Fragment>
              )}

              <div className="ck-sub-h">{f.gift ? 'Ship it to' : 'Ship it to'}</div>
              <Field id="line1" label="Address" value={f.line1} onChange={set('line1')} err={err('line1')} autoComplete="address-line1" />
              <Field id="line2" label="Apartment, suite" value={f.line2} onChange={set('line2')} err={err('line2')} autoComplete="address-line2" optional />
              <div className="ck-grid">
                <Field id="city" label="City" value={f.city} onChange={set('city')} err={err('city')} autoComplete="address-level2" />
                <Field id="region" label="State or region" value={f.region} onChange={set('region')} err={err('region')} autoComplete="address-level1" />
                <Field id="postalCode" label="Postal code" value={f.postalCode} onChange={set('postalCode')} err={err('postalCode')} autoComplete="postal-code" />
                <Field id="country" label="Country" value={f.country} onChange={set('country')} err={err('country')} autoComplete="country-name" />
              </div>
            </React.Fragment>
          )}

          {step === LAST && (
            <React.Fragment>
              <div className="ck-step-h">Check it over</div>

              <div className="ck-review">
                <div className="ck-rev-r"><span className="ck-rev-k">{f.gift ? 'Paid by' : 'Account'}</span><span className="ck-rev-v">{f.fullName}<br />{f.email}<br />{f.phone}</span></div>
                {f.gift && (
                  <div className="ck-rev-r"><span className="ck-rev-k">Gift for</span><span className="ck-rev-v">
                    {f.recipientName}{filled(f.recipientEmail) ? <React.Fragment><br />{f.recipientEmail}</React.Fragment> : <React.Fragment><br /><span className="ck-rev-note">No email · activates from the Day 00 card</span></React.Fragment>}
                  </span></div>
                )}
                <div className="ck-rev-r"><span className="ck-rev-k">Ships to</span><span className="ck-rev-v">
                  {f.line1}{f.line2 ? <React.Fragment><br />{f.line2}</React.Fragment> : null}<br />
                  {f.city}, {f.region} {f.postalCode}<br />{f.country}
                </span></div>
                <div className="ck-rev-r"><span className="ck-rev-k">Marks fit</span><span className="ck-rev-v">{f.gender} · Sweatshirt {f.sweatshirtSize} · Quarter-zip {f.zipSize}</span></div>
              </div>
              <button type="button" className="ck-edit" onClick={() => setStep(0)}>Edit your details</button>

              {/* What they are about to be charged sits between the details they just
                  checked and the boxes they are about to tick, so the amount is the
                  last thing read before agreeing to it. */}
              <Summary items={items} subtotal={cart.subtotal()} shipping={cart.shipFee()} total={cart.total()} freeShip={cart.freeShip()} savings={cart.savings()} />

              <div className="ck-consents">
                <CheckRow id="newsletter" checked={!!f.newsletter} onChange={set('newsletter')}
                  label="Send me the cohort emails"
                  note="Ship dates, Drop 01, and the Day 01 start line. Leave any time." />
                <CheckRow id="terms" checked={!!f.terms} onChange={set('terms')} err={err('terms')}
                  label="I agree to the terms"
                  note="Pre-order terms and privacy policy." />
              </div>

              {fail && <ErrorBanner>{fail}</ErrorBanner>}
            </React.Fragment>
          )}

          <div className="ck-foot">
            {step > 0 && <Button variant="ghost" size="sm" onClick={() => setStep(step - 1)} style={{ flex: '0 0 auto' }}>Back</Button>}
            {/* Steps 0-2 advance on tap, so a Continue there would be a button
                that asks nothing. Back stays, so a wrong tap is recoverable. */}
            {step < 3
              ? null
              : step < LAST
                ? <Button variant="primary" size="sm" onClick={next} style={{ flex: 1 }}>Continue</Button>
                : <Button variant="primary" size="sm" onClick={submit} disabled={busy} style={{ flex: 1 }}>
                    {busy ? 'Opening secure checkout…' : 'Continue to payment · ' + money(cart.total())}
                  </Button>}
          </div>
        </form>

      </div>
    </Sheet>
  );
}

/* ---------------- free signup (no purchase) ---------------- */
const EMPTY_LEAD = { fullName: '', email: '', phone: '', newsletter: true };

function FreeSignup({ onClose }) {
  const [f, setF] = React.useState(() => loadDraft(LEAD_DRAFT_KEY, EMPTY_LEAD));
  const [errs, setErrs] = React.useState({});
  const [busy, setBusy] = React.useState(false);
  const [fail, setFail] = React.useState(null);
  const [done, setDone] = React.useState(null);

  React.useEffect(() => { saveDraft(LEAD_DRAFT_KEY, f); }, [f]);
  const set = (k) => (v) => { setF((p) => Object.assign({}, p, { [k]: v })); setErrs((p) => (p[k] ? Object.assign({}, p, { [k]: null }) : p)); };

  const submit = () => {
    const e = checkContact(f);
    setErrs(e);
    const keys = Object.keys(e);
    if (keys.length) { focusField(keys[0]); return; }
    const payload = {
      type: 'lead',
      source: 'ascx-landing',
      intent: 'free-resources',
      submittedAt: new Date().toISOString(),
      contact: { fullName: f.fullName.trim(), email: f.email.trim(), phone: f.phone.trim() },
      consent: { newsletter: !!f.newsletter },
      meta: SIGNUP.meta(),
    };
    setBusy(true);
    setFail(null);
    SIGNUP.submitLead(payload).then(() => {
      setBusy(false);
      setDone({ email: payload.contact.email });
      clearDraft(LEAD_DRAFT_KEY);
    }).catch((e2) => {
      setBusy(false);
      setFail((e2 && e2.message) || 'Something went wrong. Try again.');
    });
  };

  if (done) {
    return (
      <Sheet onClose={onClose} labelledBy="fr-done-t" small>
        <div className="ck-done">
          <div className="ck-done-mark">
            <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="2" strokeLinecap="square"><path d="M4 12l5 5L20 6" /></svg>
          </div>
          <div className="ck-done-t" id="fr-done-t">You are on the list</div>
          <p className="ck-done-d">Everything below is on its way to <b>{done.email}</b>.</p>
          <ul className="ck-done-list">
            {LV.FREE.items.map((t) => <li key={t}><span className="sq" />{t}</li>)}
          </ul>
          <p className="ck-done-d">{LV.FREE.fine}</p>
          <Button variant="primary" fullWidth onClick={onClose}>Back to the page</Button>
        </div>
      </Sheet>
    );
  }

  return (
    <Sheet onClose={onClose} labelledBy="fr-title" small>
      <div className="ck-head">
        <div className="ck-ey">{LV.FREE.eyebrow}</div>
        <div className="ck-title" id="fr-title">Join free</div>
        <div className="ck-sub">No purchase. Three fields, and the resources are yours.</div>
      </div>
      <form className="ck-form ck-form-solo" noValidate onSubmit={(e) => { e.preventDefault(); submit(); }}>
        <ul className="ck-free-list">
          {LV.FREE.items.map((t) => <li key={t}><span className="sq" />{t}</li>)}
        </ul>
        <Field id="fullName" label="Full name" value={f.fullName} onChange={set('fullName')} err={errs.fullName} autoComplete="name" />
        <Field id="email" label="Email" type="email" value={f.email} onChange={set('email')} err={errs.email} autoComplete="email" inputMode="email" />
        <Field id="phone" label="Phone" type="tel" value={f.phone} onChange={set('phone')} err={errs.phone} autoComplete="tel" inputMode="tel"
          hint="So we can text you the Day 01 start line. Nothing else." />
        <div className="ck-consents">
          <CheckRow id="lead-newsletter" checked={!!f.newsletter} onChange={set('newsletter')}
            label="Send me the cohort emails"
            note="Ship dates, Drop 01, and the Day 01 start line. Leave any time." />
        </div>
        {fail && <ErrorBanner>{fail}</ErrorBanner>}
        <div className="ck-foot">
          <Button variant="primary" size="sm" onClick={submit} disabled={busy} style={{ flex: 1 }}>
            {busy ? 'Sending…' : 'Send me the resources'}
          </Button>
        </div>
        <div className="ck-fine">{LV.FREE.fine}</div>
      </form>
    </Sheet>
  );
}

/* ---------------- the free-resources band (body entry point) ---------------- */
function FreeBand() {
  return (
    <section id="free" className="lp-section" data-screen-label="Free resources" style={{ paddingTop: 0, paddingBottom: 90 }}>
      <div className="lp-wrap">
        <div className="free-band reveal">
          <div>
            <div className="free-k">{LV.FREE.eyebrow}</div>
            <div className="free-t">{LV.FREE.title}</div>
            <div className="free-d">{LV.FREE.line}</div>
            <ul className="free-list">
              {LV.FREE.items.map((t) => <li key={t}><span className="sq" />{t}</li>)}
            </ul>
          </div>
          <div className="free-cta">
            <Button variant="primary" size="md" fullWidth onClick={() => flows.openFree()}>{LV.FREE.cta}</Button>
            <div className="free-fine">{LV.FREE.fine}</div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------------- root mount ---------------- */
/* ---------------- return from Stripe ----------------
   Stripe sends the visitor back to /?checkout=success|cancelled. Success is
   only ever used to show a receipt — fulfilment is driven by the webhook, not
   by this URL, because anyone can visit it. On success the cart and draft are
   cleared; on cancel both are left exactly as they were so the visitor can
   pick up where they stopped. */
function ShareBlock({ code }) {
  const [state, setState] = React.useState('idle');
  const url = 'https://ascxchallenge.com/?ref=' + code;

  async function share() {
    const text = 'I just reserved my place in Cohort 001. Thirty days, starting January 1. Come with me.';
    /* navigator.share only exists on phones and only over https, which is
       exactly where it is worth having. Everywhere else, copy. */
    if (navigator.share) {
      try { await navigator.share({ title: 'ASCX', text, url }); return; }
      catch (e) { if (e && e.name === 'AbortError') return; }
    }
    try {
      await navigator.clipboard.writeText(url);
      setState('copied');
      window.setTimeout(() => setState('idle'), 2200);
    } catch (e) {
      setState('manual');
    }
  }

  return (
    <div className="ck-share">
      <div className="ck-share-k">Challenge a friend</div>
      <p className="ck-share-d">Send this to whoever you want as your accountability partner.</p>
      <button type="button" className="ck-share-b" onClick={share}>
        {state === 'copied' ? 'Link copied' : 'Share your link'}
      </button>
      <div className="ck-share-u">{url}</div>
    </div>
  );
}

function StripeReturn() {
  const [view, setView] = React.useState(null);
  const cart = useCart();

  React.useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const state = params.get('checkout');
    if (state !== 'success' && state !== 'cancelled') return;

    let pending = null;
    try { pending = JSON.parse(localStorage.getItem(PENDING_KEY) || 'null'); } catch (e) {}

    if (state === 'success') {
      /* Prefer the number Stripe hands back in the URL — localStorage may have
         been cleared, or the payment finished in a different browser. */
      setView({
        kind: 'success',
        id: params.get('order') || (pending && pending.id) || null,
        orderNo: params.get('n') || (pending && pending.orderNo) || null,
        /* Email stays out of the URL: it is personal data, and URLs leak into
           history, referrers and server logs. localStorage only. */
        email: (pending && pending.email) || null,
        ref: (pending && pending.ref) || null,
      });
      clearDraft(DRAFT_KEY);
      try { localStorage.removeItem(PENDING_KEY); } catch (e) {}
      window.ASCXCart.complete();
    } else {
      setView({ kind: 'cancelled' });
      try { localStorage.removeItem(PENDING_KEY); } catch (e) {}
    }

    /* Drop the query so a refresh does not replay this. */
    window.history.replaceState({}, '', window.location.pathname + window.location.hash);
  }, []);

  if (!view) return null;
  const close = () => setView(null);

  if (view.kind === 'cancelled') {
    return (
      <Sheet onClose={close} labelledBy="ck-cancel-t" small>
        <div className="ck-done">
          <div className="ck-done-t" id="ck-cancel-t">Payment cancelled</div>
          <p className="ck-done-d">Nothing was charged. Your cart is exactly as you left it, so you can pick up where you stopped.</p>
          <Button variant="primary" fullWidth onClick={() => { close(); flows.openCheckout(); }}>Back to checkout</Button>
        </div>
      </Sheet>
    );
  }

  return (
    <Sheet onClose={close} labelledBy="ck-paid-t" small>
      <div className="ck-done">
        <div className="ck-done-mark">
          <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="2" strokeLinecap="square"><path d="M4 12l5 5L20 6" /></svg>
        </div>
        <div className="ck-done-t" id="ck-paid-t">Payment complete</div>

        {/* The number comes first — it is what they quote if anything goes
            wrong. The sequence value is the human one; the UUID only shows for
            an order placed before 0004 ran. */}
        {(view.orderNo || view.id) && (
          <div className="ck-orderno">
            <div className="ck-orderno-k">Your order number</div>
            <div className={view.orderNo ? 'ck-orderno-v' : 'ck-orderno-v ck-orderno-uuid'}>
              {view.orderNo ? '#' + view.orderNo : view.id}
            </div>
          </div>
        )}

        <p className="ck-done-d">
          Your place in {LV.LAUNCH.cohort} is locked. A confirmation email with your receipt
          is on its way{view.email ? <> to <b>{view.email}</b></> : ' to your inbox'}.
        </p>

        {/* The minute after paying is when someone is most likely to bring a
            friend, so the link is offered here rather than buried in an app
            that does not exist yet. Web Share on a phone opens the native
            sheet; clipboard is the desktop fallback. */}
        {view.ref && <ShareBlock code={view.ref} />}

        <div className="ck-done-foot">
          <Button variant="primary" fullWidth onClick={close}>Back to the page</Button>
        </div>
      </div>
    </Sheet>
  );
}

function Flows() {
  const fl = useFlows();
  return (
    <React.Fragment>
      <StripeReturn />
      {fl.view === 'checkout' && <Checkout onClose={() => fl.close()} />}
      {fl.view === 'free' && <FreeSignup onClose={() => fl.close()} />}
    </React.Fragment>
  );
}

window.LVX = { Flows, FreeBand };
})();
