// variant-b-form.jsx
// Interactive booking-form state, dropdowns, calendar, time picker, submit flow.
// Loaded by variant-b.jsx — exposes BFormProvider/useBForm/BBookingCard/BSubmitModal/BSuccessCard to window.
//
// Design notes:
// • All state lives in a single React Context so multiple CTAs on the page (top-nav
//   "Book", services "Reserve", final-strip "Get my quote") can target the SAME form.
// • Smooth-scroll-to-form is global: window.scrollToBookingForm() — every CTA uses it.
// • Pricing is recomputed on every state change; no debouncing needed at this scale.
// • Dropdowns/pickers close on outside click and Escape.

// ── Pricing ─────────────────────────────────────────────────────────────────
const SVC_PRICING = {
  deep:      { hrs: 3, perBedHrs: 0.5, perBathHrs: 0.25, cleaners: 2 },
  apartment: { hrs: 2, perBedHrs: 0.5, perBathHrs: 0.25, cleaners: 2 },
  moveout:   { hrs: 4, perBedHrs: 0.75, perBathHrs: 0.5, cleaners: 2 },
  office:    { hrs: 3, perBedHrs: 0, perBathHrs: 0, cleaners: 2 },
};

const SVC_LABELS = {
  deep:      { name: 'Deep clean',  icon: 'sparkles' },
  apartment: { name: 'Apartment',   icon: 'home'     },
  moveout:   { name: 'Move-out',    icon: 'truck'    },
  office:    { name: 'Office',      icon: 'building' },
};

function calcQuote(service, beds, baths) {
  const s = SVC_PRICING[service];
  const extraBeds  = Math.max(0, beds  - 2);
  const extraBaths = Math.max(0, baths - 1);
  const hours = s.hrs + extraBeds * s.perBedHrs + extraBaths * s.perBathHrs;
  const roundedHours = Math.round(hours * 10) / 10;
  const cleaners = s.cleaners || 2;
  const price = Math.round(roundedHours * cleaners * SEVEN.hourlyRate);
  return { price, hours: roundedHours, cleaners, hourlyRate: SEVEN.hourlyRate };
}

const TIME_SLOTS = [
  { id: '7-9',   label: '7 – 9 AM',    note: 'Earliest' },
  { id: '9-11',  label: '9 – 11 AM',   note: 'Most popular' },
  { id: '11-13', label: '11 AM – 1 PM' },
  { id: '13-15', label: '1 – 3 PM' },
  { id: '15-17', label: '3 – 5 PM',    note: 'Last call' },
];

const WEB3FORMS_ACCESS_KEY = '7210d45d-a7c0-4c4c-990d-f1ee589cf6cb';
const DESKTOP_BOOKING_FONT_SCALE = 1.3;
function bookingFs(size) {
  return Math.round(size * DESKTOP_BOOKING_FONT_SCALE * 10) / 10;
}

// ── Form state context ──────────────────────────────────────────────────────

const BFormContext = React.createContext(null);

function nextThursday() {
  // Default date: next Thursday at least 48 hours out (matches the "within 48h" promise)
  const d = new Date();
  d.setDate(d.getDate() + ((4 - d.getDay() + 7) % 7 || 7));
  d.setHours(0, 0, 0, 0);
  return d;
}

function BFormProvider({ children }) {
  const [state, setState] = React.useState({
    service:  'deep',
    bedrooms: 2,
    bathrooms: 1,
    date:     nextThursday(),
    time:     '9-11',
    zip:      '',
    name:     '',
    phone:    '',
    email:    '',
    modalOpen: false,
    openSheet: null,   // mobile bottom-sheet: null | 'size' | 'date' | 'time'
    submitted: false,
    confirmationId: null,
    submitting: false,
    submitError: null,
  });

  const set = React.useCallback((key, value) => {
    setState(s => (typeof key === 'object' ? { ...s, ...key } : { ...s, [key]: value }));
  }, []);

  const quote = React.useMemo(
    () => calcQuote(state.service, state.bedrooms, state.bathrooms),
    [state.service, state.bedrooms, state.bathrooms]
  );

  const value = React.useMemo(() => ({ state, set, quote }), [state, set, quote]);
  return <BFormContext.Provider value={value}>{children}</BFormContext.Provider>;
}

function useBForm() {
  const ctx = React.useContext(BFormContext);
  if (!ctx) throw new Error('useBForm must be inside BFormProvider');
  return ctx;
}

// ── Scroll helper ───────────────────────────────────────────────────────────

function scrollToBookingForm() {
  const el = document.getElementById('booking-form');
  if (!el) return;
  const top = el.getBoundingClientRect().top + window.scrollY - 90;
  window.scrollTo({ top, behavior: 'smooth' });
  // Brief focus pulse so the user knows the form responded
  el.animate(
    [{ boxShadow: '0 0 0 0 rgba(126, 193, 56, 0.45)' }, { boxShadow: '0 0 0 14px rgba(126, 193, 56, 0)' }],
    { duration: 800, easing: 'ease-out' }
  );
}

function scrollToSection(id) {
  const el = document.getElementById(id);
  if (!el) return;
  const top = el.getBoundingClientRect().top + window.scrollY - 80;
  window.scrollTo({ top, behavior: 'smooth' });
}

// ── Outside-click hook ──────────────────────────────────────────────────────

function useOutsideClose(ref, onClose, active) {
  React.useEffect(() => {
    if (!active) return undefined;
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    const onKey  = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('mousedown', onDown);
    document.addEventListener('keydown',   onKey);
    return () => {
      document.removeEventListener('mousedown', onDown);
      document.removeEventListener('keydown',   onKey);
    };
  }, [active, onClose, ref]);
}

// ── Generic popover dropdown ────────────────────────────────────────────────

function BPopover({ trigger, children, width = 240, align = 'left', disabled = false }) {
  const [open, setOpen] = React.useState(false);
  const wrapRef = React.useRef(null);
  useOutsideClose(wrapRef, () => setOpen(false), open);

  return (
    <div ref={wrapRef} style={{ position: 'relative' }}>
      <div onClick={() => !disabled && setOpen(o => !o)} style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}>
        {trigger(disabled ? false : open)}
      </div>
      {open && !disabled && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 6px)',
          [align]: 0, width,
          background: B.card, border: `1px solid ${B.line}`, borderRadius: 4,
          boxShadow: '0 30px 60px -20px rgba(26,46,38,0.25), 0 2px 0 rgba(176,141,60,0.06)',
          padding: 8, zIndex: 40,
        }}>
          {typeof children === 'function' ? children({ close: () => setOpen(false) }) : children}
        </div>
      )}
    </div>
  );
}

// ── Field shell (used as popover trigger) ───────────────────────────────────

function BFieldShell({ icon, label, value, hint, active, disabled = false, onClick, style }) {
  return (
    <button onClick={onClick} type="button" disabled={disabled} style={{
      width: '100%', textAlign: 'left', background: B.card,
      border: `1px solid ${active ? B.green : B.line}`,
      borderRadius: 4, padding: '12px 14px',
      display: 'flex', alignItems: 'center', gap: 12,
      fontFamily: bSans, cursor: disabled ? 'not-allowed' : 'pointer',
      opacity: disabled ? 0.55 : 1,
      transition: 'border-color .15s, background .15s',
      ...style,
    }}>
      {icon && <Icon name={icon} size={18} sw={1.6} stroke={disabled ? B.muted : B.green} />}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontSize: bookingFs(10.5), color: B.brassDeep, fontWeight: 700,
          letterSpacing: 1.4, textTransform: 'uppercase',
        }}>{label}</div>
        <div style={{
          fontSize: bookingFs(14), color: B.ink, fontWeight: 700, marginTop: 2,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{value}</div>
        {hint && <div style={{ fontSize: bookingFs(11), color: B.muted, marginTop: 2 }}>{hint}</div>}
      </div>
      {!disabled && <Icon name="chevron-down" size={14} sw={2} stroke={B.muted} />}
    </button>
  );
}

// ── Service tabs ────────────────────────────────────────────────────────────

function BServiceTabs() {
  const { state, set } = useBForm();
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 6, marginBottom: 16 }}>
      {Object.entries(SVC_LABELS).map(([k, t]) => {
        const on = state.service === k;
        return (
          <button key={k} type="button" onClick={() => set('service', k)} style={{
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6,
            padding: '14px 4px', borderRadius: 4,
            border: `1px solid ${on ? B.green : B.line}`,
            background: on ? 'color-mix(in srgb, var(--p7-primary) 12%, transparent)' : B.card,
            color: on ? B.green : B.ink2,
            fontFamily: bSans, fontWeight: 700, fontSize: bookingFs(11.5), letterSpacing: 0.2,
            cursor: 'pointer', transition: 'all .15s',
          }}>
            <Icon name={t.icon} size={20} sw={1.6} />
            {t.name}
          </button>
        );
      })}
    </div>
  );
}

// ── Numeric stepper (used inside Bedrooms/Bathrooms popover) ────────────────

function BStepper({ label, value, onChange, min = 0, max = 10 }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: '10px 12px', borderRadius: 4,
      transition: 'background .15s',
    }}
    onMouseEnter={(e) => e.currentTarget.style.background = B.bg}
    onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
      <div>
        <div style={{ fontFamily: bSans, fontSize: 13, color: B.ink, fontWeight: 600 }}>{label}</div>
      </div>
      <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
        <button type="button" onClick={() => onChange(Math.max(min, value - 1))} aria-label="decrease" style={{
          width: 28, height: 28, borderRadius: '50%', border: `1px solid ${B.line}`, background: '#fff',
          cursor: value > min ? 'pointer' : 'not-allowed', color: value > min ? B.green : B.muted,
          display: 'grid', placeItems: 'center',
        }}>
          <Icon name="minus" size={12} sw={2.4} />
        </button>
        <span style={{
          fontFamily: bDisplay, fontSize: 20, color: B.ink, minWidth: 22, textAlign: 'center', fontWeight: 700,
        }}>{value}</span>
        <button type="button" onClick={() => onChange(Math.min(max, value + 1))} aria-label="increase" style={{
          width: 28, height: 28, borderRadius: '50%', border: `1px solid ${B.green}`, background: 'color-mix(in srgb, var(--p7-primary) 12%, transparent)',
          cursor: value < max ? 'pointer' : 'not-allowed', color: B.green,
          display: 'grid', placeItems: 'center',
        }}>
          <Icon name="plus" size={12} sw={2.4} />
        </button>
      </div>
    </div>
  );
}

// ── Date picker (mini month grid) ───────────────────────────────────────────

const DAY_NAMES = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June',
                     'July', 'August', 'September', 'October', 'November', 'December'];

function formatDate(d) {
  const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  return `${days[d.getDay()]}, ${months[d.getMonth()]} ${d.getDate()}`;
}

function sameDay(a, b) {
  return a && b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}

function BDatePicker({ value, onSelect, close }) {
  const today = React.useMemo(() => { const d = new Date(); d.setHours(0,0,0,0); return d; }, []);
  const [viewMonth, setViewMonth] = React.useState(() => new Date(value.getFullYear(), value.getMonth(), 1));

  const firstDay = new Date(viewMonth.getFullYear(), viewMonth.getMonth(), 1);
  const daysInMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + 1, 0).getDate();
  const startCol = firstDay.getDay();

  const cells = [];
  for (let i = 0; i < startCol; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(viewMonth.getFullYear(), viewMonth.getMonth(), d));
  while (cells.length % 7 !== 0) cells.push(null);

  const navMonth = (delta) => {
    setViewMonth(new Date(viewMonth.getFullYear(), viewMonth.getMonth() + delta, 1));
  };

  return (
    <div style={{ fontFamily: bSans, padding: 6 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10, padding: '0 4px' }}>
        <button type="button" onClick={() => navMonth(-1)} style={{
          width: 28, height: 28, border: 'none', background: 'transparent',
          cursor: 'pointer', color: B.ink, borderRadius: 4,
        }} aria-label="previous month">
          <Icon name="chevron-down" size={16} sw={2} style={{ transform: 'rotate(90deg)' }} />
        </button>
        <div style={{ fontFamily: bDisplay, fontSize: 16, fontWeight: 700, color: B.ink, letterSpacing: -0.3 }}>
          {MONTH_NAMES[viewMonth.getMonth()]} {viewMonth.getFullYear()}
        </div>
        <button type="button" onClick={() => navMonth(1)} style={{
          width: 28, height: 28, border: 'none', background: 'transparent',
          cursor: 'pointer', color: B.ink, borderRadius: 4,
        }} aria-label="next month">
          <Icon name="chevron-down" size={16} sw={2} style={{ transform: 'rotate(-90deg)' }} />
        </button>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 2, marginBottom: 4 }}>
        {DAY_NAMES.map((d, i) => (
          <div key={i} style={{
            textAlign: 'center', fontSize: 10, color: B.muted, fontWeight: 600,
            letterSpacing: 0.6, textTransform: 'uppercase', padding: '4px 0',
          }}>{d}</div>
        ))}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 2 }}>
        {cells.map((d, i) => {
          if (!d) return <div key={i}/>;
          const isPast = d < today;
          const isToday = sameDay(d, today);
          const isSel = sameDay(d, value);
          return (
            <button key={i} type="button" disabled={isPast}
              onClick={() => { onSelect(d); close && close(); }}
              style={{
                aspectRatio: '1/1', border: 'none',
                background: isSel ? B.green : isToday ? B.bg : 'transparent',
                color: isSel ? '#fff' : isPast ? B.line : B.ink,
                cursor: isPast ? 'not-allowed' : 'pointer',
                fontFamily: bSans, fontSize: 13, fontWeight: isSel ? 700 : 500,
                borderRadius: 4, position: 'relative',
                transition: 'background .12s',
              }}
              onMouseEnter={(e) => { if (!isPast && !isSel) e.currentTarget.style.background = 'color-mix(in srgb, var(--p7-primary) 12%, transparent)'; }}
              onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.background = isToday ? B.bg : 'transparent'; }}
            >
              {d.getDate()}
              {isToday && !isSel && <span style={{
                position: 'absolute', bottom: 3, left: '50%', transform: 'translateX(-50%)',
                width: 4, height: 4, borderRadius: '50%', background: B.green,
              }}/>}
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ── Time picker ─────────────────────────────────────────────────────────────

function BTimePicker({ value, onSelect, close }) {
  return (
    <div style={{ display: 'grid', gap: 2 }}>
      {TIME_SLOTS.map(s => {
        const on = s.id === value;
        return (
          <button key={s.id} type="button"
            onClick={() => { onSelect(s.id); close && close(); }}
            style={{
              display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              padding: '10px 12px', borderRadius: 4,
              background: on ? 'color-mix(in srgb, var(--p7-primary) 14%, transparent)' : 'transparent',
              border: 'none', cursor: 'pointer', fontFamily: bSans,
              color: on ? B.green : B.ink, transition: 'background .12s',
            }}
            onMouseEnter={(e) => { if (!on) e.currentTarget.style.background = B.bg; }}
            onMouseLeave={(e) => { if (!on) e.currentTarget.style.background = 'transparent'; }}
          >
            <span style={{ fontSize: 14, fontWeight: 600 }}>{s.label}</span>
            {s.note && (
              <span style={{
                fontSize: 10.5, fontWeight: 700, letterSpacing: 0.8, textTransform: 'uppercase',
                color: on ? B.green : B.brassDeep,
              }}>{s.note}</span>
            )}
            {on && <Icon name="check" size={14} sw={2.4} stroke={B.green} />}
          </button>
        );
      })}
    </div>
  );
}

function timeLabel(id) {
  return (TIME_SLOTS.find(s => s.id === id) || TIME_SLOTS[1]).label;
}

function buildLeadPayload(state, quote, source) {
  const service = SVC_LABELS[state.service]?.name || state.service;
  const homeSize = state.service === 'office'
    ? 'Not needed for office'
    : `${state.bedrooms} bed${state.bedrooms !== 1 ? 's' : ''}, ${state.bathrooms} bath${state.bathrooms !== 1 ? 's' : ''}`;
  const preferredDate = formatDate(state.date);
  const preferredTime = timeLabel(state.time);
  return {
    access_key: WEB3FORMS_ACCESS_KEY,
    subject: 'New 7 Seven Cleaning quote request',
    from_name: '7 Seven Cleaning Website',
    name: state.name.trim(),
    phone: state.phone.trim(),
    email: state.email.trim(),
    service,
    home_size: homeSize,
    preferred_date: preferredDate,
    preferred_time: preferredTime,
    zip_code: state.zip || 'Not provided',
    estimated_total: `$${quote.price}`,
    estimated_hours: quote.hours,
    cleaners: quote.cleaners,
    hourly_rate: `$${quote.hourlyRate}/cleaner/hour`,
    source,
    page_url: window.location.href,
    message: [
      `Service: ${service}`,
      `Home size: ${homeSize}`,
      `Preferred date: ${preferredDate}`,
      `Preferred time: ${preferredTime}`,
      `ZIP: ${state.zip || 'Not provided'}`,
      `Estimate: $${quote.price} (${quote.hours} hrs x ${quote.cleaners} cleaners x $${quote.hourlyRate}/hr)`,
    ].join('\n'),
  };
}

async function submitLeadToWeb3Forms(state, quote, source) {
  const response = await fetch('https://api.web3forms.com/submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify(buildLeadPayload(state, quote, source)),
  });
  const result = await response.json().catch(() => ({}));
  if (!response.ok || result.success === false) {
    throw new Error(result.message || 'Could not send your quote request. Please call us instead.');
  }
  return result;
}

function trackLeadConversion(state, quote, source) {
  if (typeof window.gtag !== 'function') return;
  window.gtag('event', 'generate_lead', {
    currency: 'USD',
    value: quote.price,
    service_type: SVC_LABELS[state.service]?.name || state.service,
    lead_source: source,
  });
}

// ── Size picker (bedrooms + bathrooms in one popover) ───────────────────────

function BSizePicker({ close }) {
  const { state, set } = useBForm();
  return (
    <div style={{ display: 'grid', gap: 4 }}>
      <div style={{
        fontFamily: bSans, fontSize: 10.5, color: B.brassDeep, fontWeight: 700,
        letterSpacing: 1.4, textTransform: 'uppercase', padding: '4px 8px',
      }}>How big is your space?</div>
      <BStepper label="Bedrooms" value={state.bedrooms}  onChange={(v) => set('bedrooms', v)}  min={0} max={8} />
      <BStepper label="Bathrooms" value={state.bathrooms} onChange={(v) => set('bathrooms', v)} min={1} max={6} />
      <button type="button" onClick={close} style={{
        margin: '6px 8px 4px', padding: '10px 14px',
        background: B.green, color: '#fff', border: 'none', borderRadius: 4,
        fontFamily: bSans, fontWeight: 700, fontSize: 13, cursor: 'pointer',
      }}>Done</button>
    </div>
  );
}

// ── Booking card (the heart of the prototype) ───────────────────────────────

function BBookingCard({ embedded = false }) {
  const { state, set, quote } = useBForm();

  const isOffice = state.service === 'office';
  const sizeValue = isOffice ? 'Not needed for office' : `${state.bedrooms} bed${state.bedrooms !== 1 ? 's' : ''} · ${state.bathrooms} bath${state.bathrooms !== 1 ? 's' : ''}`;
  const dateValue = formatDate(state.date);
  const timeValue = timeLabel(state.time);

  // Form validity for the CTA: ZIP must be 5 digits (loose check; full check on submit)
  const zipOk = /^\d{5}$/.test(state.zip);

  const handleSubmit = (e) => {
    if (e) e.preventDefault();
    if (!zipOk) {
      // Highlight zip — focus it
      const zipEl = document.getElementById('booking-zip');
      if (zipEl) { zipEl.focus(); zipEl.scrollIntoView && null; }
      return;
    }
    set('modalOpen', true);
  };

  return (
    <form id="booking-form" onSubmit={handleSubmit} style={{
      background: B.card, borderRadius: 6, padding: 32,
      border: `1px solid ${B.line}`,
      boxShadow: '0 40px 80px -40px rgba(26,46,38,0.18), 0 1px 0 rgba(176,141,60,0.08)',
      alignSelf: 'start',
      position: 'relative',
      scrollMarginTop: 90,
    }}>
      {/* Brass corner accent */}
      <div style={{
        position: 'absolute', top: -1, right: -1, width: 36, height: 36,
        borderTop: `2px solid ${B.brass}`, borderRight: `2px solid ${B.brass}`,
        pointerEvents: 'none',
      }}/>

      {/* Header */}
      <div style={{ marginBottom: 22 }}>
        <div style={{
          fontFamily: bSans, fontSize: bookingFs(10.5), letterSpacing: 1.6, textTransform: 'uppercase',
          color: B.brassDeep, fontWeight: 700, marginBottom: 8,
          display: 'inline-flex', alignItems: 'center', gap: 8,
        }}>
          <span style={{ width: 22, height: 1, background: B.brass }}/>
          Reservations
        </div>
        <div style={{ fontFamily: bDisplay, fontSize: bookingFs(26), fontWeight: 800, color: B.ink, letterSpacing: -0.5, lineHeight: 1.15 }}>
          Get your free quote
        </div>
        <div style={{ fontFamily: bSans, fontSize: bookingFs(13), color: B.muted, marginTop: 4 }}>
          ~60 seconds. We'll text you a flat-rate estimate.
        </div>
      </div>

      <BServiceTabs />

      {/* Size + Date + Time */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 10 }}>
        <BPopover width={260} disabled={isOffice} trigger={(open) => (
          <BFieldShell icon="home" label="Home size" value={sizeValue} active={open} disabled={isOffice} />
        )}>
          {({ close }) => <BSizePicker close={close} />}
        </BPopover>
        <BPopover width={290} trigger={(open) => (
          <BFieldShell icon="calendar" label="Preferred date" value={dateValue} active={open} />
        )}>
          {({ close }) => <BDatePicker value={state.date} onSelect={(d) => set('date', d)} close={close} />}
        </BPopover>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 10 }}>
        <BPopover width={240} trigger={(open) => (
          <BFieldShell icon="clock" label="Time window" value={timeValue} active={open} />
        )}>
          {({ close }) => <BTimePicker value={state.time} onSelect={(t) => set('time', t)} close={close} />}
        </BPopover>
        <div style={{
          border: `1px solid ${zipOk ? B.green : (state.zip && !zipOk ? '#C04D2F' : B.line)}`,
          borderRadius: 4, padding: '8px 14px',
          background: B.card, display: 'flex', alignItems: 'center', gap: 10,
          transition: 'border-color .15s',
        }}>
          <Icon name="pin" size={18} sw={1.6} stroke={B.green} />
          <div style={{ flex: 1, display: 'grid' }}>
            <label htmlFor="booking-zip" style={{
              fontFamily: bSans, fontSize: bookingFs(10.5), color: B.brassDeep, fontWeight: 700,
              letterSpacing: 1.4, textTransform: 'uppercase',
            }}>ZIP code</label>
            <input
              id="booking-zip"
              type="text"
              inputMode="numeric"
              maxLength={5}
              placeholder=""
              value={state.zip}
              onChange={(e) => set('zip', e.target.value.replace(/[^\d]/g, '').slice(0, 5))}
              style={{
                border: 'none', outline: 'none', background: 'transparent',
                fontFamily: bSans, fontSize: bookingFs(14), color: B.ink, fontWeight: 700,
                padding: 0, width: '100%',
              }}
            />
          </div>
        </div>
      </div>

      {/* Estimated total */}
      <div style={{
        marginTop: 14, padding: '16px 18px', background: B.bg, borderRadius: 4,
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        border: `1px solid ${B.line}`, gap: 12,
      }}>
        <div style={{ minWidth: 0 }}>
          <div style={{ fontFamily: bSans, fontSize: bookingFs(10.5), color: B.brassDeep, fontWeight: 700, letterSpacing: 1.4, textTransform: 'uppercase' }}>
            Estimated total
          </div>
          <div style={{ fontFamily: bDisplay, fontSize: bookingFs(30), fontWeight: 800, color: B.ink, letterSpacing: -0.6, lineHeight: 1.1, marginTop: 2 }}>
            ${quote.price}
            <span style={{ fontFamily: bSans, fontSize: bookingFs(12.5), color: B.muted, fontWeight: 600, marginLeft: 8 }}>
              · {quote.hours} hrs · {quote.cleaners} cleaners
            </span>
          </div>
        </div>
        <span style={{
          fontFamily: bSans, fontSize: bookingFs(10.5), letterSpacing: 1.4, textTransform: 'uppercase',
          color: B.brassDeep, fontWeight: 700,
          padding: '6px 10px', border: `1px solid ${B.brass}`, borderRadius: 999, whiteSpace: 'nowrap',
        }}>Flat rate</span>
      </div>

      <button type="submit" style={{
        width: '100%', marginTop: 16, padding: '18px 20px',
        background: B.green, color: '#fff', border: 'none', borderRadius: 4,
        fontFamily: bSans, fontSize: bookingFs(15), fontWeight: 800, letterSpacing: 0.2,
        cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 10,
        boxShadow: '0 20px 40px -20px color-mix(in srgb, var(--p7-primary) 60%, transparent)',
        transition: ROLLOVER_TRANSITION,
      }}
      {...rollover(
        { transform: 'translateY(-2px)', background: B.greenDark, boxShadow: '0 24px 42px -22px color-mix(in srgb, var(--p7-primary) 78%, transparent)' },
        { transform: 'translateY(0)', background: B.green, boxShadow: '0 20px 40px -20px color-mix(in srgb, var(--p7-primary) 60%, transparent)' }
      )}
      >
        See available times
        <Icon name="arrow-right" size={18} sw={2.2} />
      </button>

      <div style={{
        marginTop: 14, display: 'flex', alignItems: 'center', justifyContent: 'center',
        gap: 16, fontFamily: bSans, fontSize: bookingFs(12), color: B.muted,
      }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          <Icon name="shield" size={13} sw={1.8} stroke={B.muted}/>
          Free cancellation
        </span>
        <span style={{ width: 4, height: 4, borderRadius: '50%', background: B.line }}/>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          <Icon name="check" size={13} sw={2.2} stroke={B.green}/>
          24h re-clean
        </span>
      </div>
    </form>
  );
}

// ── Submit modal (collects name/phone after the quote) ──────────────────────

function BSubmitModal() {
  const { state, set, quote } = useBForm();
  if (!state.modalOpen) return null;

  const close = () => set('modalOpen', false);

  const phoneOk = /^\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/.test(state.phone.replace(/\s/g, ''));
  const nameOk  = state.name.trim().length >= 2;
  const canSubmit = phoneOk && nameOk && !state.submitting;

  const onSubmit = async (e) => {
    e.preventDefault();
    if (!canSubmit) return;
    set({ submitting: true, submitError: null });
    try {
      await submitLeadToWeb3Forms(state, quote, 'Desktop booking modal');
      trackLeadConversion(state, quote, 'Desktop booking modal');
      const id = 'SEV-' + Math.floor(Math.random() * 9000 + 1000);
      set({ submitted: true, modalOpen: false, confirmationId: id, submitting: false, submitError: null });
      // Scroll to top of form so success card is visible
      setTimeout(() => scrollToBookingForm(), 100);
    } catch (err) {
      set({ submitting: false, submitError: err.message || 'Could not send your quote request. Please call us instead.' });
    }
  };

  return (
    <div onClick={close} style={{
      position: 'fixed', inset: 0, zIndex: 100, background: 'rgba(20,30,25,0.55)',
      backdropFilter: 'blur(6px)', display: 'grid', placeItems: 'center',
      animation: 'b-fade-in .2s ease-out',
    }}>
      <style>{`@keyframes b-fade-in { from { opacity: 0 } to { opacity: 1 } }
                @keyframes b-pop-in  { from { transform: translateY(12px) scale(.96); opacity: 0 } to { transform: translateY(0) scale(1); opacity: 1 } }`}</style>
      <form onClick={(e) => e.stopPropagation()} onSubmit={onSubmit} style={{
        width: 'min(520px, calc(100vw - 32px))',
        background: B.card, borderRadius: 6, padding: 36, position: 'relative',
        border: `1px solid ${B.line}`, fontFamily: bSans,
        boxShadow: '0 60px 120px -40px rgba(20,30,25,0.5)',
        animation: 'b-pop-in .25s ease-out',
      }}>
        <button type="button" onClick={close} aria-label="close" style={{
          position: 'absolute', top: 16, right: 16, width: 32, height: 32, borderRadius: '50%',
          border: `1px solid ${B.line}`, background: '#fff', cursor: 'pointer', color: B.muted,
          display: 'grid', placeItems: 'center', fontSize: 16, fontWeight: 600,
        }}>×</button>

        <div style={{
          fontFamily: bSans, fontSize: 10.5, letterSpacing: 1.6, textTransform: 'uppercase',
          color: B.brassDeep, fontWeight: 700, marginBottom: 10,
          display: 'inline-flex', alignItems: 'center', gap: 8,
        }}>
          <span style={{ width: 22, height: 1, background: B.brass }}/>
          One last step
        </div>
        <div style={{ fontFamily: bDisplay, fontSize: 30, fontWeight: 700, color: B.ink, letterSpacing: -0.6, lineHeight: 1.15, marginBottom: 6 }}>
          Where should we text your quote?
        </div>
        <div style={{ fontSize: 13.5, color: B.muted, marginBottom: 22, lineHeight: 1.5 }}>
          We'll send your <strong style={{ color: B.ink }}>${quote.price}</strong> estimate and available time slots within an hour. No marketing, ever.
        </div>

        <div style={{ display: 'grid', gap: 10 }}>
          <label style={{ display: 'block' }}>
            <div style={{ fontSize: 10.5, color: B.brassDeep, fontWeight: 700, letterSpacing: 1.4, textTransform: 'uppercase', marginBottom: 6 }}>Your name</div>
            <input value={state.name} onChange={(e) => set('name', e.target.value)}
              placeholder="Jane Smith" autoFocus
              style={{
                width: '100%', padding: '14px 16px', borderRadius: 4,
                border: `1px solid ${B.line}`, fontFamily: bSans, fontSize: 15,
                color: B.ink, background: B.card, outline: 'none',
              }}
              onFocus={(e) => e.target.style.borderColor = B.green}
              onBlur={(e) => e.target.style.borderColor = B.line}
            />
          </label>
          <label style={{ display: 'block' }}>
            <div style={{ fontSize: 10.5, color: B.brassDeep, fontWeight: 700, letterSpacing: 1.4, textTransform: 'uppercase', marginBottom: 6 }}>Mobile phone</div>
            <input value={state.phone} onChange={(e) => set('phone', e.target.value)}
              placeholder="(360) 555-0142" inputMode="tel"
              style={{
                width: '100%', padding: '14px 16px', borderRadius: 4,
                border: `1px solid ${B.line}`, fontFamily: bSans, fontSize: 15,
                color: B.ink, background: B.card, outline: 'none',
              }}
              onFocus={(e) => e.target.style.borderColor = B.green}
              onBlur={(e) => e.target.style.borderColor = B.line}
            />
          </label>
          <label style={{ display: 'block' }}>
            <div style={{ fontSize: 10.5, color: B.brassDeep, fontWeight: 700, letterSpacing: 1.4, textTransform: 'uppercase', marginBottom: 6 }}>Email (optional)</div>
            <input value={state.email} onChange={(e) => set('email', e.target.value)}
              placeholder="you@example.com" inputMode="email"
              style={{
                width: '100%', padding: '14px 16px', borderRadius: 4,
                border: `1px solid ${B.line}`, fontFamily: bSans, fontSize: 15,
                color: B.ink, background: B.card, outline: 'none',
              }}
              onFocus={(e) => e.target.style.borderColor = B.green}
              onBlur={(e) => e.target.style.borderColor = B.line}
            />
          </label>
        </div>

        {state.submitError && (
          <div style={{ marginTop: 12, color: '#C04D2F', fontSize: 12.5, lineHeight: 1.45 }}>
            {state.submitError}
          </div>
        )}

        <button type="submit" disabled={!canSubmit} style={{
          width: '100%', marginTop: 18, padding: '16px 20px',
          background: canSubmit ? B.green : B.line, color: '#fff',
          border: 'none', borderRadius: 4, cursor: canSubmit ? 'pointer' : 'not-allowed',
          fontFamily: bSans, fontSize: 15, fontWeight: 700, letterSpacing: 0.2,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 10,
          transition: 'background .15s',
        }}>
          {state.submitting ? 'Sending...' : 'Send my quote'}
          <Icon name="arrow-right" size={16} sw={2.2}/>
        </button>

        <div style={{ marginTop: 14, textAlign: 'center', fontSize: 11.5, color: B.muted }}>
          By submitting you agree to receive a text from us. Reply STOP anytime.
        </div>
      </form>
    </div>
  );
}

// ── Success card (replaces the form after submit) ───────────────────────────

function BSuccessCard() {
  const { state, set, quote } = useBForm();
  const reset = () => set({
    submitted: false, name: '', phone: '', email: '', confirmationId: null,
  });

  return (
    <div id="booking-form" style={{
      background: B.card, borderRadius: 6, padding: 40,
      border: `1px solid ${B.green}`,
      boxShadow: '0 40px 80px -40px rgba(126,193,56,0.30), 0 0 0 4px color-mix(in srgb, var(--p7-primary) 8%, transparent)',
      position: 'relative', scrollMarginTop: 90,
    }}>
      <div style={{
        position: 'absolute', top: -1, right: -1, width: 36, height: 36,
        borderTop: `2px solid ${B.brass}`, borderRight: `2px solid ${B.brass}`,
        pointerEvents: 'none',
      }}/>

      <div style={{
        width: 64, height: 64, borderRadius: '50%',
        background: 'color-mix(in srgb, var(--p7-primary) 18%, transparent)',
        color: B.green, display: 'grid', placeItems: 'center', marginBottom: 20,
      }}>
        <Icon name="check-circle" size={36} sw={1.8}/>
      </div>

      <div style={{
        fontFamily: bSans, fontSize: 10.5, letterSpacing: 1.6, textTransform: 'uppercase',
        color: B.brassDeep, fontWeight: 700, marginBottom: 8,
        display: 'inline-flex', alignItems: 'center', gap: 8,
      }}>
        <span style={{ width: 22, height: 1, background: B.brass }}/>
        Confirmation {state.confirmationId}
      </div>
      <div style={{ fontFamily: bDisplay, fontSize: 34, fontWeight: 700, color: B.ink, letterSpacing: -0.8, lineHeight: 1.1, marginBottom: 10 }}>
        Thanks, {state.name.split(' ')[0] || 'friend'} — we'll text you within an hour.
      </div>
      <div style={{ fontFamily: bSans, fontSize: 14.5, color: B.ink2, lineHeight: 1.6, marginBottom: 24 }}>
        Your quote of <strong style={{ color: B.ink }}>${quote.price}</strong> for a {SVC_LABELS[state.service].name.toLowerCase()} on <strong style={{ color: B.ink }}>{formatDate(state.date)}</strong> ({timeLabel(state.time)}) is on its way to <strong style={{ color: B.ink }}>{state.phone}</strong>.
      </div>

      <div style={{
        background: B.bg, borderRadius: 4, padding: '18px 20px', marginBottom: 22,
        display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 16,
        border: `1px solid ${B.line}`,
      }}>
        {[
          { l: 'Reply time', v: '< 1 hour', icon: 'clock' },
          { l: 'No payment yet', v: 'Pay after clean', icon: 'shield' },
          { l: 'Re-clean policy', v: '24h guarantee', icon: 'check-circle' },
        ].map((it, i) => (
          <div key={i}>
            <Icon name={it.icon} size={18} sw={1.6} stroke={B.green}/>
            <div style={{ fontFamily: bSans, fontSize: 11, color: B.brassDeep, fontWeight: 700, letterSpacing: 1.3, textTransform: 'uppercase', marginTop: 8 }}>{it.l}</div>
            <div style={{ fontFamily: bSans, fontSize: 13.5, color: B.ink, fontWeight: 600, marginTop: 2 }}>{it.v}</div>
          </div>
        ))}
      </div>

      <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
        <a href={SEVEN.phoneHref} style={{
          padding: '14px 22px', background: B.green, color: '#fff',
          borderRadius: 4, textDecoration: 'none', fontFamily: bSans,
          fontSize: 14, fontWeight: 700, letterSpacing: 0.2,
          display: 'inline-flex', alignItems: 'center', gap: 8,
        }}>
          <Icon name="phone" size={14} sw={2}/> Call us now
        </a>
        <button type="button" onClick={reset} style={{
          padding: '14px 22px', background: 'transparent', color: B.ink,
          border: `1px solid ${B.line}`, borderRadius: 4, cursor: 'pointer',
          fontFamily: bSans, fontSize: 14, fontWeight: 600,
        }}>Book another quote</button>
      </div>
    </div>
  );
}

// ── Sticky bottom CTA bar (mobile-style, useful on long page) ───────────────

function BStickyCTA() {
  const [shown, setShown] = React.useState(false);
  const { quote, state } = useBForm();

  React.useEffect(() => {
    const onScroll = () => {
      const trigger = document.getElementById('booking-form');
      if (!trigger) return;
      const r = trigger.getBoundingClientRect();
      // Show once hero scrolls off-screen; hide when the form is back in view
      const heroPassed = window.scrollY > 600;
      const formInView = r.top < window.innerHeight && r.bottom > 0;
      setShown(heroPassed && !formInView);
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  if (state.submitted) return null;

  return (
    <div style={{
      position: 'fixed', bottom: shown ? 16 : -120, left: '50%',
      transform: 'translateX(-50%)', zIndex: 60,
      transition: 'bottom .35s cubic-bezier(.4,.0,.2,1)',
      background: B.ink, color: '#fff', borderRadius: 6, padding: '12px 12px 12px 22px',
      display: 'flex', alignItems: 'center', gap: 18,
      boxShadow: '0 30px 60px -20px rgba(20,30,25,0.45)',
      fontFamily: bSans, minWidth: 'min(720px, calc(100vw - 32px))',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        <div style={{
          width: 36, height: 36, borderRadius: 4,
          background: 'color-mix(in srgb, var(--p7-primary) 30%, transparent)',
          color: B.green, display: 'grid', placeItems: 'center',
        }}>
          <Icon name="sparkles" size={18} sw={1.8}/>
        </div>
        <div>
          <div style={{ fontSize: 11, color: B.brass, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase' }}>
            Your estimate
          </div>
          <div style={{ fontFamily: bDisplay, fontSize: 22, fontWeight: 700, letterSpacing: -0.4 }}>
            ${quote.price} <span style={{ fontFamily: bSans, fontSize: 12, color: 'rgba(255,255,255,0.6)', fontWeight: 500 }}>
              · {SVC_LABELS[state.service].name}
            </span>
          </div>
        </div>
      </div>
      <div style={{ width: 1, height: 32, background: 'rgba(255,255,255,0.15)' }}/>
      <a href={SEVEN.phoneHref} style={{
        color: '#fff', textDecoration: 'none', fontSize: 14, fontWeight: 600,
        display: 'inline-flex', alignItems: 'center', gap: 8,
      }}>
        <Icon name="phone" size={14} sw={2} stroke={B.brass}/> {SEVEN.phone}
      </a>
      <button type="button" onClick={scrollToBookingForm} style={{
        marginLeft: 'auto', padding: '12px 22px', background: B.green, color: '#fff',
        border: 'none', borderRadius: 4, fontFamily: bSans, fontSize: 14, fontWeight: 700,
        cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 8,
        letterSpacing: 0.2, transition: ROLLOVER_TRANSITION,
      }}
      {...rollover(
        { transform: 'translateY(-2px)', background: B.greenDark, boxShadow: '0 14px 26px -18px rgba(0,0,0,0.45)' },
        { transform: 'translateY(0)', background: B.green, boxShadow: 'none' }
      )}>
        Continue booking <Icon name="arrow-right" size={14} sw={2.4}/>
      </button>
    </div>
  );
}

// ── Booking form OR success card, depending on state ────────────────────────

function BBookingPanel() {
  const { state } = useBForm();
  return state.submitted ? <BSuccessCard /> : <BBookingCard />;
}

// Expose globals
Object.assign(window, {
  BFormContext, BFormProvider, useBForm,
  BBookingCard, BBookingPanel, BSubmitModal, BSuccessCard, BStickyCTA,
  BSizePicker, BDatePicker, BTimePicker, BServiceTabs, BStepper, BPopover, BFieldShell,
  scrollToBookingForm, scrollToSection,
  SVC_LABELS, SVC_PRICING, TIME_SLOTS, calcQuote, formatDate, timeLabel,
  WEB3FORMS_ACCESS_KEY, buildLeadPayload, submitLeadToWeb3Forms, trackLeadConversion,
  nextThursday, sameDay,
});
