// stepper-multi.jsx — "Multi Booking" variant of the Class booking flow.
// Forked from stepper-class.jsx. The "I'm booking this for someone else"
// section becomes a multi-spot booker: the buyer can reserve additional
// spots in the class and enters a name (required) + email (optional) for
// each added person. Spot count drives attendee count, totals, and the
// order-summary breakdown.
//
// Mounts <StepperMultiApp /> to #root (unless data-framed is set).
// Reuses brand data from brands.jsx and shared components from
// components.jsx / scenarios.jsx / flows.jsx.

const { useState: useStateSC, useEffect: useEffectSC } = React;

const MOBILE_BREAKPOINT = 900;

// Spot booking — a 3-row × 6-column grid (18 spots). A few are pre-taken so
// the map reads like a real studio.
const SC_SEAT_ROWS = ['A', 'B', 'C'];
const SC_SEAT_COLS = 6;
const SC_TAKEN_SEATS = new Set(['A3', 'B1', 'C5', 'C6']);
function useViewportIsMobile() {
  const [isMobile, setIsMobile] = useStateSC(
    typeof window !== 'undefined' ? window.innerWidth < MOBILE_BREAKPOINT : false,
  );
  useEffectSC(() => {
    const onResize = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return isMobile;
}

// ────────────────────────────────────────────────────────────────
// Returning-client account profile (used when loggedIn = true).
// One per brand. Match family/membership feel to the brand.
// ────────────────────────────────────────────────────────────────
const RETURNING_PROFILES = {
  halcyon: {
    customer: { name: 'Jordan Reyes', email: 'jordan.reyes@gmail.com', phone: '(347) 555-0181' },
    family: [
      { id: 'kid-1', name: 'Ellie Reyes',  relation: 'Daughter (12)' },
      { id: 'kid-2', name: 'Marcus Reyes', relation: 'Son (10)'      },
    ],
    membership: { name: 'Unlimited Reformer', renews: 'Sep 1', credits: 'Unlimited classes' },
    creditsLeft: 6,            // for non-unlimited memberships; here informational
    guestPasses: 2,
    initials: 'JR',
  },
  forge: {
    customer: { name: 'Sam Bennett', email: 'sam.bennett@hey.com', phone: '(512) 555-0144' },
    family: [
      { id: 'spouse', name: 'Alex Bennett', relation: 'Spouse' },
    ],
    membership: { name: 'Unlimited Strength', renews: 'Sep 14', credits: 'Unlimited classes' },
    creditsLeft: 8,
    guestPasses: 3,
    initials: 'SB',
  },
  sol: {
    customer: { name: 'Priya Shah', email: 'priya.shah@gmail.com', phone: '(303) 555-0127' },
    family: [
      { id: 'kid', name: 'Riya Shah', relation: 'Daughter (14)' },
      { id: 'parent', name: 'Asha Shah', relation: 'Mother' },
    ],
    membership: { name: 'Unlimited Yoga', renews: 'Sep 6', credits: 'Unlimited classes' },
    creditsLeft: 5,
    guestPasses: 1,
    initials: 'PS',
  },
};

function StepperMultiApp({ mobile: mobileProp, loggedIn = false, initialBrand = 'halcyon' }) {
  const viewportIsMobile = useViewportIsMobile();
  const mobile = mobileProp !== undefined ? mobileProp : viewportIsMobile;
  const allBrands = window.BRANDS;
  const [brandId, setBrandId] = useStateSC(initialBrand);
  const baseBrand = allBrands[brandId];
  const profile = loggedIn ? RETURNING_PROFILES[brandId] : null;

  // ── Studio settings (tweaks) panel — opt-in via window.SC_TWEAKS_ENABLED ──
  const tweaksEnabled = typeof window !== 'undefined' && window.SC_TWEAKS_ENABLED;
  const tweakDefaults = (typeof window !== 'undefined' && window.SC_TWEAK_DEFAULTS) || {};
  const [t, setTweak] = (tweaksEnabled ? window.useTweaks(tweakDefaults) : [tweakDefaults, () => {}]);

  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')
    : ((typeof window !== 'undefined' && window.STEPPER_APP_BG) || '#FAFAF7');
  const hideTerms = !!t.hideTerms;
  const hideBillingAddress = !!t.hideBillingAddress;
  const hideLogo = !!t.hideLogo;

  const c = brand.class;

  // --- form state ---
  // Single-attendee booking (matches the Purchase screen pattern).
  const [activeStep, setActiveStep] = useStateSC(1);
  const [completed, setCompleted] = useStateSC({});
  const [tierId, setTierId] = useStateSC(loggedIn ? 'membership' : 'dropin');
  const tier = c.pricing.find(t => t.id === tierId) || c.pricing[0];
  // Buyer details — name split into first/last, address moved to step 3.
  const defaultCustomer = {
    firstName: '', lastName: '', email: '', phone: '',
    street: '', apt: '', city: '', state: '', zip: '',
  };
  const splitProfileCustomer = (p) => {
    const parts = (p?.name || '').trim().split(/\s+/);
    return {
      ...defaultCustomer,
      firstName: parts[0] || '',
      lastName: parts.slice(1).join(' '),
      email: p?.email || '',
      phone: p?.phone || '',
    };
  };
  const [customer, setCustomer] = useStateSC(loggedIn ? splitProfileCustomer(profile.customer) : defaultCustomer);
  // Waiver — single attendee. Logged-in clients are already on file.
  const [waiverOpen, setWaiverOpen] = useStateSC(false);
  const [waiverChecked, setWaiverChecked] = useStateSC(loggedIn);
  // SMS + terms + recipient
  const [smsTransactional, setSmsTransactional] = useStateSC(true);
  const [smsMarketing, setSmsMarketing] = useStateSC(true);
  const [agreeTerms, setAgreeTerms] = useStateSC(true);
  const [forSomeoneElse, setForSomeoneElse] = useStateSC(false);
  // Multi-booking: who the extra spots are for — '' until the buyer picks
  // a scenario, then 'family' or 'guest'. `includeSelf` confirms whether the
  // buyer is also attending (taking a spot too). `people` holds the added
  // attendees; each row collects a name (required) and email (optional), and
  // it always keeps at least one row so a scenario reveals a ready line.
  const [recipientType, setRecipientType] = useStateSC(''); // '' | 'family' | 'guest'
  const [includeSelf, setIncludeSelf] = useStateSC(true);
  const [people, setPeople] = useStateSC([{ firstName: '', lastName: '', dob: '', email: '', phone: '' }]);
  const [referralSource, setReferralSource] = useStateSC('');
  // Spot booking — one selected seat per attendee; index follows attendeeList.
  const [seatByPerson, setSeatByPerson] = useStateSC([]);
  const [activeSpot, setActiveSpot] = useStateSC(0);
  // Whether the booker draws from a family member's shared pack (family flow).
  const [buyerUseCredit, setBuyerUseCredit] = useStateSC(false);
  // Payment
  const [savePay, setSavePay] = useStateSC(true);
  const [discount, setDiscount] = useStateSC(0);
  const [promoCode, setPromoCode] = useStateSC('');
  const [summaryOpen, setSummaryOpen] = useStateSC(false);   // mobile drawer state

  // Multi-booking: the buyer always attends; extra spots come from `people`
  // (only counted while the section is enabled). Feeding them through
  // addedFamily makes the existing attendee-count and total math apply.
  // The buyer occupies a spot when not booking for others, or when booking
  // for others and confirming they're attending too. Extra spots only count
  // once a scenario (family/guest) is chosen.
  const buyerAttends = !forSomeoneElse || includeSelf;
  const extraPeople = (forSomeoneElse && recipientType) ? people : [];
  const whoFor = { self: buyerAttends, family: [], guests: [], addedFamily: extraPeople, allWaiversSigned: waiverChecked };
  const householdTierId = tierId;
  const setHouseholdTierId = setTierId;
  const guestTierIds = [];
  const setGuestTierIds = () => {};

  // Charge model: drop-ins are per-person, packs and memberships are a
  // single household purchase regardless of how many bookings they cover
  // today (the remaining sessions stay on the account for next time).
  const tierTotalForCount = (t, count) => {
    if (!t || count <= 0) return 0;
    if (t.recurring) return t.price;
    if (t.sessions && t.sessions > 1) return t.price;
    return t.price * count;
  };

  const addedFamilyCount = (whoFor.addedFamily || []).length;
  const familyTotalCount = whoFor.family.length + addedFamilyCount;
  const householdCount = (whoFor.self ? 1 : 0) + familyTotalCount;
  const guestCount = whoFor.guests.length;
  const attendeeCount = Math.max(1, householdCount + guestCount);
  const dropInUnitPrice = c.pricing[0].price;

  const householdTier = c.pricing.find(t => t.id === householdTierId) || c.pricing[0];
  const guestTiers = whoFor.guests.map((_, i) =>
    c.pricing.find(t => t.id === (guestTierIds[i] || 'dropin')) || c.pricing[0]
  );

  // ── Per-person payment (multi-booking) ─────────────────────────────
  // One entry per attendee, each able to choose its own pricing option. The
  // buyer uses the shared `tierId`; each added person carries its own
  // `tierId` (default drop-in). With a single attendee this collapses back
  // to one option and the pay step looks exactly as before.
  const personFallback = (i) =>
    recipientType === 'guest' ? `Guest ${i + 1}` : `Family member ${i + 1}`;
  const attendeeList = [];
  if (buyerAttends) {
    attendeeList.push({
      key: 'buyer', isBuyer: true,
      label: (customer.firstName && customer.firstName.trim().split(' ')[0]) || 'You',
      title: 'You',
      tierId,
      setTierId,
      rawUseCredit: buyerUseCredit,
      setUseCredit: setBuyerUseCredit,
    });
  }
  extraPeople.forEach((p, i) => {
    const first = (p.firstName || '').trim();
    const full = `${p.firstName || ''} ${p.lastName || ''}`.trim();
    attendeeList.push({
      key: `person-${i}`, isBuyer: false,
      label: first || personFallback(i),
      title: full || personFallback(i),
      tierId: p.tierId || 'dropin',
      setTierId: (id) =>
        setPeople(prev => prev.map((pp, idx) => (idx === i ? { ...pp, tierId: id } : pp))),
      rawUseCredit: !!p.useCredit,
      setUseCredit: (v) =>
        setPeople(prev => prev.map((pp, idx) => (idx === i ? { ...pp, useCredit: v } : pp))),
    });
  });
  const tierFor = (a) => c.pricing.find(t => t.id === a.tierId) || c.pricing[0];
  const isMultiPay = attendeeList.length > 1;

  // Shared family pack — family flow only. Whichever family member buys a class
  // pack owns it, and the rest of the family may draw a credit from it instead
  // of paying. The first attendee holding a pack is the owner. Guest flow never
  // shares — each guest must pick their own pricing option.
  const isPackTier = (t) => !t.recurring && t.sessions > 1;
  const packOwnerIndex = (!loggedIn && recipientType === 'family')
    ? attendeeList.findIndex(a => isPackTier(tierFor(a)))
    : -1;
  const familyCreditOn = packOwnerIndex !== -1;
  const packOwner = familyCreditOn ? attendeeList[packOwnerIndex] : null;
  const packTier = packOwner ? tierFor(packOwner) : null;
  const packTotalCredits = packTier ? packTier.sessions : 0;
  const packOwnerName = packOwner ? packOwner.label : '';

  // Finalize per-attendee credit usage: any non-owner family member may use a
  // shared credit; the owner is the one paying for the pack.
  attendeeList.forEach((a, idx) => {
    a.isPackOwner = familyCreditOn && idx === packOwnerIndex;
    a.useCredit = familyCreditOn && !a.isPackOwner && a.rawUseCredit;
  });

  // Credits: the owner's own attendance uses one; each opted-in member uses one
  // more. Remaining gates further opt-ins.
  const creditsUsedByOwner = familyCreditOn ? 1 : 0;
  const creditsUsedByOthers = attendeeList.filter(a => a.useCredit).length;
  const creditsRemaining = packTotalCredits - creditsUsedByOwner - creditsUsedByOthers;

  // Logged-in mode: account holder uses membership credit or drop-in;
  // family members always pay drop-in (no per-family choice); each guest
  // can pay with their own tier or burn a guest pass off the account.
  // NLI mode: household shares one tier; each guest picks their own.
  const usingMembership = loggedIn && tierId === 'membership';
  let subtotal, membershipCredit, displayPrice, total, unitPrice;
  if (loggedIn) {
    const householdGrossDropIn = dropInUnitPrice * householdCount;
    const guestGross = whoFor.guests.reduce((sum, _, i) => {
      const id = guestTierIds[i] || 'dropin';
      if (id === 'guestpass') return sum;
      const t = c.pricing.find(t => t.id === id) || c.pricing[0];
      return sum + tierTotalForCount(t, 1);
    }, 0);
    subtotal = householdGrossDropIn + guestGross;
    membershipCredit = usingMembership && whoFor.self ? dropInUnitPrice : 0;
    displayPrice = subtotal - membershipCredit;
    total = Math.max(0, displayPrice - discount);
    unitPrice = dropInUnitPrice;
  } else {
    // Each attendee pays for their own chosen option, except those covered by
    // a shared family-pack credit.
    subtotal = attendeeList.reduce(
      (sum, a) => (a.useCredit ? sum : sum + tierTotalForCount(tierFor(a), 1)), 0);
    membershipCredit = 0;
    displayPrice = subtotal;
    total = Math.max(0, displayPrice - discount);
    unitPrice = householdTier.price;
  }

  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; });
  };

  // Spot booking — normalize the seat array to the current attendee count so it
  // stays in sync as people are added/removed.
  const seats = Array.from({ length: attendeeList.length }, (_, i) => seatByPerson[i] || null);
  const activeIdx = Math.min(activeSpot, Math.max(0, attendeeList.length - 1));
  const spotsOk = attendeeList.length > 0 && seats.every(Boolean);
  const assignSeat = (seatId) => {
    if (SC_TAKEN_SEATS.has(seatId)) return;
    const toggledOff = seats[activeIdx] === seatId;
    const next = seats.map(s => (s === seatId ? null : s));
    if (!toggledOff) next[activeIdx] = seatId;
    setSeatByPerson(next);
    if (!toggledOff) {
      const ni = next.findIndex(s => !s);
      if (ni !== -1) setActiveSpot(ni);
    }
  };
  const spotsSummary = seats.filter(Boolean).join(', ');

  const whoForSummary = () => {
    const ppl = [];
    if (whoFor.self) ppl.push(loggedIn ? profile.customer.name.split(' ')[0] : 'You');
    whoFor.family.forEach(id => {
      const member = profile?.family.find(f => f.id === id);
      if (member) ppl.push(member.name.split(' ')[0]);
    });
    (whoFor.addedFamily || []).forEach(f => {
      if (f.firstName) ppl.push(f.firstName.trim());
    });
    whoFor.guests.forEach(g => ppl.push(g.name || 'Guest'));
    return ppl.length ? `${ppl.length} spot${ppl.length > 1 ? 's' : ''} reserved · ${ppl.join(' + ')}` : 'No attendees selected';
  };

  const paySummary = () => {
    if (loggedIn) {
      const selfPart = whoFor.self ? (usingMembership ? 'Membership' : 'Drop-in') : null;
      const familyPart = familyTotalCount > 0
        ? `${familyTotalCount} family`
        : null;
      const guestPart = guestCount > 0
        ? `${guestCount} guest${guestCount > 1 ? 's' : ''}`
        : null;
      const parts = [selfPart, familyPart, guestPart].filter(Boolean);
      return `${parts.join(' · ')} · $${total.toFixed(2)}`;
    }
    // With one attendee, show the chosen option; with several, the head count
    // stays compact even when each person picked a different option.
    if (isMultiPay) {
      return `${attendeeList.length} people · $${total.toFixed(2)}`;
    }
    return `${tierFor(attendeeList[0] || { tierId }).label} · $${total.toFixed(2)}`;
  };

  // Per-group payment breakdown used by the order summary. Each entry is one
  // line item the buyer sees on the right (or in the mobile drawer).
  const householdLabel = whoFor.self && familyTotalCount === 0
    ? 'Yourself'
    : !whoFor.self && familyTotalCount > 0
      ? `${familyTotalCount} family member${familyTotalCount > 1 ? 's' : ''}`
      : `Yourself + ${familyTotalCount} family member${familyTotalCount > 1 ? 's' : ''}`;
  const paymentBreakdown = loggedIn ? [
    // Self
    ...(whoFor.self ? [{
      label: profile.customer.name.split(' ')[0] || 'Yourself',
      sub: usingMembership ? `${profile.membership.name} · Free` : 'Drop-in',
      amount: usingMembership ? 0 : dropInUnitPrice,
    }] : []),
    // Family on file (selected) + inline-added family
    ...whoFor.family.map(id => {
      const member = profile.family.find(f => f.id === id);
      return {
        label: member ? member.name.split(' ')[0] : 'Family',
        sub: 'Drop-in',
        amount: dropInUnitPrice,
      };
    }),
    ...(whoFor.addedFamily || []).map(f => ({
      label: f.firstName ? f.firstName.trim() : 'Family',
      sub: 'Drop-in',
      amount: dropInUnitPrice,
    })),
    // Guests — guest pass renders as $0/Pass
    ...whoFor.guests.map((g, i) => {
      const id = guestTierIds[i] || 'dropin';
      if (id === 'guestpass') {
        return { label: g.name || `Guest ${i + 1}`, sub: 'Guest pass', amount: 0 };
      }
      const t = c.pricing.find(t => t.id === id) || c.pricing[0];
      return {
        label: g.name || `Guest ${i + 1}`,
        sub: t.label,
        amount: tierTotalForCount(t, 1),
      };
    }),
  ] : attendeeList.map(a => {
    // One line per attendee, priced by the option that attendee chose — or
    // $0 when covered by a credit from the shared family pack.
    if (a.useCredit) return { label: a.label, sub: 'Family pack credit', amount: 0 };
    const t = tierFor(a);
    return { label: a.label, sub: t.label, amount: tierTotalForCount(t, 1) };
  });

  // ─── Step bodies ───────────────────────────────────────────────
  // Step 1 — booker details (single attendee), liability waiver, optional
  // "for someone else" block, SMS consent. Matches the Purchase screen but
  // keeps the waiver since this is a class/appointment booking.
  const detailsSummary = customer.firstName && customer.email
    ? `${customer.firstName} ${customer.lastName} · ${customer.email}`.trim()
    : null;
  const stepWhoFor = (
    <StepCard
      brand={brand} mobile={mobile}
      step={1} title="Your details"
      isOpen={activeStep === 1} isCompleted={completed[1]}
      summary={completed[1] ? detailsSummary : null}
      onEdit={() => editStep(1)}
    >
      {loggedIn ? (
        <div style={{
          padding: '12px 14px', borderRadius: 12,
          background: brand.accentSoft, border: `1px solid ${brand.border}`,
          fontSize: 13.5, color: brand.ink, lineHeight: 1.5,
        }}>
          Booking as <strong>{profile.customer.name}</strong> · {profile.customer.email}.
          Waiver already on file.
        </div>
      ) : (
        <>
          <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>
          <SmsConsent
            brand={brand}
            transactional={smsTransactional} setTransactional={setSmsTransactional}
            marketing={smsMarketing} setMarketing={setSmsMarketing}
          />
          <SCMultiBookingBlock
            brand={brand}
            enabled={forSomeoneElse} setEnabled={setForSomeoneElse}
            type={recipientType} setType={setRecipientType}
            includeSelf={includeSelf} setIncludeSelf={setIncludeSelf}
            people={people} setPeople={setPeople}
          />
          {/* Buyer signs the waiver only when they're attending a spot. */}
          {buyerAttends && (
            <WaiverInline
              brand={brand} brandName={brand.name}
              open={waiverOpen} setOpen={setWaiverOpen}
              checked={waiverChecked} onChange={setWaiverChecked}
            />
          )}
          <SCReferralSourceSelect
            brand={brand}
            value={referralSource}
            onChange={setReferralSource}
          />
        </>
      )}
      <StepNext brand={brand} onClick={() => goNext(1)} />
    </StepCard>
  );

  // Step 2 — pick a spot for each attendee (3 × 6 studio grid).
  const stepSpots = (
    <StepCard
      brand={brand} mobile={mobile}
      step={2} title="Pick your spots"
      isOpen={activeStep === 2} isCompleted={completed[2]}
      summary={completed[2] ? spotsSummary : null}
      onEdit={() => editStep(2)}
    >
      <SCSeatMap
        brand={brand} mobile={mobile}
        people={attendeeList.map(a => a.title)}
        seats={seats} activeIdx={activeIdx}
        setActiveIdx={setActiveSpot} onAssign={assignSeat}
      />
      <StepNext brand={brand} onClick={() => goNext(2)} disabled={!spotsOk} />
    </StepCard>
  );

  const loggedInPayOptions = loggedIn ? [
    {
      id: 'membership',
      label: `Use ${profile.membership.name.toLowerCase()} credit`,
      sub: `${profile.membership.credits} · renews ${profile.membership.renews}`,
      cost: 'Free',
    },
    {
      id: 'dropin',
      label: 'Pay drop-in',
      sub: 'Save your credits for another time',
      cost: `$${c.pricing[0].price}`,
    },
  ] : null;

  // Pack/membership footnote shown under a picker once that option is chosen.
  const tierNote = (t) => (
    <>
      {t.sessions > 1 && !t.recurring && (
        <div style={{ fontSize: 12, color: brand.inkSoft, marginTop: 10, lineHeight: 1.5 }}>
          1 credit applied today. {Math.max(0, t.sessions - 1)} remaining for future bookings.
        </div>
      )}
      {t.recurring && (
        <div style={{ fontSize: 12, color: brand.inkSoft, marginTop: 10, lineHeight: 1.5 }}>
          Membership starts today and includes this class. Pause or cancel any time, no fees.
        </div>
      )}
    </>
  );

  const stepPay = (
    <StepCard
      brand={brand} mobile={mobile}
      step={3} title="Choose how to pay"
      isOpen={activeStep === 3} isCompleted={completed[3]}
      summary={completed[3] ? paySummary() : null}
      onEdit={() => editStep(3)}
    >
      {loggedIn ? (
        <PaymentGroup
          brand={brand}
          title="For you"
          subtitle="Use your membership credit or pay drop-in."
        >
          <PaymentSourceChoice
            brand={brand}
            options={loggedInPayOptions}
            value={tierId}
            onChange={setTierId}
          />
        </PaymentGroup>
      ) : isMultiPay ? (
        // Multiple people booked — one payment option per person.
        <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
          <div style={{ fontSize: 12.5, color: brand.inkSoft, lineHeight: 1.45 }}>
            Choose a payment option for each person.
          </div>
          {familyCreditOn && (
            <div style={{
              fontSize: 12.5, color: brand.ink, lineHeight: 1.45,
              background: brand.accentSoft, border: `1px solid ${brand.border}`,
              borderRadius: 10, padding: '10px 12px',
            }}>
              {packOwnerName}'s {packTier.label.toLowerCase()} has {Math.max(0, packTotalCredits - 1)} shareable
              credit{packTotalCredits - 1 === 1 ? '' : 's'} — family members can use one instead of paying.
            </div>
          )}
          {attendeeList.map(a => {
            const at = tierFor(a);
            const showCredit = familyCreditOn && !a.isPackOwner;
            const creditDisabled = !a.useCredit && creditsRemaining <= 0;
            return (
              <PaymentGroup key={a.key} brand={brand} title={a.title}>
                {showCredit && (
                  <label style={{
                    display: 'flex', alignItems: 'flex-start', gap: 8, marginBottom: 10,
                    cursor: creditDisabled ? 'not-allowed' : 'pointer',
                  }}>
                    <span
                      onClick={(e) => { e.preventDefault(); if (!creditDisabled) a.setUseCredit(!a.useCredit); }}
                      style={{
                        flex: '0 0 16px', width: 16, height: 16, marginTop: 1, borderRadius: 4,
                        border: `1.5px solid ${a.useCredit ? brand.accent : brand.border}`,
                        background: a.useCredit ? brand.accent : '#fff',
                        display: 'grid', placeItems: 'center', opacity: creditDisabled ? 0.5 : 1,
                      }}>
                      {a.useCredit && (
                        <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: 12.5, color: creditDisabled ? brand.inkSoft : brand.ink, lineHeight: 1.45 }}>
                      Use a credit from {packOwnerName}'s family pack{creditDisabled ? ' (none left)' : ''}
                    </span>
                  </label>
                )}
                {a.useCredit ? (
                  <div style={{
                    fontSize: 12.5, color: brand.inkSoft, lineHeight: 1.5, padding: '10px 12px',
                    border: `1px solid ${brand.border}`, borderRadius: 10, background: brand.surface,
                  }}>
                    Covered by a family pack credit — no charge.
                    {' '}{Math.max(0, creditsRemaining)} credit{creditsRemaining === 1 ? '' : 's'} left.
                  </div>
                ) : (
                  <>
                    <TierPicker brand={brand} options={c.pricing} value={a.tierId} onChange={a.setTierId} />
                    {tierNote(at)}
                  </>
                )}
              </PaymentGroup>
            );
          })}
        </div>
      ) : (
        <PaymentGroup
          brand={brand}
          title="For this booking"
          subtitle="Pick a drop-in, class pack, or membership."
        >
          <TierPicker brand={brand} options={c.pricing} value={tierId} onChange={setTierId} />
          {tierNote(tier)}
        </PaymentGroup>
      )}
      <StepNext brand={brand} onClick={() => goNext(3)} />
    </StepCard>
  );

  // For logged-in: payment details are simplified — confirmation + saved card.
  // When the booking is free (membership credit covers it, or a discount
  // brings total to $0), we skip every credit-card affordance.
  const isFree = total === 0;
  const stepPayment = (
    <StepCard
      brand={brand} mobile={mobile}
      step={4} title={isFree ? 'Confirm booking' : (loggedIn ? 'Confirm & book' : 'Payment')}
      isOpen={activeStep === 4} isCompleted={false}
      summary={null}
      onEdit={() => editStep(4)}
    >
      {isFree ? (
        <>
          <div style={{
            marginTop: loggedIn ? 0 : 14,
            padding: '14px 16px',
            borderRadius: 12,
            background: brand.accentSoft,
            border: `1px solid ${brand.border}`,
            fontSize: 13.5, color: brand.ink, lineHeight: 1.5,
          }}>
            {usingMembership
              ? `This booking is covered by your ${profile.membership.name.toLowerCase()} membership. No payment needed.`
              : 'No payment required — your discount covers the full booking.'}
          </div>
        </>
      ) : loggedIn ? (
        <SavedPaymentRow brand={brand} customer={profile.customer} />
      ) : (
        <>
          <PaymentSection brand={brand} methods={{ apple: true, google: true, link: true, ach: true }} />
          <SavePayment brand={brand} returning={false} value={savePay} onChange={setSavePay} />
          {!hideBillingAddress && (
            <SCAddressBlock brand={brand} customer={customer} setCustomer={setCustomer} />
          )}
        </>
      )}
      {!loggedIn && !hideTerms && (
        <SCPurchaseTermsAccordion
          brand={brand} agreed={agreeTerms} setAgreed={setAgreeTerms}
        />
      )}

      {(() => {
         const detailsOk = loggedIn
           || (customer.firstName && customer.lastName && /\S+@\S+\.\S+/.test(customer.email));
         // Booking for others requires picking a scenario, and every added
         // spot needs a name (email is optional).
         const typeOk = !forSomeoneElse || !!recipientType;
         const peopleOk = !forSomeoneElse || extraPeople.every(p => (p.firstName || '').trim() && (p.lastName || '').trim());
         // Waiver gating: the buyer signs the waiver only when attending a
         // spot. Added attendees sign at check-in (noted in their section).
         const waiverOk = loggedIn || !buyerAttends || waiverChecked;
         const termsOk = hideTerms || agreeTerms;
         const ok = (loggedIn || (waiverOk && termsOk && detailsOk && typeOk && peopleOk));
         return (
           <button
             disabled={!ok}
             onClick={() => alert(`Booked ${c.title} · ${isFree ? 'Free' : `$${total.toFixed(2)}`}`)}
             style={{
               width: '100%',
               marginTop: 18,
               background: ok ? brand.accent : '#D8D6CF',
               color: '#fff',
               border: 0, borderRadius: 12,
               padding: '16px 18px',
               fontSize: 15, fontWeight: 700,
               cursor: ok ? 'pointer' : 'not-allowed',
               fontFamily: brand.displayFont,
               letterSpacing: '-0.005em',
             }}>
             {isFree
               ? 'Confirm booking · Free'
               : (loggedIn
                   ? `Confirm booking · $${total.toFixed(2)}`
                   : (isMultiPay
                       ? `Book class · $${total.toFixed(2)}`
                       : (tier.recurring ? `Start membership · $${total}/mo` : `Book class · $${total}`)))
             }
           </button>
         );
       })()}
    </StepCard>
  );

  // ─── Layout ────────────────────────────────────────────────────
  return (
    <div style={{
      minHeight: mobile ? 0 : '100vh',
      background: appBg,
      fontFamily: brand.bodyFont,
      color: brand.ink,
    }}>
      {/* Brand bar */}
      {!hideLogo && (
      <BrandBar
        brand={brand} mobile={mobile} loggedIn={loggedIn}
        profile={profile}
        allBrands={allBrands} brandId={brandId}
        setBrandId={(id) => {
          setBrandId(id);
          setActiveStep(1);
          setCompleted({});
          // Reset state that's brand-dependent
          if (loggedIn) {
            const newProfile = RETURNING_PROFILES[id];
            setCustomer(newProfile.customer);
            setTierId('membership');
          }
        }}
      />
      )}

      {/* Body — desktop = 2 col, mobile = single col with collapsible summary */}
      {mobile ? (
        <div style={{ padding: '14px 16px 120px', display: 'flex', flexDirection: 'column', gap: 12 }}>
          <ClassDetailsCard brand={brand} c={c} mobile />
          {loggedIn
            ? <WelcomeBackCallout brand={brand} profile={profile} />
            : <SignInCallout brand={brand} />}
          {stepWhoFor}
          {stepSpots}
          {stepPay}
          {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 }}>
            <ClassDetailsCard brand={brand} c={c} />
            {loggedIn
              ? <WelcomeBackCallout brand={brand} profile={profile} />
              : <SignInCallout brand={brand} />}
            {stepWhoFor}
            {stepSpots}
            {stepPay}
            {stepPayment}
          </div>

          <OrderSummaryAside
            brand={brand} c={c} tier={tier}
            displayPrice={displayPrice} subtotal={subtotal} membershipCredit={membershipCredit}
            discount={discount} total={total}
            promoCode={promoCode} setPromoCode={setPromoCode}
            onApplyPromo={() => promoCode && setDiscount(5)}
            usingMembership={usingMembership}
            attendeeCount={attendeeCount} dropInUnitPrice={dropInUnitPrice}
            paymentBreakdown={paymentBreakdown}
          />
        </div>
      )}

      {/* Mobile sticky bottom bar with collapsible summary drawer */}
      {mobile && (
        <MobileBottomBar
          brand={brand} c={c} tier={tier}
          displayPrice={displayPrice} subtotal={subtotal} membershipCredit={membershipCredit}
          discount={discount} total={total}
          promoCode={promoCode} setPromoCode={setPromoCode}
          onApplyPromo={() => promoCode && setDiscount(5)}
          open={summaryOpen} setOpen={setSummaryOpen}
          usingMembership={usingMembership}
          attendeeCount={attendeeCount} dropInUnitPrice={dropInUnitPrice}
          paymentBreakdown={paymentBreakdown}
        />
      )}

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

// ────────────────────────────────────────────────────────────────
// WelcomeBackCallout — replaces SignInCallout for returning members.
// Mirrors the visual shape of SignInCallout (white card with accent
// border + soft glow) but greets the user and surfaces their active
// membership and renewal date instead of a sign-in CTA.
// ────────────────────────────────────────────────────────────────
function WelcomeBackCallout({ brand, profile }) {
  const firstName = profile.customer.name.split(' ')[0];
  return (
    <div style={{ padding: '4px 2px', marginBottom: 10 }}>
      <div style={{
        fontFamily: brand.displayFont,
        fontWeight: brand.displayWeight,
        fontSize: 26,
        color: brand.ink,
        lineHeight: 1.2,
        letterSpacing: '-0.01em',
      }}>
        Welcome back, {firstName}
      </div>
      <div style={{ fontSize: 13, color: brand.inkSoft, marginTop: 4, lineHeight: 1.4 }}>
        {profile.membership.name} membership · renews {profile.membership.renews}
        {profile.guestPasses > 0 && (
          <> · {profile.guestPasses} guest pass{profile.guestPasses === 1 ? '' : 'es'} available</>
        )}
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Brand bar — shows logged-in user's avatar instead of brand-switcher
// when loggedIn=true. Brand switcher still visible (for prototype demo).
// ────────────────────────────────────────────────────────────────
function BrandBar({ brand, mobile, loggedIn, profile, allBrands, brandId, setBrandId }) {
  return (
    <div style={{
      padding: mobile ? '20px 16px 0' : '28px 28px 0',
      maxWidth: 1280, margin: '0 auto',
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      gap: 12,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
        <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',
          flex: '0 0 auto',
        }}>{brand.initials}</div>
        <div style={{
          fontFamily: brand.displayFont, fontWeight: brand.displayWeight,
          fontSize: mobile ? 16 : 19, color: brand.ink,
          letterSpacing: '-0.01em',
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{brand.name}</div>
      </div>

      {loggedIn && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <div style={{
            width: 32, height: 32, borderRadius: 999,
            background: brand.accentSoft, color: brand.accentDeep,
            display: 'grid', placeItems: 'center',
            fontWeight: 700, fontSize: 12,
            fontFamily: brand.displayFont,
            border: `1.5px solid ${brand.accent}`,
          }}>{profile.initials}</div>
          {!mobile && (
            <div style={{ fontSize: 13, color: brand.ink, fontWeight: 600 }}>
              {profile.customer.name.split(' ')[0]}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// SavedPaymentRow — single row showing card on file (logged-in only)
// ────────────────────────────────────────────────────────────────
function SavedPaymentRow({ brand, customer }) {
  return (
    <div style={{
      border: `1px solid ${brand.border}`,
      borderRadius: 12,
      padding: '14px 16px',
      display: 'flex', alignItems: 'center', gap: 12,
      background: '#FAFAF7',
    }}>
      <div style={{
        width: 38, height: 26, borderRadius: 5,
        background: 'linear-gradient(135deg, #1F1D1A, #44423E)',
        color: '#fff',
        fontSize: 8, fontWeight: 800,
        display: 'grid', placeItems: 'center',
        flex: '0 0 auto',
        letterSpacing: '0.05em',
      }}>VISA</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13.5, fontWeight: 600, color: brand.ink }}>Visa ending in 4242</div>
        <div style={{ fontSize: 12, color: brand.inkSoft, marginTop: 1 }}>Default · {customer.email}</div>
      </div>
      <button style={{
        background: 'transparent', border: 0,
        color: brand.accent, fontSize: 12, fontWeight: 700,
        letterSpacing: '0.06em', textTransform: 'uppercase',
        cursor: 'pointer',
      }}>Change</button>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// OrderSummaryAside — sticky right sidebar on desktop
// ────────────────────────────────────────────────────────────────
function OrderSummaryAside({ brand, c, tier, displayPrice, subtotal, membershipCredit, discount, total, promoCode, setPromoCode, onApplyPromo, usingMembership, attendeeCount, dropInUnitPrice, paymentBreakdown }) {
  return (
    <aside style={{
      position: 'sticky',
      top: 28,
      background: '#fff',
      border: `1px solid ${brand.border}`,
      borderRadius: 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>

      <SummaryLineItem brand={brand} c={c} tier={tier} displayPrice={displayPrice} usingMembership={usingMembership} attendeeCount={attendeeCount} dropInUnitPrice={dropInUnitPrice} paymentBreakdown={paymentBreakdown} />

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

      <SummaryTotals brand={brand} subtotal={subtotal} membershipCredit={membershipCredit} discount={discount} total={total} usingMembership={usingMembership} />

      <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>
  );
}

// ────────────────────────────────────────────────────────────────
// MobileBottomBar — fixed bottom CTA + collapsible summary
// ────────────────────────────────────────────────────────────────
function MobileBottomBar({ brand, c, tier, displayPrice, subtotal, membershipCredit, discount, total, promoCode, setPromoCode, onApplyPromo, open, setOpen, usingMembership, attendeeCount, dropInUnitPrice, paymentBreakdown }) {
  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',
        }}>
          <SummaryLineItem brand={brand} c={c} tier={tier} displayPrice={displayPrice} usingMembership={usingMembership} attendeeCount={attendeeCount} dropInUnitPrice={dropInUnitPrice} paymentBreakdown={paymentBreakdown} compact />
          <div style={{ height: 12 }}></div>
          <PromoInput brand={brand} value={promoCode} onChange={setPromoCode} onApply={onApplyPromo} />
          <div style={{ height: 12 }}></div>
          <SummaryTotals brand={brand} subtotal={subtotal} membershipCredit={membershipCredit} discount={discount} total={total} usingMembership={usingMembership} />
        </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 }}>
          {usingMembership ? 'Free' : `$${total.toFixed(2)}`}
        </div>
      </button>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Reusable summary chunks
// ────────────────────────────────────────────────────────────────
function SummaryLineItem({ brand, c, tier, displayPrice, usingMembership, attendeeCount = 1, dropInUnitPrice, compact, paymentBreakdown }) {
  const additional = Math.max(0, attendeeCount - 1);
  const showLegacyBreakdown = attendeeCount > 1 && !paymentBreakdown;
  return (
    <div style={{ marginBottom: compact ? 0 : 18 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
        <div style={{
          width: 56, height: 56, borderRadius: 10,
          flex: '0 0 auto',
          background: c.image ? `url(${c.image}) center/cover` : `linear-gradient(135deg, ${brand.accent}, ${brand.accentDeep})`,
          position: 'relative',
        }}>
          <div style={{
            position: 'absolute', top: -6, right: -6,
            minWidth: 22, height: 22, borderRadius: 999,
            padding: '0 6px',
            background: brand.ink, color: '#fff',
            display: 'grid', placeItems: 'center',
            fontSize: 11, fontWeight: 700,
          }}>{attendeeCount}</div>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 600, color: brand.ink, lineHeight: 1.35 }}>{c.title}</div>
          <div style={{ fontSize: 12, color: brand.inkSoft, marginTop: 2, lineHeight: 1.4 }}>
            {c.instructor} · {c.date}
            <br/>
            {c.time} · {c.duration}
          </div>
          {showLegacyBreakdown && (
            <div style={{ fontSize: 11.5, color: brand.inkSoft, marginTop: 4, lineHeight: 1.4 }}>
              {usingMembership ? (
                <>1 × Membership credit{additional > 0 && <> · {additional} × Drop-in (${dropInUnitPrice})</>}</>
              ) : (
                <>{attendeeCount} × ${tier.price}</>
              )}
            </div>
          )}
        </div>
        <div style={{ fontFamily: brand.displayFont, fontSize: 14, fontWeight: 700, color: brand.ink, textAlign: 'right' }}>
          {usingMembership && additional === 0 ? (
            <>
              <div style={{ fontSize: 11, color: brand.inkSoft, fontWeight: 500, textDecoration: 'line-through' }}>${tier.price}</div>
              <div style={{ color: '#3A7A4D' }}>Free</div>
            </>
          ) : `$${displayPrice}`}
        </div>
      </div>
      {paymentBreakdown && paymentBreakdown.length > 0 && (
        <div style={{
          marginTop: 12, paddingTop: 12,
          borderTop: `1px dashed ${brand.border}`,
          display: 'flex', flexDirection: 'column', gap: 6,
        }}>
          {paymentBreakdown.map((row, i) => (
            <div key={i} style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 12, fontWeight: 600, color: brand.ink, lineHeight: 1.3 }}>{row.label}</div>
                <div style={{ fontSize: 11, color: brand.inkSoft, marginTop: 1 }}>{row.sub}</div>
              </div>
              <div style={{ fontSize: 12.5, fontWeight: 700, color: brand.ink, fontFamily: brand.displayFont }}>
                ${Number(row.amount).toFixed(2)}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function PromoInput({ 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 SummaryTotals({ brand, subtotal, membershipCredit = 0, discount, total, usingMembership }) {
  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>
      {usingMembership && membershipCredit > 0 && (
        <div style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', color: '#3A7A4D' }}>
          <span>Membership credit</span><span>−${membershipCredit.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>
  );
}

// ────────────────────────────────────────────────────────────────
// Class details card (always visible)
// ────────────────────────────────────────────────────────────────
function ClassDetailsCard({ brand, c, mobile }) {
  return (
    <div style={{
      background: '#fff',
      border: `1px solid ${brand.border}`,
      borderRadius: 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: c.image ? `url(${c.image}) center/cover` : `linear-gradient(135deg, ${brand.accent}, ${brand.accentDeep})`,
      }}></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 booking</div>
        <div style={{ fontFamily: brand.displayFont, fontWeight: brand.displayWeight, fontSize: mobile ? 18 : 22, color: brand.ink, lineHeight: 1.2, letterSpacing: '-0.01em' }}>{c.title}</div>
        <div style={{ fontSize: mobile ? 12 : 13, color: brand.inkSoft, marginTop: mobile ? 4 : 6, lineHeight: 1.5 }}>
          {c.instructor} · {c.date} {mobile ? <br/> : 'at '}
          {c.time} · {c.duration}
        </div>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Step wrapper (collapsible card)
// ────────────────────────────────────────────────────────────────
function StepCard({ 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: 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 }}>{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>
  );
}

// ────────────────────────────────────────────────────────────────
// SmsConsent — two compact, preselected SMS consent checkboxes
// shown directly under the phone field.
// ────────────────────────────────────────────────────────────────
function SmsConsent({ 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>
  );
}

// PaymentGroup — labelled wrapper around a TierPicker so Step 2 can list one
// section per payment group (household vs. each guest). Title is the human
// label (e.g. "Yourself + Ava"); subtitle explains how the group's payment
// covers the bookings inside it.
function PaymentGroup({ brand, title, subtitle, children }) {
  return (
    <div>
      <div style={{
        fontSize: 11, fontWeight: 700, letterSpacing: '0.08em',
        textTransform: 'uppercase', color: brand.inkSoft, marginBottom: 4,
      }}>{title}</div>
      {subtitle && (
        <div style={{ fontSize: 12, color: brand.inkSoft, marginBottom: 10, lineHeight: 1.45 }}>
          {subtitle}
        </div>
      )}
      {children}
    </div>
  );
}

function StepNext({ 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>
  );
}

// ────────────────────────────────────────────────────────────────
// Seat map — 3 × 6 studio grid. Tap a person chip to make them active, then
// tap an open spot. Assigning advances to the next person who needs one.
// ────────────────────────────────────────────────────────────────
function SCSeatMap({ brand, mobile, people, seats, activeIdx, setActiveIdx, onAssign }) {
  const chip = (label, i) => {
    const active = i === activeIdx;
    const seat = seats[i];
    return (
      <button key={i} type="button" onClick={() => setActiveIdx(i)}
        style={{
          display: 'inline-flex', alignItems: 'center', gap: 8,
          padding: '7px 12px', borderRadius: 999,
          border: `1.5px solid ${active ? brand.accent : brand.border}`,
          background: active ? brand.accentSoft : '#fff',
          cursor: 'pointer', fontFamily: brand.bodyFont,
        }}>
        <span style={{
          width: 18, height: 18, borderRadius: 999, flex: '0 0 auto',
          background: seat ? brand.accent : (active ? brand.accent : brand.border),
          color: '#fff', fontSize: 10, fontWeight: 700,
          display: 'grid', placeItems: 'center',
        }}>{i + 1}</span>
        <span style={{ fontSize: 13, fontWeight: 600, color: brand.ink }}>{label}</span>
        <span style={{ fontSize: 12, fontWeight: 700, color: seat ? brand.accentDeep : brand.inkSoft }}>
          {seat || 'Pick a spot'}
        </span>
      </button>
    );
  };
  const legendDot = (bg, border, label) => (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
      <span style={{ width: 14, height: 14, borderRadius: 4, background: bg, border: `1.5px solid ${border}` }} />
      <span style={{ fontSize: 11.5, color: brand.inkSoft }}>{label}</span>
    </span>
  );
  return (
    <div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 14 }}>
        {people.map((p, i) => chip(p, i))}
      </div>

      <div style={{
        textAlign: 'center', fontSize: 10.5, fontWeight: 700, letterSpacing: '0.18em',
        textTransform: 'uppercase', color: brand.inkSoft,
        background: brand.surface, border: `1px solid ${brand.border}`,
        borderRadius: 6, padding: '5px 0', marginBottom: 12,
      }}>Front of room</div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: mobile ? 6 : 8 }}>
        {SC_SEAT_ROWS.map(row => (
          <div key={row} style={{ display: 'flex', alignItems: 'center', gap: mobile ? 6 : 8 }}>
            <div style={{
              width: 16, flex: '0 0 16px', textAlign: 'center',
              fontSize: 11, fontWeight: 700, color: brand.inkSoft, fontFamily: brand.bodyFont,
            }}>{row}</div>
            {Array.from({ length: SC_SEAT_COLS }, (_, c) => {
              const id = `${row}${c + 1}`;
              const taken = SC_TAKEN_SEATS.has(id);
              const owner = seats.indexOf(id);
              const assigned = owner !== -1;
              const bg = taken ? '#E7E7E3' : assigned ? brand.accent : '#fff';
              const bd = taken ? '#E7E7E3' : assigned ? brand.accent : brand.border;
              const fg = assigned ? '#fff' : brand.inkSoft;
              return (
                <button key={id} type="button"
                  disabled={taken}
                  onClick={() => onAssign(id)}
                  aria-label={`Seat ${id}${taken ? ' (taken)' : ''}`}
                  title={id}
                  style={{
                    flex: 1, aspectRatio: '1 / 1', minWidth: 0,
                    borderRadius: 8, border: `1.5px solid ${bd}`,
                    background: bg, color: fg,
                    fontSize: mobile ? 11 : 13, fontWeight: 700, fontFamily: brand.bodyFont,
                    display: 'grid', placeItems: 'center',
                    cursor: taken ? 'not-allowed' : 'pointer',
                    transition: 'background .12s ease, border-color .12s ease',
                  }}>
                  {assigned ? owner + 1 : ''}
                </button>
              );
            })}
          </div>
        ))}
      </div>

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16, marginTop: 14 }}>
        {legendDot('#fff', brand.border, 'Available')}
        {legendDot(brand.accent, brand.accent, 'Your spots')}
        {legendDot('#E7E7E3', '#E7E7E3', 'Taken')}
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Inline expandable waiver inside Payment step
// ────────────────────────────────────────────────────────────────
function WaiverInline({ brand, brandName, open, setOpen, checked, onChange }) {
  return (
    <div style={{
      border: `1px solid ${brand.border}`,
      borderRadius: 12,
      overflow: 'hidden',
      marginTop: 14,
      background: '#FAFAF7',
    }}>
      <button
        type="button"
        onClick={() => setOpen(!open)}
        style={{
          width: '100%',
          display: 'flex', alignItems: 'center', gap: 10,
          padding: '12px 14px',
          background: 'transparent',
          border: 0,
          textAlign: 'left',
          cursor: 'pointer',
        }}>
        <div style={{
          width: 28, height: 28, borderRadius: 8,
          background: brand.accentSoft,
          color: brand.accentDeep,
          display: 'grid', placeItems: 'center',
          flex: '0 0 auto',
        }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 700, color: brand.ink }}>Liability waiver</div>
          <div style={{ fontSize: 11.5, color: brand.inkSoft, marginTop: 1 }}>{open ? 'Tap to collapse' : 'Tap to read full waiver'}</div>
        </div>
        <svg width="16" height="16" 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="6 9 12 15 18 9"/></svg>
      </button>
      {open && (
        <div style={{
          maxHeight: 200, overflow: 'auto',
          padding: '0 14px 14px',
          fontSize: 12, lineHeight: 1.55,
          color: brand.inkSoft,
          borderTop: `1px solid ${brand.border}`,
        }}>
          <p style={{ marginTop: 12 }}>I, the undersigned, acknowledge that participation in the activities offered by {brandName} carries inherent risks of physical injury. I confirm that I am physically able to participate and have disclosed any conditions that may affect my safe practice.</p>
          <p>I voluntarily assume all risk of injury, loss, or damage to person or property arising from participation, including (but not limited to) strains, sprains, falls, and contact with equipment. I agree to follow all instructor and studio guidance.</p>
          <p>I release {brandName}, its instructors, employees, and contractors from any and all claims, demands, and causes of action arising out of my participation, except where caused by gross negligence.</p>
          <p>This waiver is binding upon me, my heirs, and my legal representatives. I have read this waiver in full and understand its terms.</p>
        </div>
      )}
      <div style={{ padding: '12px 14px', borderTop: `1px solid ${brand.border}`, background: '#fff' }}>
        <Checkbox brand={brand} checked={checked} onChange={onChange}>
          I've read and agree to the liability waiver. I'm participating at my own risk and have no conditions that prevent safe practice.
        </Checkbox>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// "Multi Booking" block — replaces the single-recipient block. The buyer
// picks a scenario (booking for family member(s) or guest(s)), confirms
// whether they're also attending a spot themselves, then adds a line per
// extra person (name required, email optional). A − / + stepper and an
// "Add another person" link control how many spots are reserved.
// ────────────────────────────────────────────────────────────────
const MULTI_MAX_EXTRA = 8;

function SCMultiBookingBlock({
  brand, enabled, setEnabled, type, setType,
  includeSelf, setIncludeSelf, people, setPeople,
}) {
  const count = people.length;                          // extra people
  const totalSpots = (includeSelf ? 1 : 0) + count;     // incl. buyer if attending

  const isGuest = type === 'guest';
  const nounPlural = isGuest ? 'guests' : 'family members';
  const noun = (n) => isGuest
    ? (n === 1 ? 'guest' : 'guests')
    : (n === 1 ? 'family member' : 'family members');
  const rowLabel = isGuest ? 'GUEST' : 'FAMILY MEMBER';
  const rowHelper = isGuest
    ? "They'll sign the liability waiver at check-in and get their own account for next time. Add an email to send their confirmation."
    : "We'll save them to your account so you can book for them with one tap next time. Add an email to send their confirmation.";

  const pickType = (next) => {
    setType(prev => (prev === next ? '' : next));
    // Fresh start when switching scenarios.
    if (next !== type) setPeople([{ firstName: '', lastName: '', dob: '', email: '', phone: '' }]);
  };
  const setCount = (n) => {
    const next = Math.max(1, Math.min(MULTI_MAX_EXTRA, n));
    setPeople(prev => {
      const rows = prev.slice(0, next);
      while (rows.length < next) rows.push({ firstName: '', lastName: '', dob: '', email: '', phone: '' });
      return rows;
    });
  };
  const updatePerson = (i, k) => (v) =>
    setPeople(prev => prev.map((p, idx) => (idx === i ? { ...p, [k]: v } : p)));
  const removePerson = (i) =>
    setPeople(prev => (prev.length > 1 ? prev.filter((_, idx) => idx !== i) : prev));

  const headerSub = !type
    ? 'Pick who these spots are for.'
    : `${totalSpots} spot${totalSpots === 1 ? '' : 's'} reserved · ${includeSelf ? 'you + ' : ''}${count} ${noun(count)}`;

  return (
    <div style={{
      marginTop: 18,
      border: `1.5px dashed ${enabled ? brand.accent : brand.border}`,
      background: enabled ? brand.accentSoft : '#fff',
      borderRadius: 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">
            {/* Two-people glyph — booking for a group */}
            <circle cx="9" cy="8" r="3.2"/>
            <path d="M3 20 v-1 a5 5 0 0 1 5-5h2 a5 5 0 0 1 5 5 v1"/>
            <path d="M16 4.2 a3.2 3.2 0 0 1 0 7.6"/>
            <path d="M18 14.1 a5 5 0 0 1 3 4.9 v1"/>
          </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 booking this for someone else
          </span>
          <span style={{
            display: 'block', fontSize: 12.5, color: brand.inkSoft, marginTop: 2,
          }}>
            {enabled
              ? headerSub
              : 'Booking spots for family or guests too? Tap to reserve extra spots and add their details.'}
          </span>
        </span>
        <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 }}>
          {/* Scenario selector — booking for family member(s) or guest(s) */}
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 22, paddingLeft: 2 }}>
            <SCTypeBox brand={brand} checked={type === 'family'}
                       onChange={() => pickType('family')} label="Family member(s)" />
            <SCTypeBox brand={brand} checked={type === 'guest'}
                       onChange={() => pickType('guest')} label="Guest(s)" />
          </div>

          {type && (
            <>
              {/* Top of both scenarios — confirm if the buyer is attending too */}
              <SCSelfToggle
                brand={brand} checked={includeSelf} onChange={setIncludeSelf}
                nounPlural={nounPlural}
              />

              {/* How many extra people */}
              <div style={{
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                gap: 12, padding: '12px 14px',
                border: `1px solid ${brand.border}`, borderRadius: 12, background: '#fff',
              }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 13.5, fontWeight: 700, color: brand.ink }}>
                    {isGuest ? 'How many guests?' : 'How many family members?'}
                  </div>
                  <div style={{ fontSize: 11.5, color: brand.inkSoft, marginTop: 1 }}>
                    {includeSelf
                      ? `Spots in this class besides your own — ${totalSpots} total.`
                      : `Spots in this class — ${totalSpots} total.`}
                  </div>
                </div>
                <SCStepper brand={brand} value={count} min={1} max={MULTI_MAX_EXTRA}
                           onChange={setCount} />
              </div>

              {/* One line per added person */}
              {people.map((p, i) => (
                <SCRecipientSection key={i} brand={brand}
                  groupTitle={`${rowLabel} ${i + 1}`}
                  onRemove={people.length > 1 ? () => removePerson(i) : null}
                  helper={i === people.length - 1 ? rowHelper : null}>
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                    <Field brand={brand} label="First name"
                           value={p.firstName} onChange={updatePerson(i, 'firstName')} autoComplete="given-name" />
                    <Field brand={brand} label="Last name"
                           value={p.lastName} onChange={updatePerson(i, 'lastName')} autoComplete="family-name" />
                  </div>
                  {!isGuest && (
                    <Field brand={brand} label="Date of birth" type="date"
                           value={p.dob} onChange={updatePerson(i, 'dob')} autoComplete="bday" />
                  )}
                  <Field brand={brand} label="Email (optional)" type="email"
                         value={p.email} onChange={updatePerson(i, 'email')} autoComplete="off" />
                  <Field brand={brand} label="Phone (optional)" type="tel"
                         value={p.phone} onChange={updatePerson(i, 'phone')} autoComplete="off" />
                </SCRecipientSection>
              ))}

              {count < MULTI_MAX_EXTRA && (
                <button
                  type="button"
                  onClick={() => setCount(count + 1)}
                  style={{
                    display: 'inline-flex', alignItems: 'center', gap: 8,
                    alignSelf: 'flex-start',
                    background: 'transparent', border: 0, padding: '2px 0',
                    color: brand.accent, fontFamily: brand.bodyFont,
                    fontSize: 13.5, fontWeight: 700, cursor: 'pointer',
                  }}>
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none"
                       stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
                    <line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
                  </svg>
                  Add another {isGuest ? 'guest' : 'family member'}
                </button>
              )}
            </>
          )}
        </div>
      )}
    </div>
  );
}

// Self-inclusion confirmation shown at the top of both scenarios — does the
// buyer want a spot for themselves in addition to the people they're adding?
function SCSelfToggle({ brand, checked, onChange, nounPlural }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 12,
      padding: '12px 14px',
      border: `1.5px solid ${checked ? brand.accent : brand.border}`,
      background: checked ? '#fff' : brand.surface,
      borderRadius: 12,
      transition: 'border-color .15s ease, background .15s ease',
    }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13.5, fontWeight: 700, color: brand.ink }}>
          I'm attending this class too
        </div>
        <div style={{ fontSize: 11.5, color: brand.inkSoft, marginTop: 1, lineHeight: 1.4 }}>
          {checked
            ? `Reserves a spot for you on top of the ${nounPlural} below.`
            : `Only the ${nounPlural} below get a spot — you're just booking.`}
        </div>
      </div>
      <button
        type="button"
        role="switch"
        aria-checked={checked}
        aria-label="I'm attending this class too"
        onClick={() => onChange(!checked)}
        style={{
          width: 38, height: 22, borderRadius: 999,
          background: checked ? brand.accent : brand.border,
          border: 0, padding: 0, position: 'relative',
          transition: 'background .15s ease', flex: '0 0 38px', cursor: 'pointer',
        }}>
        <span style={{
          position: 'absolute', top: 2,
          left: checked ? 18 : 2,
          width: 18, height: 18, borderRadius: '50%',
          background: '#fff', boxShadow: '0 1px 2px rgba(0,0,0,0.12)',
          transition: 'left .15s ease',
        }} />
      </button>
    </div>
  );
}

// Compact − / + quantity stepper used by the multi-booking block.
function SCStepper({ brand, value, min = 0, max = 99, onChange }) {
  const btn = (label, onClick, disabled) => (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      aria-label={label}
      style={{
        width: 32, height: 32, borderRadius: 8,
        border: `1.5px solid ${disabled ? brand.border : brand.accent}`,
        background: '#fff',
        color: disabled ? brand.border : brand.accent,
        display: 'grid', placeItems: 'center',
        cursor: disabled ? 'not-allowed' : 'pointer',
        flex: '0 0 32px',
      }}>
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none"
           stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round">
        {label === 'Increase' && <line x1="12" y1="5" x2="12" y2="19"/>}
        <line x1="5" y1="12" x2="19" y2="12"/>
      </svg>
    </button>
  );
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, flex: '0 0 auto' }}>
      {btn('Decrease', () => onChange(value - 1), value <= min)}
      <div style={{
        minWidth: 20, textAlign: 'center',
        fontFamily: brand.displayFont, fontWeight: 700, fontSize: 16, color: brand.ink,
      }}>{value}</div>
      {btn('Increase', () => onChange(value + 1), value >= max)}
    </div>
  );
}

function SCTypeBox({ 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>
  );
}

// Per-family-member waiver. mode='guardian' (minor) shows a guardian
// checkbox; mode='send' (adult) shows a clipboard copy button instead.
function SCFamilyWaiver({ brand, brandName, checked, onChange, mode = 'guardian', personName }) {
  const [open, setOpen] = useStateSC(false);
  const firstName = personName ? personName.trim().split(' ')[0] : null;
  return (
    <div style={{
      marginTop: 10,
      border: `1px solid ${brand.border}`,
      borderRadius: 12,
      overflow: 'hidden',
      background: '#fff',
    }}>
      <button
        type="button"
        onClick={() => setOpen(!open)}
        style={{
          width: '100%',
          display: 'flex', alignItems: 'center', gap: 10,
          padding: '12px 14px',
          background: 'transparent', border: 0,
          textAlign: 'left', cursor: 'pointer',
          fontFamily: brand.bodyFont,
        }}
        aria-expanded={open}>
        <div style={{
          width: 28, height: 28, borderRadius: 8,
          background: brand.accentSoft, color: brand.accentDeep,
          display: 'grid', placeItems: 'center', flex: '0 0 auto',
        }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none"
               stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
            <polyline points="14 2 14 8 20 8"/>
            <line x1="9" y1="15" x2="15" y2="15"/>
          </svg>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 700, color: brand.ink }}>Liability waiver</div>
          <div style={{ fontSize: 11.5, color: brand.inkSoft, marginTop: 1 }}>
            {mode === 'send'
              ? (open ? 'Tap to collapse' : 'Tap to read · then copy to send')
              : (open ? 'Tap to collapse' : 'Tap to read full waiver')}
          </div>
        </div>
        <svg width="16" height="16" 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="6 9 12 15 18 9"/>
        </svg>
      </button>
      {open && (
        <div style={{
          maxHeight: 180, overflow: 'auto',
          padding: '0 14px 14px',
          fontSize: 12, lineHeight: 1.55, color: brand.inkSoft,
          borderTop: `1px solid ${brand.border}`,
        }}>
          <p style={{ marginTop: 12 }}>I, as the legal guardian or authorized booker for this family member, acknowledge that participation in the activities offered by {brandName} carries inherent risks of physical injury. I confirm that the participant is physically able to participate and have disclosed any conditions that may affect their safe practice.</p>
          <p>On their behalf, I voluntarily assume all risk of injury, loss, or damage to person or property arising from participation, including (but not limited to) strains, sprains, falls, and contact with equipment. The participant agrees to follow all instructor and studio guidance.</p>
          <p>I release {brandName}, its instructors, employees, and contractors from any and all claims, demands, and causes of action arising out of the participant's participation, except where caused by gross negligence.</p>
          <p>This waiver is binding upon me, the participant, our heirs, and our legal representatives.</p>
        </div>
      )}
      <div style={{
        padding: '12px 14px',
        borderTop: `1px solid ${brand.border}`,
        background: '#fff',
      }}>
        {mode === 'send' ? (
          <CopyWaiverButton brand={brand} brandName={brandName} personName={personName} />
        ) : (
          <label style={{
            display: 'flex', alignItems: 'flex-start', gap: 8,
            cursor: 'pointer',
          }}>
            <span
              onClick={(e) => { e.preventDefault(); onChange(!checked); }}
              style={{
                flex: '0 0 16px',
                width: 16, height: 16, marginTop: 1, 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="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: 12.5, color: brand.ink, lineHeight: 1.45 }}>
              {firstName
                ? `I am the legal guardian of ${firstName} — I'm signing this waiver on their behalf.`
                : `I am the legal guardian — I'm signing this waiver on their behalf.`}
            </span>
            <input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)}
                   style={{ position: 'absolute', opacity: 0, pointerEvents: 'none' }} />
          </label>
        )}
      </div>
    </div>
  );
}

function SCRecipientSection({ brand, groupTitle, helper, children, onRemove }) {
  return (
    <div style={{
      padding: 16,
      border: `1px solid ${brand.border}`,
      background: brand.surface,
      borderRadius: 12,
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        gap: 8, marginBottom: 12,
      }}>
        <div style={{
          fontFamily: brand.bodyFont, fontSize: 11, fontWeight: 700,
          letterSpacing: '0.1em', textTransform: 'uppercase',
          color: brand.ink,
        }}>{groupTitle}</div>
        {onRemove && (
          <button
            type="button"
            onClick={onRemove}
            style={{
              display: 'inline-flex', alignItems: 'center', gap: 4,
              background: 'transparent', border: 0, padding: 0,
              color: brand.inkSoft, fontFamily: brand.bodyFont,
              fontSize: 11, fontWeight: 700, letterSpacing: '0.06em',
              textTransform: 'uppercase', cursor: 'pointer',
            }}>
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none"
                 stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
              <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
            </svg>
            Remove
          </button>
        )}
      </div>
      {children}
      {helper && (
        <div style={{
          fontSize: 12, color: brand.inkSoft,
          marginTop: 6, lineHeight: 1.5,
        }}>{helper}</div>
      )}
    </div>
  );
}

// ────────────────────────────────────────────────────────────────
// Billing address — collected in the payment step.
// ────────────────────────────────────────────────────────────────
// "How did you hear about us?" — single attribution question, ported
// from the Purchase screen for cross-flow consistency.
function SCReferralSourceSelect({ 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>
  );
}

function SCAddressBlock({ brand, customer, setCustomer }) {
  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,
      }}>Billing address</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>
  );
}

// ────────────────────────────────────────────────────────────────
// Purchase terms accordion — pre-checked, expand to read the studio
// purchase terms (refunds, booking, cancellations, memberships, gifts).
// Mirrors the Purchase screen accordion.
// ────────────────────────────────────────────────────────────────
function SCPurchaseTermsAccordion({ brand, agreed, setAgreed }) {
  const [open, setOpen] = useStateSC(false);
  return (
    <div style={{
      marginTop: 14,
      border: `1.5px solid ${agreed ? brand.accent : brand.border}`,
      background: agreed ? brand.accentSoft : '#fff',
      borderRadius: 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> Drop-ins, class packs, memberships,
              gift cards and add-on purchases are non-refundable.
            </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;
              cancellation takes effect at the end of the current billing cycle.
            </p>
            <p style={{ margin: 0 }}>
              <strong>Gifts.</strong> When booking for someone else, the recipient
              receives access to the booked item; the buyer retains the billing
              record. Gift bookings 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>
  );
}

// ────────────────────────────────────────────────────────────────
// Studio settings panel — same pattern as Purchase / Appointment.
// Gated by window.SC_TWEAKS_ENABLED.
// ────────────────────────────────────────────────────────────────
function SCTweaks({ 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.SC_TWEAK_DEFAULTS) || {};
    Object.keys(defaults).forEach(k => setTweak(k, defaults[k]));
  };
  useEffectSC(() => {
    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>
  );
}

// ────────────────────────────────────────────────────────────────
// Brand switcher
// ────────────────────────────────────────────────────────────────
function BrandSwitcher({ allBrands, brandId, setBrandId, mobile }) {
  return (
    <div style={{ display: 'inline-flex', gap: 4, padding: 3, background: '#F4F2EC', borderRadius: 999 }}>
      {Object.values(allBrands).map(b => (
        <button key={b.id} onClick={() => setBrandId(b.id)} style={{
          background: brandId === b.id ? '#fff' : 'transparent',
          border: 0, borderRadius: 999,
          padding: mobile ? '5px 10px' : '6px 14px',
          fontSize: mobile ? 11 : 12, fontWeight: brandId === b.id ? 700 : 500,
          color: brandId === b.id ? b.ink : '#6E6E6A',
          boxShadow: brandId === b.id ? '0 1px 3px rgba(0,0,0,0.08)' : 'none',
          cursor: 'pointer',
        }}>{mobile ? b.name.split(' ')[0] : b.name}</button>
      ))}
    </div>
  );
}

// Expose for both standalone and framed renders
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.StepperMultiApp = StepperMultiApp;
const _stepperMultiRoot = document.getElementById('root');
if (_stepperMultiRoot && !_stepperMultiRoot.dataset.framed) {
  ReactDOM.createRoot(_stepperMultiRoot).render(<StepperMultiApp />);
}
