// intro-pack-checkout.jsx — Stripe-style two-step checkout that works for
// every pricing option in the brand catalog (intro pack, multi-class pack,
// monthly membership, single drop-in). All copy + bullets are derived from
// the existing pricing-option shape in brands.jsx — no extra studio input.
// URL param `?offer=<id>` picks which option to render.

const { useState: useStateIP, useEffect: useEffectIP } = React;

const IP_BREAKPOINT = 900;
function useIPViewportIsMobile() {
  const [m, setM] = useStateIP(typeof window !== 'undefined' ? window.innerWidth < IP_BREAKPOINT : false);
  useEffectIP(() => {
    const onResize = () => setM(window.innerWidth < IP_BREAKPOINT);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return m;
}

// ────────────────────────────────────────────────────────────────
// Find a pricing option by id across packs / class pricing / appointment
// pricing. Falls back to the intro pack if nothing matches.
// ────────────────────────────────────────────────────────────────
function findPricingOption(brand, id) {
  const pools = [brand.packs, brand.class?.pricing, brand.appointment?.pricing].filter(Boolean);
  for (const pool of pools) {
    const hit = pool.find(o => o.id === id);
    if (hit) return hit;
  }
  return brand.packs?.[0];
}

// Pick the offer id from `?offer=` (or fall back).
function readOfferIdFromUrl() {
  if (typeof window === 'undefined') return null;
  const params = new URLSearchParams(window.location.search);
  return params.get('offer');
}

// ────────────────────────────────────────────────────────────────
// Derive everything we render from a single pricing option.
// ────────────────────────────────────────────────────────────────
function derivePackData(option) {
  const isRecurring = !!option.recurring;
  const isSingle = !isRecurring && option.sessions === 1 && !option.credits;
  const hasCredits = !!(option.credits && option.credits > 0);
  // "Intro-style" = unlimited access but time-bound (no credits, has expiry)
  const isIntroStyle = !isRecurring && !hasCredits && !isSingle;

  const bullets = [];
  if (isRecurring) {
    bullets.push('Unlimited classes');
    bullets.push('Billed monthly · cancel anytime');
    bullets.push('Starts today');
  } else if (hasCredits) {
    bullets.push(`${option.credits} classes`);
    if (option.expires) bullets.push(`${option.expires} to use`);
    if (option.perCredit) bullets.push(`$${option.perCredit} per class`);
  } else if (isSingle) {
    bullets.push('One class');
    if (option.expires) bullets.push(`${option.expires} to use`);
    else bullets.push('Use anytime');
  } else if (isIntroStyle) {
    bullets.push('Unlimited classes during pack');
    if (option.expires) bullets.push(`${option.expires} to use`);
    bullets.push('New students only');
  }

  let ctaPrefix;
  let ctaSuffix = '';
  if (isRecurring) {
    ctaPrefix = 'Start membership';
    ctaSuffix = '/mo';
  } else if (isSingle) {
    ctaPrefix = 'Book class';
  } else {
    ctaPrefix = `Buy ${option.label.toLowerCase()}`;
  }

  return {
    id: option.id,
    headline: option.label,
    description: option.sub,
    price: option.price,
    bullets,
    ctaPrefix,
    ctaSuffix,
    isRecurring,
    isSingle,
  };
}

function IntroPackCheckoutApp({ brandId = 'halcyon', pricingId }) {
  const baseBrand = window.BRANDS[brandId];
  const mobile = useIPViewportIsMobile();

  // ── Tweaks panel integration — opt-in via window.IP_TWEAKS_ENABLED ──
  const tweaksEnabled = typeof window !== 'undefined' && window.IP_TWEAKS_ENABLED;
  const defaults = (typeof window !== 'undefined' && window.IP_TWEAK_DEFAULTS) || {};
  const [t, setTweak] = (tweaksEnabled ? window.useTweaks(defaults) : [defaults, () => {}]);

  const brand = tweaksEnabled ? {
    ...baseBrand,
    accent:        t.accent     ?? baseBrand.accent,
    accentDeep:    t.accent     ?? baseBrand.accentDeep,
    accentSoft:    baseBrand.accentSoft,
    surface:       t.panel      ?? baseBrand.surface,
    border:        t.lineColor  ?? baseBrand.border,
    ink:           t.headline   ?? baseBrand.ink,
    inkSoft:       t.details    ?? baseBrand.inkSoft,
    displayFont:   t.font       ?? baseBrand.displayFont,
    bodyFont:      t.font       ?? baseBrand.bodyFont,
    displayWeight: t.weight     ?? baseBrand.displayWeight,
    _radius:       t.radius     ?? 12,
    _buttonTextColor: t.buttonTextColor,
  } : {
    ...baseBrand,
    _radius: 12,
  };
  const appBg = tweaksEnabled ? (t.background ?? '#FAFAF7') : '#FAFAF7';
  const hideTerms = !!t.hideTerms;
  const hideBillingAddress = !!t.hideBillingAddress;
  const hideLogo = !!t.hideLogo;

  const offerId = pricingId || readOfferIdFromUrl() || 'intro';
  const option = findPricingOption(brand, offerId);
  const pack = derivePackData(option);

  const [activeStep, setActiveStep] = useStateIP(1);
  const [completed, setCompleted] = useStateIP({});
  const [customer, setCustomer] = useStateIP({
    firstName: '', lastName: '', email: '', phone: '',
    street: '', apt: '', city: '', state: '', zip: '',
  });
  const [smsTransactional, setSmsTransactional] = useStateIP(true);
  const [smsMarketing, setSmsMarketing] = useStateIP(true);
  const [agreeTerms, setAgreeTerms] = useStateIP(true);
  const [savePay, setSavePay] = useStateIP(true);
  const [forSomeoneElse, setForSomeoneElse] = useStateIP(false);
  // Single recipient — type picks which sub-form to show (family OR guest).
  // Both forms collect the same fields now (name + birthday + optional email + phone).
  const [recipientType, setRecipientType] = useStateIP(''); // '' | 'family' | 'guest' | 'gift'
  const [recipient, setRecipient] = useStateIP({
    firstName: '', lastName: '', birthday: '',
    email: '', emailConfirm: '', phone: '', note: '',
  });
  const [referralSource, setReferralSource] = useStateIP('');
  const [promoCode, setPromoCode] = useStateIP('');
  const [discount, setDiscount] = useStateIP(0);
  const [summaryOpen, setSummaryOpen] = useStateIP(false);

  const subtotal = pack.price;
  const total = Math.max(0, subtotal - discount);

  // When the "for someone else" toggle is on, require a type to be picked
  // and the recipient's name filled. Per-type extras:
  //   family — also need a birthday
  //   guest  — also need a valid email
  //   gift   — also need a valid email
  const emailOk = /\S+@\S+\.\S+/.test(recipient.email);
  let typeOk = false;
  if (recipientType === 'family') typeOk = recipient.birthday.trim().length > 0;
  else if (recipientType === 'guest') typeOk = emailOk;
  else if (recipientType === 'gift')  typeOk = emailOk;
  const recipientValid = !forSomeoneElse
    || (recipientType !== ''
        && recipient.firstName.trim().length > 0
        && recipient.lastName.trim().length > 0
        && typeOk);

  const detailsValid =
       customer.firstName.trim().length > 0
    && customer.lastName.trim().length > 0
    && /\S+@\S+\.\S+/.test(customer.email)
    && recipientValid;

  const goNext = (step) => {
    setCompleted(s => ({ ...s, [step]: true }));
    setActiveStep(step + 1);
  };
  const editStep = (step) => {
    setActiveStep(step);
    setCompleted(s => { const n = { ...s }; delete n[step]; return n; });
  };

  const fullName = `${customer.firstName} ${customer.lastName}`.trim();
  const detailsSummary = fullName && customer.email
    ? `${fullName} · ${customer.email}`
    : null;

  const stepDetails = (
    <IPStepCard
      brand={brand} mobile={mobile}
      step={1} title="Your details"
      isOpen={activeStep === 1} isCompleted={completed[1]}
      summary={completed[1] ? detailsSummary : null}
      onEdit={() => editStep(1)}
    >
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
        <Field
          brand={brand} label="First name"
          value={customer.firstName}
          onChange={v => setCustomer(c => ({ ...c, firstName: v }))}
          autoComplete="given-name"
        />
        <Field
          brand={brand} label="Last name"
          value={customer.lastName}
          onChange={v => setCustomer(c => ({ ...c, lastName: v }))}
          autoComplete="family-name"
        />
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
        <Field
          brand={brand} label="Email"
          type="email"
          value={customer.email}
          onChange={v => setCustomer(c => ({ ...c, email: v }))}
          autoComplete="email"
        />
        <Field
          brand={brand} label="Phone"
          type="tel"
          value={customer.phone}
          onChange={v => setCustomer(c => ({ ...c, phone: v }))}
          autoComplete="tel"
        />
      </div>
      <ExistingAccountNote brand={brand} />
      <div style={{ height: 6 }}></div>
      <SmsConsentIP
        brand={brand}
        transactional={smsTransactional} setTransactional={setSmsTransactional}
        marketing={smsMarketing} setMarketing={setSmsMarketing}
      />
      <RecipientBlock
        brand={brand}
        enabled={forSomeoneElse} setEnabled={setForSomeoneElse}
        type={recipientType} setType={setRecipientType}
        recipient={recipient} setRecipient={setRecipient}
      />
      <ReferralSourceSelect
        brand={brand}
        value={referralSource}
        onChange={setReferralSource}
      />
      <IPStepNext brand={brand} disabled={!detailsValid} onClick={() => goNext(1)} />
    </IPStepCard>
  );

  const isFree = total === 0;
  const stepPayment = (
    <IPStepCard
      brand={brand} mobile={mobile}
      step={2} title={isFree ? 'Confirm purchase' : 'Payment'}
      isOpen={activeStep === 2} isCompleted={false}
      summary={null}
      onEdit={() => editStep(2)}
    >
      {isFree ? (
        <div style={{
          padding: '14px 16px',
          borderRadius: brand._radius ?? 12,
          background: brand.accentSoft,
          border: `1px solid ${brand.border}`,
          fontSize: 13.5, color: brand.ink, lineHeight: 1.5,
        }}>
          No payment required — your discount covers the full purchase. Click confirm to add this to your account.
        </div>
      ) : (
        <>
          <PaymentSection
            brand={brand}
            methods={{ apple: true, google: true, link: true, ach: false }}
          />
          <IPSavePayment brand={brand} value={savePay} onChange={setSavePay} />
          {!hideBillingAddress && (
            <AddressBlock
              brand={brand} customer={customer} setCustomer={setCustomer}
              heading="Billing address"
            />
          )}
        </>
      )}
      {!hideTerms && (
        <PurchaseTermsAccordion
          brand={brand} agreed={agreeTerms} setAgreed={setAgreeTerms}
        />
      )}
      {(() => {
         const termsOk = hideTerms || agreeTerms;
         const ok = detailsValid && termsOk;
         return (
           <button
             disabled={!ok}
             onClick={() => alert(`Purchased ${pack.headline} · ${isFree ? 'Free' : `$${total.toFixed(2)}`}`)}
             style={{
               width: '100%',
               marginTop: 18,
               background: ok ? brand.accent : '#D8D6CF',
               color: brand._buttonTextColor || '#fff',
               border: 0, borderRadius: brand._radius ?? 12,
               padding: '16px 18px',
               fontSize: 15, fontWeight: 700,
               cursor: ok ? 'pointer' : 'not-allowed',
               fontFamily: brand.displayFont,
               letterSpacing: '-0.005em',
             }}>
             {isFree ? 'Confirm purchase · Free' : `${pack.ctaPrefix} · $${total.toFixed(2)}${pack.ctaSuffix}`}
           </button>
         );
       })()}
    </IPStepCard>
  );

  return (
    <div style={{
      minHeight: mobile ? 0 : '100vh',
      background: appBg,
      fontFamily: brand.bodyFont,
      color: brand.ink,
    }}>
      {!hideLogo && <IPHeaderLogo brand={brand} mobile={mobile} />}

      {mobile ? (
        <div style={{ padding: '14px 16px 120px', display: 'flex', flexDirection: 'column', gap: 12 }}>
          {stepDetails}
          {stepPayment}
        </div>
      ) : (
        <div style={{
          display: 'grid',
          gridTemplateColumns: 'minmax(0, 1fr) 380px',
          gap: 0,
          maxWidth: 1280,
          margin: '0 auto',
          padding: '28px 28px 60px',
          alignItems: 'start',
        }}>
          <div style={{ paddingRight: 48, display: 'flex', flexDirection: 'column', gap: 14 }}>
            {stepDetails}
            {stepPayment}
          </div>

          <IPOrderAside
            brand={brand} pack={pack}
            subtotal={subtotal} discount={discount} total={total}
            promoCode={promoCode} setPromoCode={setPromoCode}
            onApplyPromo={() => promoCode && setDiscount(5)}
          />
        </div>
      )}

      {mobile && (
        <IPMobileBottomBar
          brand={brand} pack={pack}
          subtotal={subtotal} discount={discount} total={total}
          promoCode={promoCode} setPromoCode={setPromoCode}
          onApplyPromo={() => promoCode && setDiscount(5)}
          open={summaryOpen} setOpen={setSummaryOpen}
        />
      )}

      <PoweredByZipper />
      {tweaksEnabled && <IPTweaks t={t} setTweak={setTweak} />}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Brand bar — same chrome as the booking stepper (no avatar, no
// switcher, just the studio mark + name).
// ────────────────────────────────────────────────────────────────
function IPHeaderLogo({ brand, mobile }) {
  return (
    <div style={{
      padding: mobile ? '20px 16px 0' : '28px 28px 0',
      maxWidth: 1280, margin: '0 auto',
      display: 'flex', alignItems: 'center', justifyContent: 'flex-start', gap: 10,
    }}>
      <div style={{
        width: mobile ? 36 : 40, height: mobile ? 36 : 40,
        borderRadius: brand._radius ?? 10,
        background: brand.accent, color: '#fff',
        display: 'grid', placeItems: 'center',
        fontFamily: brand.displayFont, fontWeight: 800,
        fontSize: mobile ? 15 : 17, letterSpacing: '-0.01em',
      }}>{brand.initials}</div>
      <div style={{
        fontFamily: brand.displayFont, fontWeight: brand.displayWeight,
        fontSize: mobile ? 16 : 19, color: brand.ink,
        letterSpacing: '-0.01em',
      }}>{brand.name}</div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Pack details card (peer of ClassDetailsCard from the booking flow).
// Price tile replaces the class image.
// ────────────────────────────────────────────────────────────────
function IPPackCard({ brand, pack, mobile }) {
  return (
    <div style={{
      background: '#fff',
      border: `1px solid ${brand.border}`,
      borderRadius: brand._radius ?? 14,
      padding: mobile ? 14 : 18,
      display: 'flex',
      gap: mobile ? 12 : 16,
      alignItems: 'center',
    }}>
      <div style={{
        width: mobile ? 72 : 96, height: mobile ? 72 : 96, borderRadius: 12,
        flex: '0 0 auto',
        background: `linear-gradient(135deg, ${brand.accent}, ${brand.accentDeep})`,
        color: '#fff',
        display: 'grid', placeItems: 'center',
        fontFamily: brand.displayFont, fontWeight: 800,
        fontSize: mobile ? 24 : 30,
        letterSpacing: '-0.02em',
      }}>${pack.price}</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontSize: mobile ? 10 : 11, fontWeight: 700,
          letterSpacing: '0.1em', textTransform: 'uppercase',
          color: brand.inkSoft, marginBottom: mobile ? 4 : 6,
        }}>You're buying</div>
        <div style={{
          fontFamily: brand.displayFont, fontWeight: brand.displayWeight,
          fontSize: mobile ? 18 : 22, color: brand.ink,
          lineHeight: 1.2, letterSpacing: '-0.01em',
        }}>{pack.headline}</div>
        <div style={{
          fontSize: mobile ? 12 : 13, color: brand.inkSoft,
          marginTop: mobile ? 4 : 6, lineHeight: 1.5,
        }}>{pack.description} · {pack.expires}</div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Step wrapper — collapses to summary row when completed; reopens on
// click. Mirrors stepper-class.jsx's StepCard.
// ────────────────────────────────────────────────────────────────
function IPStepCard({ brand, mobile, step, title, isOpen, isCompleted, summary, onEdit, children }) {
  const stateColor = isOpen ? brand.accent : (isCompleted ? '#3A7A4D' : brand.border);
  return (
    <div style={{
      background: '#fff',
      border: `1px solid ${isOpen ? brand.accent : brand.border}`,
      boxShadow: isOpen ? `0 0 0 3px ${brand.accentSoft}` : 'none',
      borderRadius: brand._radius ?? 14,
      transition: 'all .2s ease',
      overflow: 'hidden',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 12,
        padding: mobile ? '14px 16px' : '18px 20px',
        cursor: !isOpen && isCompleted ? 'pointer' : 'default',
      }} onClick={() => { if (!isOpen && isCompleted) onEdit(); }}>
        <div style={{
          width: 26, height: 26, borderRadius: 999,
          background: isCompleted ? '#3A7A4D' : (isOpen ? brand.accent : 'transparent'),
          border: `1.5px solid ${stateColor}`,
          color: isCompleted || isOpen ? '#fff' : brand.inkSoft,
          fontSize: 13, fontWeight: 700,
          display: 'grid', placeItems: 'center',
          flex: '0 0 auto',
          fontFamily: brand.bodyFont,
        }}>
          {isCompleted ? (
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
          ) : step}
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontFamily: brand.displayFont, fontWeight: 700,
            fontSize: mobile ? 15 : 16,
            color: isCompleted && !isOpen ? brand.inkSoft : brand.ink,
            letterSpacing: '-0.005em',
          }}>{title}</div>
          {summary && !isOpen && (
            <div style={{ fontSize: mobile ? 12 : 13, color: brand.inkSoft, marginTop: 2,
              whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{summary}</div>
          )}
        </div>
        {isCompleted && !isOpen && (
          <button onClick={(e) => { e.stopPropagation(); onEdit(); }} style={{
            background: 'transparent', border: 0,
            color: brand.accent, fontSize: 12, fontWeight: 700,
            letterSpacing: '0.06em', textTransform: 'uppercase',
            cursor: 'pointer',
          }}>Edit</button>
        )}
      </div>
      {isOpen && (
        <div style={{ padding: mobile ? '0 16px 16px' : '0 20px 20px' }}>
          {children}
        </div>
      )}
    </div>
  );
}

function IPStepNext({ brand, onClick, disabled }) {
  return (
    <div style={{ marginTop: 16, display: 'flex', justifyContent: 'flex-end' }}>
      <button onClick={onClick} disabled={disabled} style={{
        background: disabled ? '#D8D6CF' : brand.accent, color: '#fff',
        border: 0, borderRadius: 10,
        padding: '12px 28px',
        fontSize: 14, fontWeight: 700,
        cursor: disabled ? 'not-allowed' : 'pointer',
        fontFamily: brand.displayFont,
        letterSpacing: '-0.005em',
      }}>Next</button>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Existing-account hint shown directly under the email field.
// ────────────────────────────────────────────────────────────────
function ExistingAccountNote({ brand }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'flex-start', gap: 8,
      marginTop: -4, marginBottom: 6,
      padding: '8px 10px',
      background: brand.accentSoft,
      borderRadius: 8,
      fontSize: 11.5, color: brand.ink, lineHeight: 1.45,
    }}>
      <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={brand.accentDeep} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" style={{ flex: '0 0 auto', marginTop: 1 }}>
        <circle cx="12" cy="12" r="10"/>
        <line x1="12" y1="16" x2="12" y2="12"/>
        <line x1="12" y1="8" x2="12.01" y2="8"/>
      </svg>
      <span>If this email matches an existing client account, this purchase will be added to that account automatically.</span>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// SMS consent — same compact preselected pair as the booking stepper.
// ────────────────────────────────────────────────────────────────
function SmsConsentIP({ brand, transactional, setTransactional, marketing, setMarketing }) {
  const row = (checked, onChange, label) => (
    <label style={{
      display: 'flex', alignItems: 'flex-start', gap: 8,
      cursor: 'pointer', padding: '4px 0',
    }}>
      <span
        onClick={e => { e.preventDefault(); onChange(!checked); }}
        style={{
          flex: '0 0 14px',
          width: 14, height: 14,
          marginTop: 2,
          borderRadius: 4,
          border: `1.5px solid ${checked ? brand.accent : brand.border}`,
          background: checked ? brand.accent : '#fff',
          display: 'grid', placeItems: 'center',
          transition: 'all .15s ease',
        }}>
        {checked && (
          <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
        )}
      </span>
      <span style={{ fontSize: 11.5, color: brand.inkSoft, lineHeight: 1.45 }}>{label}</span>
      <input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} style={{ position: 'absolute', opacity: 0, pointerEvents: 'none' }} />
    </label>
  );
  return (
    <div style={{ marginTop: 8, paddingLeft: 2 }}>
      {row(transactional, setTransactional, 'I agree to receive transactional SMS messages (class/appt reminders + confirmations).')}
      {row(marketing, setMarketing, 'I agree to receive other SMS communication from this business (news, promotions) and can opt-out at any time.')}
    </div>
  );
}

// Standalone T&C consent — same checkbox row, but exported so the payment
// step can render it under the card fields.
function TermsConsentRow({ brand, checked, onChange }) {
  return (
    <label style={{
      display: 'flex', alignItems: 'flex-start', gap: 8,
      cursor: 'pointer', padding: '4px 0',
    }}>
      <span
        onClick={e => { e.preventDefault(); onChange(!checked); }}
        style={{
          flex: '0 0 14px',
          width: 14, height: 14, marginTop: 2,
          borderRadius: 4,
          border: `1.5px solid ${checked ? brand.accent : brand.border}`,
          background: checked ? brand.accent : '#fff',
          display: 'grid', placeItems: 'center',
          transition: 'all .15s ease',
        }}>
        {checked && (
          <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
        )}
      </span>
      <span style={{ fontSize: 12, color: brand.inkSoft, lineHeight: 1.45 }}>
        I agree to the{' '}
        <a href="https://website.com/policies" target="_blank" rel="noopener noreferrer"
           onClick={(e) => e.stopPropagation()}
           style={{ color: brand.accent, textDecoration: 'underline' }}>
          terms &amp; conditions
        </a>
        {' '}of this purchase.
      </span>
      <input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)}
             style={{ position: 'absolute', opacity: 0, pointerEvents: 'none' }} />
    </label>
  );
}

// ────────────────────────────────────────────────────────────────
// IPSavePayment — "Save my card for next time" checkbox below the
// PaymentSection. Mirrors the SavePayment row used in the Booking flow.
// ────────────────────────────────────────────────────────────────
function IPSavePayment({ brand, value, onChange }) {
  return (
    <label style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '10px 0 0', cursor: 'pointer', userSelect: 'none',
    }}>
      <span style={{
        width: 18, height: 18, borderRadius: 5,
        border: `1.5px solid ${value ? brand.accent : brand.border}`,
        background: value ? brand.accent : '#fff',
        display: 'grid', placeItems: 'center', flex: '0 0 auto',
        transition: 'all .15s ease',
      }}>
        {value && (
          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="20 6 9 17 4 12"/>
          </svg>
        )}
      </span>
      <input type="checkbox" checked={value} onChange={e => onChange(e.target.checked)}
             style={{ display: 'none' }} />
      <span style={{ fontSize: 12.5, color: brand.inkSoft, lineHeight: 1.4 }}>
        Save my card for next time — skip the form on your next purchase
      </span>
    </label>
  );
}

// ────────────────────────────────────────────────────────────────
// Purchase terms — collapsible card under the card form.
// Tap the header to expand the full terms inline (NOT a liability waiver —
// these are the purchase-side terms: refunds, sign-up, etc).
// ────────────────────────────────────────────────────────────────
function PurchaseTermsAccordion({ brand, agreed, setAgreed }) {
  const [open, setOpen] = useStateIP(false);
  return (
    <div style={{
      marginTop: 14,
      border: `1.5px solid ${agreed ? brand.accent : brand.border}`,
      background: agreed ? brand.accentSoft : '#fff',
      borderRadius: brand._radius ?? 12,
      transition: 'border-color .15s ease, background .15s ease',
      overflow: 'hidden',
    }}>
      <button
        type="button"
        onClick={() => setOpen(o => !o)}
        style={{
          width: '100%', background: 'transparent', border: 0,
          padding: '14px 16px',
          display: 'flex', alignItems: 'center', gap: 10,
          cursor: 'pointer', textAlign: 'left',
          fontFamily: brand.bodyFont, color: brand.ink,
        }}
        aria-expanded={open}>
        <span aria-hidden="true" style={{
          width: 18, height: 18, borderRadius: 999,
          border: `1.5px solid ${agreed ? brand.accent : brand.border}`,
          background: agreed ? brand.accent : 'transparent',
          display: 'grid', placeItems: 'center', flexShrink: 0,
        }}>
          {agreed && (
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff"
                 strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round">
              <polyline points="20 6 9 17 4 12" />
            </svg>
          )}
        </span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 14, fontWeight: 600, color: brand.ink, letterSpacing: '-0.005em' }}>
            Purchase terms and conditions
          </div>
          <div style={{ fontSize: 12, color: brand.inkSoft, marginTop: 2 }}>
            {agreed ? 'Agreed — tap to review.' : 'Required — tap to review and agree.'}
          </div>
        </div>
        <svg width="14" height="14" viewBox="0 0 24 24"
             fill="none" stroke={brand.inkSoft} strokeWidth="2.4"
             strokeLinecap="round" strokeLinejoin="round"
             style={{ transform: open ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform .15s ease' }}>
          <polyline points="6 9 12 15 18 9" />
        </svg>
      </button>
      {open && (
        <div style={{
          padding: '0 16px 16px',
          fontFamily: brand.bodyFont, fontSize: 13, color: brand.inkSoft, lineHeight: 1.55,
        }}>
          <div style={{
            background: '#fff',
            border: `1px solid ${brand.border}`,
            borderRadius: 10,
            padding: '12px 14px',
            maxHeight: 200, overflowY: 'auto',
            color: brand.ink,
          }}>
            <p style={{ margin: '0 0 8px' }}>
              <strong>All sales are final.</strong> Intro packs, class packs, memberships,
              gift cards and add-on purchases are non-refundable. Unused credits
              expire per the terms listed on the product page.
            </p>
            <p style={{ margin: '0 0 8px' }}>
              <strong>Booking.</strong> All classes and appointments must be booked
              online through the studio's scheduling tools — walk-ins are not
              guaranteed a spot.
            </p>
            <p style={{ margin: '0 0 8px' }}>
              <strong>Cancellations.</strong> Cancel or reschedule at least 12 hours
              before your booking to avoid losing the credit. Late cancellations
              and no-shows forfeit the credit.
            </p>
            <p style={{ margin: '0 0 8px' }}>
              <strong>Memberships.</strong> Recurring memberships renew automatically
              until cancelled. You can cancel at any time from your account; the
              cancellation takes effect at the end of the current billing cycle.
            </p>
            <p style={{ margin: 0 }}>
              <strong>Gifts.</strong> When purchasing for someone else, the recipient
              receives access to the purchased item; the buyer retains the billing
              record. Gift items are non-transferrable once redeemed.
            </p>
          </div>
          <label style={{
            marginTop: 12,
            display: 'flex', alignItems: 'flex-start', gap: 8,
            cursor: 'pointer',
          }}>
            <span
              onClick={(e) => { e.preventDefault(); setAgreed(!agreed); }}
              style={{
                flex: '0 0 14px',
                width: 14, height: 14, marginTop: 2,
                borderRadius: 4,
                border: `1.5px solid ${agreed ? brand.accent : brand.border}`,
                background: agreed ? brand.accent : '#fff',
                display: 'grid', placeItems: 'center',
                transition: 'all .15s ease',
              }}>
              {agreed && (
                <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="#fff"
                     strokeWidth="4" strokeLinecap="round" strokeLinejoin="round">
                  <polyline points="20 6 9 17 4 12"/>
                </svg>
              )}
            </span>
            <span style={{ fontSize: 12, color: brand.inkSoft, lineHeight: 1.45 }}>
              I agree to the purchase terms and conditions above.
            </span>
            <input type="checkbox" checked={agreed} onChange={e => setAgreed(e.target.checked)}
                   style={{ position: 'absolute', opacity: 0, pointerEvents: 'none' }} />
          </label>
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Address — street / apt / city / state / zip. Apt is optional, the rest
// follow standard US billing fields so this can double as a billing address
// for any payment provider on the next step.
// ────────────────────────────────────────────────────────────────
// ────────────────────────────────────────────────────────────────
// Recipient block — "I'm making this purchase for someone else" toggle.
// When on, the user picks ONE of two mutually-exclusive checkboxes
// (Family member / Guest); the matching fields appear below. A purchase
// can only be made for one person via this checkout — picking one type
// clears the other.
// ────────────────────────────────────────────────────────────────
function RecipientBlock({ brand, enabled, setEnabled, type, setType, recipient, setRecipient }) {
  const pickType = (next) => {
    setType(prev => prev === next ? '' : next);
    if (next !== type) setRecipient({
      firstName: '', lastName: '', birthday: '',
      email: '', emailConfirm: '', phone: '', note: '',
    });
  };
  const update = (k) => (v) => setRecipient(r => ({ ...r, [k]: v }));

  return (
    <div style={{
      marginTop: 18,
      // Visually distinct from the SMS consent checkboxes:
      // dashed accent border, faint accent wash, gift-tag icon on the header.
      border: `1.5px dashed ${enabled ? brand.accent : brand.border}`,
      background: enabled ? brand.accentSoft : '#fff',
      borderRadius: brand._radius ?? 14,
      padding: 16,
      transition: 'border-color .15s ease, background .15s ease',
    }}>
      <button
        type="button"
        onClick={() => setEnabled(!enabled)}
        aria-expanded={enabled}
        style={{
          width: '100%', background: 'transparent', border: 0,
          padding: 0, cursor: 'pointer', textAlign: 'left',
          display: 'flex', alignItems: 'center', gap: 12,
          fontFamily: brand.bodyFont,
        }}>
        <span aria-hidden="true" style={{
          width: 36, height: 36, borderRadius: '50%',
          background: enabled ? brand.accent : brand.surface,
          color: enabled ? '#fff' : brand.accent,
          display: 'grid', placeItems: 'center',
          flex: '0 0 36px',
          transition: 'background .15s ease, color .15s ease',
          border: enabled ? 'none' : `1.5px solid ${brand.border}`,
        }}>
          <svg width="20" height="20" viewBox="0 0 24 24" fill="none"
               stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            {/* Person silhouette on the right */}
            <circle cx="17" cy="8" r="3.2"/>
            <path d="M11.5 20 v-1 a4.5 4.5 0 0 1 4.5-4.5h1 a4.5 4.5 0 0 1 4.5 4.5 v1"/>
            {/* Arrow — same geometry as the SignInCallout log-in arrow */}
            <line x1="2" y1="12" x2="11" y2="12"/>
            <polyline points="6 7 11 12 6 17"/>
          </svg>
        </span>
        <span style={{ flex: 1, minWidth: 0 }}>
          <span style={{
            display: 'block',
            fontFamily: brand.displayFont, fontWeight: 700,
            fontSize: 15, color: brand.ink, letterSpacing: '-0.005em',
          }}>
            I'm making this purchase for someone else
          </span>
          <span style={{
            display: 'block',
            fontSize: 12.5, color: brand.inkSoft, marginTop: 2,
          }}>
            {enabled ? 'Pick who this purchase is for.' : 'Buying a gift, signing up a family member or a friend? Tap to add their details.'}
          </span>
        </span>
        {/* Visual switch on the right */}
        <span aria-hidden="true" style={{
          width: 38, height: 22, borderRadius: 999,
          background: enabled ? brand.accent : brand.border,
          position: 'relative',
          transition: 'background .15s ease',
          flex: '0 0 38px',
        }}>
          <span style={{
            position: 'absolute', top: 2,
            left: enabled ? 18 : 2,
            width: 18, height: 18, borderRadius: '50%',
            background: '#fff',
            boxShadow: '0 1px 2px rgba(0,0,0,0.12)',
            transition: 'left .15s ease',
          }} />
        </span>
        <input type="checkbox" checked={enabled} onChange={() => setEnabled(!enabled)}
               style={{ position: 'absolute', opacity: 0, pointerEvents: 'none' }} />
      </button>

      {enabled && (
        <div style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 14 }}>
          {/* Mutually-exclusive type checkboxes */}
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 22, paddingLeft: 2 }}>
            <RecipientTypeCheckbox brand={brand}
              checked={type === 'family'} onChange={() => pickType('family')}
              label="Family member" />
            <RecipientTypeCheckbox brand={brand}
              checked={type === 'guest'} onChange={() => pickType('guest')}
              label="Guest" />
            <RecipientTypeCheckbox brand={brand}
              checked={type === 'gift'} onChange={() => pickType('gift')}
              label="Gift" />
          </div>

          {type === 'family' && (
            <RecipientSection
              brand={brand}
              groupTitle="FAMILY MEMBER"
              helper="We'll save them to your account so you can book for them with one tap next time."
            >
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                <Field brand={brand} label="First name"
                       value={recipient.firstName} onChange={update('firstName')} autoComplete="given-name" />
                <Field brand={brand} label="Last name"
                       value={recipient.lastName} onChange={update('lastName')} autoComplete="family-name" />
              </div>
              <Field brand={brand} label="Date of birth" type="date"
                     value={recipient.birthday} onChange={update('birthday')} autoComplete="bday" />
              <Field brand={brand} label="Family member's email (optional)" type="email"
                     value={recipient.email} onChange={update('email')} autoComplete="email" />
              <Field brand={brand} label="Family member's phone (optional)" type="tel"
                     value={recipient.phone} onChange={update('phone')} autoComplete="tel" />
            </RecipientSection>
          )}

          {type === 'guest' && (
            <RecipientSection
              brand={brand}
              groupTitle="GUEST"
              helper="Your guest will have their own account for future bookings, but will be linked to yours as a guest as well."
            >
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                <Field brand={brand} label="First name"
                       value={recipient.firstName} onChange={update('firstName')} autoComplete="given-name" />
                <Field brand={brand} label="Last name"
                       value={recipient.lastName} onChange={update('lastName')} autoComplete="family-name" />
              </div>
              <Field brand={brand} label="Guest's email" type="email"
                     value={recipient.email} onChange={update('email')} autoComplete="email" />
              <Field brand={brand} label="Guest's phone (optional)" type="tel"
                     value={recipient.phone} onChange={update('phone')} autoComplete="tel" />
            </RecipientSection>
          )}

          {type === 'gift' && (
            <RecipientSection
              brand={brand}
              groupTitle="GIFT"
              helper="Your gift recipient will have their own account for future bookings loaded with this item. They will receive an email with instructions to claim their account."
            >
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                <Field brand={brand} label="First name"
                       value={recipient.firstName} onChange={update('firstName')} autoComplete="given-name" />
                <Field brand={brand} label="Last name"
                       value={recipient.lastName} onChange={update('lastName')} autoComplete="family-name" />
              </div>
              <Field brand={brand} label="Recipient's email" type="email"
                     value={recipient.email} onChange={update('email')} autoComplete="email" />
              <Field brand={brand} label="Recipient's phone (optional)" type="tel"
                     value={recipient.phone} onChange={update('phone')} autoComplete="tel" />
              <Field brand={brand} label="Note for the gift card (optional)" rows={3}
                     value={recipient.note} onChange={update('note')} />
            </RecipientSection>
          )}
        </div>
      )}
    </div>
  );
}

function RecipientTypeCheckbox({ brand, checked, onChange, label }) {
  return (
    <label style={{
      display: 'inline-flex', alignItems: 'center', gap: 8,
      cursor: 'pointer', padding: '4px 0',
    }}>
      <span
        onClick={(e) => { e.preventDefault(); onChange(); }}
        style={{
          width: 16, height: 16, borderRadius: 4,
          border: `1.5px solid ${checked ? brand.accent : brand.border}`,
          background: checked ? brand.accent : '#fff',
          display: 'grid', placeItems: 'center',
          transition: 'all .15s ease', flex: '0 0 16px',
        }}>
        {checked && (
          <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="20 6 9 17 4 12"/>
          </svg>
        )}
      </span>
      <span style={{ fontSize: 13.5, color: brand.ink }}>{label}</span>
      <input type="checkbox" checked={checked} onChange={onChange}
             style={{ position: 'absolute', opacity: 0, pointerEvents: 'none' }} />
    </label>
  );
}

function RecipientSection({ brand, groupTitle, helper, children }) {
  return (
    <div style={{
      padding: 16,
      border: `1px solid ${brand.border}`,
      background: brand.surface,
      borderRadius: brand._radius ?? 12,
    }}>
      <div style={{
        fontFamily: brand.bodyFont, fontSize: 11, fontWeight: 700,
        letterSpacing: '0.1em', textTransform: 'uppercase',
        color: brand.ink, marginBottom: 12,
      }}>{groupTitle}</div>
      {children}
      <div style={{
        fontSize: 12, color: brand.inkSoft,
        marginTop: 6, lineHeight: 1.5,
      }}>{helper}</div>
    </div>
  );
}

function AddressBlock({ brand, customer, setCustomer, heading = 'Address' }) {
  const update = (k) => (v) => setCustomer(c => ({ ...c, [k]: v }));
  return (
    <div style={{ marginTop: 16 }}>
      <div style={{
        fontFamily: brand.bodyFont, fontSize: 10.5, fontWeight: 700,
        letterSpacing: '0.08em', textTransform: 'uppercase',
        color: brand.inkSoft, marginBottom: 8, paddingLeft: 2,
      }}>{heading}</div>
      <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 10 }}>
        <Field brand={brand} label="Street address"
               value={customer.street} onChange={update('street')} autoComplete="address-line1" />
        <Field brand={brand} label="Apt / unit" optional
               value={customer.apt} onChange={update('apt')} autoComplete="address-line2" />
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr 1fr', gap: 10 }}>
        <Field brand={brand} label="City"
               value={customer.city} onChange={update('city')} autoComplete="address-level2" />
        <Field brand={brand} label="State"
               value={customer.state} onChange={update('state')} autoComplete="address-level1" />
        <Field brand={brand} label="ZIP"
               value={customer.zip} onChange={update('zip')} autoComplete="postal-code" />
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// "How did you hear about us?" — a single attribution question shown
// under the SMS consent. Kept as a native <select> with a brand-matched
// chrome so we don't pull in a popover dependency for a one-off form field.
// ────────────────────────────────────────────────────────────────
function ReferralSourceSelect({ brand, value, onChange }) {
  const options = [
    'Instagram',
    'TikTok',
    'Google search',
    'Friend or family',
    'Walked by the studio',
    'Email or newsletter',
    'Other',
  ];
  return (
    <label style={{
      display: 'block', position: 'relative', marginTop: 16, marginBottom: 4,
    }}>
      <div style={{
        position: 'absolute', left: 14, top: 8,
        fontSize: 10.5, fontWeight: 600,
        letterSpacing: '0.04em', textTransform: 'uppercase',
        color: brand.inkSoft, pointerEvents: 'none',
      }}>How did you hear about us?</div>
      <select
        value={value}
        onChange={e => onChange(e.target.value)}
        style={{
          width: '100%',
          appearance: 'none', WebkitAppearance: 'none',
          background: brand.surface,
          border: `1.5px solid ${brand.border}`,
          borderRadius: 12,
          padding: '22px 38px 8px 14px',
          fontSize: 15, color: value ? brand.ink : brand.inkSoft,
          fontFamily: brand.bodyFont,
          outline: 'none', cursor: 'pointer',
        }}>
        <option value="">Select an option</option>
        {options.map(o => <option key={o} value={o}>{o}</option>)}
      </select>
      <svg
        width="14" height="14" viewBox="0 0 24 24"
        fill="none" stroke={brand.inkSoft} strokeWidth="2.2"
        strokeLinecap="round" strokeLinejoin="round"
        style={{ position: 'absolute', right: 14, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none' }}>
        <polyline points="6 9 12 15 18 9" />
      </svg>
    </label>
  );
}

// ────────────────────────────────────────────────────────────────
// Sticky right-side aside — pack line item, promo input, totals.
// ────────────────────────────────────────────────────────────────
function IPOrderAside({ brand, pack, subtotal, discount, total, promoCode, setPromoCode, onApplyPromo }) {
  return (
    <aside style={{
      position: 'sticky', top: 28,
      background: '#fff',
      border: `1px solid ${brand.border}`,
      borderRadius: brand._radius ?? 14,
      padding: 24,
      boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
    }}>
      <div style={{
        fontFamily: brand.displayFont, fontWeight: 700,
        fontSize: 18, color: brand.ink, marginBottom: 18,
        letterSpacing: '-0.01em',
      }}>Order Summary</div>

      <IPSummaryLineItem brand={brand} pack={pack} />

      <IPPromoInput brand={brand} value={promoCode} onChange={setPromoCode} onApply={onApplyPromo} />

      <IPSummaryTotals brand={brand} subtotal={subtotal} discount={discount} total={total} />

      <div style={{
        marginTop: 18, paddingTop: 16,
        borderTop: `1px solid ${brand.border}`,
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
        fontSize: 11, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase',
        color: brand.inkSoft,
      }}>
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9 12 11 14 15 10"/></svg>
        Secure SSL Checkout
      </div>
    </aside>
  );
}

// ────────────────────────────────────────────────────────────────
// Mobile bottom bar — collapsible summary drawer + always-visible total.
// ────────────────────────────────────────────────────────────────
function IPMobileBottomBar({ brand, pack, subtotal, discount, total, promoCode, setPromoCode, onApplyPromo, open, setOpen }) {
  return (
    <div style={{
      position: 'fixed', bottom: 0, left: 0, right: 0,
      background: '#fff',
      borderTop: `1px solid ${brand.border}`,
      boxShadow: '0 -4px 24px rgba(0,0,0,0.06)',
      zIndex: 30,
    }}>
      {open && (
        <div style={{
          padding: '16px 16px 12px',
          borderBottom: `1px solid ${brand.border}`,
          maxHeight: '50vh', overflow: 'auto',
        }}>
          <IPSummaryLineItem brand={brand} pack={pack} compact />
          <div style={{ height: 12 }}></div>
          <IPPromoInput brand={brand} value={promoCode} onChange={setPromoCode} onApply={onApplyPromo} />
          <div style={{ height: 12 }}></div>
          <IPSummaryTotals brand={brand} subtotal={subtotal} discount={discount} total={total} />
        </div>
      )}
      <button
        onClick={() => setOpen(!open)}
        style={{
          width: '100%', background: 'transparent', border: 0,
          padding: '12px 16px 10px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          cursor: 'pointer',
        }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={brand.inkSoft} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" style={{ transform: open ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform .2s ease' }}>
            <polyline points="18 15 12 9 6 15"/>
          </svg>
          <span style={{ fontSize: 12, fontWeight: 700, color: brand.inkSoft, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
            {open ? 'Hide summary' : 'Order summary'}
          </span>
        </div>
        <div style={{ fontFamily: brand.displayFont, fontWeight: 700, fontSize: 15, color: brand.ink }}>
          ${total.toFixed(2)}
        </div>
      </button>
    </div>
  );
}

function IPSummaryLineItem({ brand, pack, compact }) {
  return (
    <div style={{ marginBottom: compact ? 0 : 18 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
        <img
          src="https://images.unsplash.com/photo-1518611012118-696072aa579a?auto=format&fit=crop&w=200&h=200&q=80"
          alt="Pilates studio"
          style={{
            width: 56, height: 56, borderRadius: 10,
            flex: '0 0 auto',
            objectFit: 'cover',
            display: 'block',
          }}
        />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 600, color: brand.ink, lineHeight: 1.35 }}>{pack.headline}</div>
          {pack.description && (
            <div style={{ fontSize: 12, color: brand.inkSoft, marginTop: 2, lineHeight: 1.4 }}>
              {pack.description}
            </div>
          )}
        </div>
        <div style={{ fontFamily: brand.displayFont, fontSize: 14, fontWeight: 700, color: brand.ink, textAlign: 'right' }}>
          ${pack.price}{pack.isRecurring ? '/mo' : ''}
        </div>
      </div>
      {pack.bullets && pack.bullets.length > 0 && (
        <ul style={{
          listStyle: 'none', padding: 0, margin: '12px 0 0',
          display: 'flex', flexDirection: 'column', gap: 6,
        }}>
          {pack.bullets.map((b, i) => (
            <li key={i} style={{
              display: 'flex', alignItems: 'flex-start', gap: 8,
              fontSize: 12.5, color: brand.ink, lineHeight: 1.4,
            }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={brand.accent} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" style={{ flex: '0 0 auto', marginTop: 2 }}>
                <polyline points="20 6 9 17 4 12"/>
              </svg>
              <span>{b}</span>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

function IPPromoInput({ brand, value, onChange, onApply }) {
  return (
    <div>
      <div style={{ fontSize: 11, fontWeight: 700, color: brand.inkSoft, letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 8 }}>Gift or discount code</div>
      <div style={{ display: 'flex', gap: 8 }}>
        <input
          value={value}
          onChange={e => onChange(e.target.value)}
          placeholder="Code"
          style={{
            flex: 1,
            padding: '11px 12px',
            borderRadius: 8,
            border: `1px solid ${brand.border}`,
            background: '#F4F2EC',
            fontSize: 13,
            color: brand.ink,
            outline: 'none',
          }}
        />
        <button
          onClick={onApply}
          style={{
            padding: '0 16px',
            borderRadius: 8,
            border: `1px solid ${brand.border}`,
            background: '#fff',
            fontSize: 12, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase',
            color: brand.ink,
            cursor: 'pointer',
          }}>Apply</button>
      </div>
    </div>
  );
}

function IPSummaryTotals({ brand, subtotal, discount, total }) {
  return (
    <div style={{ paddingTop: 14, fontSize: 13.5 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0' }}>
        <span>Subtotal</span><span>${subtotal.toFixed(2)}</span>
      </div>
      {discount > 0 && (
        <div style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', color: brand.accent }}>
          <span>Discount</span><span>−${discount.toFixed(2)}</span>
        </div>
      )}
      <div style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0' }}>
        <span>Tax</span><span>$0.00</span>
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', padding: '12px 0 0', borderTop: `1px solid ${brand.border}`, marginTop: 8, fontWeight: 700, fontFamily: brand.displayFont, fontSize: 15 }}>
        <span>Total</span><span>${total.toFixed(2)}</span>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Studio settings panel — opt-in via window.IP_TWEAKS_ENABLED.
// Mirrors the appointment/schedule widget panels so studios can preview
// brand changes against the checkout flow.
// ────────────────────────────────────────────────────────────────
function IPTweaks({ t, setTweak }) {
  const {
    TweaksPanel: Panel, TweakSection: Section, TweakColor: Color,
    TweakSlider: Slider, TweakSelect: Select, TweakToggle: Toggle,
    TweakButton: Button,
  } = window;
  const resetAll = () => {
    const defaults = (typeof window !== 'undefined' && window.IP_TWEAK_DEFAULTS) || {};
    Object.keys(defaults).forEach(k => setTweak(k, defaults[k]));
  };
  useEffectIP(() => {
    if (!Panel) return;
    const id = window.setTimeout(() => window.postMessage({ type: '__activate_edit_mode' }, '*'), 0);
    return () => window.clearTimeout(id);
  }, [Panel]);
  if (!Panel) return null;
  const FONTS = [
    "'Poppins', system-ui, sans-serif",
    "'Inter', system-ui, sans-serif",
    "'Sora', system-ui, sans-serif",
    "'Open Sans', system-ui, sans-serif",
    "'DM Sans', system-ui, sans-serif",
    "'Manrope', system-ui, sans-serif",
    "'Bebas Neue', sans-serif",
    "'Playfair Display', serif",
  ];
  const fontLabel = (f) => f.replace(/['"]/g, '').split(',')[0];
  return (
    <Panel title="Studio settings" noDeckControls={true}>
      <Section label="Surfaces" />
      <Color label="Accent"      value={t.accent}     onChange={(v) => setTweak('accent', v)} />
      <Color label="Background"  value={t.background} onChange={(v) => setTweak('background', v)} />
      <Color label="Panel"       value={t.panel}      onChange={(v) => setTweak('panel', v)} />
      <Color label="Line color"  value={t.lineColor || '#ECECEC'}
                                 onChange={(v) => setTweak('lineColor', v)} />

      <Section label="Text on background" />
      <Color label="Primary"     value={t.headline}   onChange={(v) => setTweak('headline', v)} />
      <Color label="Secondary"   value={t.details}    onChange={(v) => setTweak('details', v)} />

      <Section label="Text on accent" />
      <Color label="Button text" value={t.buttonTextColor || '#FFFFFF'}
                                 onChange={(v) => setTweak('buttonTextColor', v)} />

      <Section label="Typography" />
      <Select label="Font family" value={t.font}
              options={FONTS.map(f => ({ value: f, label: fontLabel(f) }))}
              onChange={(v) => setTweak('font', v)} />
      <Slider label="Headline weight" value={t.weight} min={300} max={800} step={100}
              onChange={(v) => setTweak('weight', v)} />

      <Section label="Shape" />
      <Slider label="Corner radius" value={t.radius} min={0} max={28} step={1} unit="px"
              onChange={(v) => setTweak('radius', v)} />

      <Section label="Hide" />
      <Toggle label="Logo"                          value={!!t.hideLogo}
              onChange={(v) => setTweak('hideLogo', v)} />
      <Toggle label="Purchase terms and conditions" value={!!t.hideTerms}
              onChange={(v) => setTweak('hideTerms', v)} />
      <Toggle label="Billing address"               value={!!t.hideBillingAddress}
              onChange={(v) => setTweak('hideBillingAddress', v)} />

      <Section label="" />
      <Button label="Restore default settings" secondary onClick={resetAll} />
    </Panel>
  );
}

function PoweredByZipper() {
  return (
    <div style={{
      padding: '36px 0 8px',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      gap: 10,
      fontSize: 10.5, fontWeight: 600, letterSpacing: '0.1em',
      textTransform: 'uppercase', color: '#9CA3AF',
    }}>
      <span>Powered by</span>
      <img src="zipper-logo-grey.svg" alt="Zipper" height="18"
           style={{ display: 'block', height: 18, width: 'auto' }} />
    </div>
  );
}

window.IntroPackCheckoutApp = IntroPackCheckoutApp;
const _ipRoot = document.getElementById('root');
if (_ipRoot && !_ipRoot.dataset.framed) {
  ReactDOM.createRoot(_ipRoot).render(<IntroPackCheckoutApp />);
}
