// ═══════════════════════════════════════════════════════════════════════
// Dyra Mobile — Listing Detail + Booking Flow
// ═══════════════════════════════════════════════════════════════════════

const PM2 = DYRA_MOBILE_PALETTE;
const TM2 = DYRA_MOBILE_TYPE;

// ═══════════════════════════════════════════════════════════════════════
// LISTING DETAIL
// ═══════════════════════════════════════════════════════════════════════
function MListing({ propertyId, go, openMenu }) {
  const [scrolled, setScrolled] = React.useState(false);
  const [photoIdx, setPhotoIdx] = React.useState(0);
  const [galleryOpen, setGalleryOpen] = React.useState(false);
  const [showAllAmenities, setShowAllAmenities] = React.useState(false);
  const props = window.DyraStore?.state?.properties || window.DYRA?.PROPERTIES || [];
  // Match by id OR slug so deep-links like /#listing:presidential resolve
  // (the /presidential landing CTA passes the slug, not the UUID).
  const p = props.find(x => x.id === propertyId || x.slug === propertyId) || props[0];
  const photos = p.photos || p.gallery?.map((g, i) => ({ src: null, caption: g })) || [];
  const photoCount = p.photos?.length || p.gallery?.length || 1;

  // ── Date persistence ──────────────────────────────────────────────────
  // Read dates + guest count out of the URL ("#listing:slug?checkIn=…")
  // and fall back to whatever was set on the homepage search. The user
  // can override either using the inline picker below.
  const initialSearch = React.useMemo(() => {
    const h = window.location.hash || '';
    const qi = h.indexOf('?');
    let urlIn = '', urlOut = '', urlGuests = 0;
    if (qi >= 0) {
      const sp = new URLSearchParams(h.slice(qi+1));
      urlIn = sp.get('checkIn') || '';
      urlOut = sp.get('checkOut') || '';
      urlGuests = Number(sp.get('guests') || 0) || 0;
    }
    let sticky = null;
    try { sticky = JSON.parse(sessionStorage.getItem('dyra.search') || 'null'); } catch (e) {}
    const stickyTotal = (sticky?.guests?.adults || 0) + (sticky?.guests?.children || 0);
    return {
      checkIn:  urlIn  || sticky?.dates?.checkIn  || '',
      checkOut: urlOut || sticky?.dates?.checkOut || '',
      guests:   urlGuests || stickyTotal || 1,
    };
  }, []);
  const [pickerOpen, setPickerOpen] = React.useState(false);
  const [dates, setDates] = React.useState({ checkIn: initialSearch.checkIn, checkOut: initialSearch.checkOut });
  const [guests, setGuests] = React.useState(initialSearch.guests);
  // Friendly labels for the sticky reserve bar
  const datesLabel = (dates.checkIn && dates.checkOut)
    ? (() => {
        const a = new Date(dates.checkIn + 'T00:00:00');
        const b = new Date(dates.checkOut + 'T00:00:00');
        const fmt = (d) => d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
        const nights = Math.max(1, Math.round((b - a) / 86400000));
        return `${fmt(a)} – ${fmt(b)} · ${nights} night${nights === 1 ? '' : 's'}`;
      })()
    : 'Pick your dates';

  // Deep-link from external pages (e.g. /presidential landing page) can set
  // ?focus=dates to scroll the inline date picker into view on mount.
  React.useEffect(() => {
    try {
      const sp = new URLSearchParams(window.location.search || '');
      if (sp.get('focus') !== 'dates') return;
      const t = setTimeout(() => {
        const el = document.getElementById('dates-section');
        if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }, 250);
      return () => clearTimeout(t);
    } catch (e) {}
  }, []);

  return (
    <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: PM2.bg, position: 'relative' }}>
      <div onScroll={(e) => setScrolled(e.target.scrollTop > 240)}
        style={{ flex: 1, overflow: 'auto', WebkitOverflowScrolling: 'touch' }}>

        {/* sticky transparent app bar — overlays photo */}
        <div style={{ position: 'sticky', top: 0, zIndex: 50, marginBottom: -56 }}>
          <MAppBar
            transparent scrolled={scrolled}
            title={p.name}
            leading={<MIconBtn label="Back" onClick={() => go('browse')}>
              <span style={{
                width: 36, height: 36, borderRadius: 18,
                background: scrolled ? 'transparent' : 'rgba(255,255,255,0.85)',
                backdropFilter: 'blur(8px)',
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              }}><MIco name="back" size={20} color={PM2.ink}/></span>
            </MIconBtn>}
            trailing={null}
          />
        </div>

        {/* HERO PHOTO PAGER */}
        <div style={{ position: 'relative' }}>
          <div
            onScroll={(e) => {
              const i = Math.round(e.target.scrollLeft / e.target.clientWidth);
              if (i !== photoIdx) setPhotoIdx(i);
            }}
            style={{
              display: 'flex', overflowX: 'auto', overflowY: 'hidden',
              scrollSnapType: 'x mandatory', WebkitOverflowScrolling: 'touch',
            }}>
            {Array.from({length: photoCount}).map((_, i) => (
              <div key={i} style={{ minWidth: '100%', height: 380, scrollSnapAlign: 'start' }}>
                <MPhoto property={p} idx={i} style={{ width: '100%', height: '100%' }}/>
              </div>
            ))}
          </div>
          <div style={{
            position: 'absolute', bottom: 14, left: '50%', transform: 'translateX(-50%)',
            background: 'rgba(20,15,10,0.7)', backdropFilter: 'blur(8px)',
            color: '#fff', fontFamily: TM2.sans, fontSize: 11, fontWeight: 500,
            padding: '5px 12px', borderRadius: 100,
          }}>{photoIdx + 1} / {photoCount}</div>
          <button onClick={() => setGalleryOpen(true)} style={{
            position: 'absolute', bottom: 14, right: 14,
            background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(8px)',
            border: 'none', borderRadius: 100, padding: '6px 12px',
            fontFamily: TM2.sans, fontSize: 11, fontWeight: 500, color: PM2.ink,
            cursor: 'pointer',
          }}>All photos</button>
        </div>

        {/* TITLE BLOCK */}
        <div style={{ padding: '20px 20px 0' }}>
          <MEyebrow>
            {p.cardEyebrow || `${p.walkMinTo770 ? `${p.walkMinTo770} min walk to 770` : 'Walking distance to 770'} · ${p.tier}`}
          </MEyebrow>
          <h1 style={{
            fontFamily: TM2.serif, fontSize: 28, fontWeight: 500,
            color: PM2.ink, margin: '8px 0 6px', lineHeight: 1.1, letterSpacing: '-0.01em',
          }}>{p.name}</h1>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, fontFamily: TM2.sans, fontSize: 13, color: PM2.inkSoft }}>
            {p.rating && p.reviews > 0 ? (
              <>
                <MIco name="star" size={13} color={PM2.accent}/>
                <span style={{ fontWeight: 500, color: PM2.ink }}>{p.rating}</span>
                <span style={{ color: PM2.muted }}>({p.reviews} reviews)</span>
                <span style={{ color: PM2.line }}>·</span>
              </>
            ) : null}
            <span style={{ color: PM2.muted }}>Crown Heights</span>
          </div>
          {/* WhatsApp host — compact pill at the top of the listing so guests
              can ask a question without scrolling all the way down. */}
          <div style={{ marginTop: 12, display: 'flex' }}>
            <a
              href={`https://wa.me/18625207797?text=${encodeURIComponent('Hi! I have a question about ' + (p.name || 'your listing'))}`}
              style={{
                display: 'inline-flex', alignItems: 'center', gap: 8,
                padding: '8px 14px 8px 8px', borderRadius: 100,
                background: PM2.surface, border: `0.5px solid ${PM2.line}`,
                color: PM2.ink, textDecoration: 'none',
                fontFamily: TM2.sans, fontSize: 13, fontWeight: 500,
              }}
            >
              <span style={{
                width: 26, height: 26, borderRadius: 13, background: '#25D366',
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
              }}><MIco name="whatsapp" size={15} color="#fff"/></span>
              <span>WhatsApp the host</span>
            </a>
          </div>
        </div>

        {/* QUICK FACTS */}
        <div style={{
          margin: '20px 20px 0',
          padding: '16px 0',
          borderTop: `0.5px solid ${PM2.line}`,
          borderBottom: `0.5px solid ${PM2.line}`,
          display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8,
        }}>
          {[
            { ico: 'bed',   v: p.bedrooms, l: 'Bedrooms' },
            { ico: 'users', v: p.sleeps,   l: 'Sleeps' },
            { ico: 'bath',  v: p.baths,    l: 'Baths' },
            { ico: 'pin',   v: p.walkMinTo770 || '—', l: 'Min to 770' },
          ].map((f, i) => (
            <div key={i} style={{ textAlign: 'center' }}>
              <div style={{ color: PM2.accent, display: 'flex', justifyContent: 'center', marginBottom: 6 }}>
                <MIco name={f.ico} size={20}/>
              </div>
              <div style={{ fontFamily: TM2.serif, fontSize: 22, color: PM2.ink, fontWeight: 500, lineHeight: 1 }}>{f.v}</div>
              <div style={{ fontFamily: TM2.sans, fontSize: 10, color: PM2.muted, marginTop: 4, letterSpacing: '0.06em', textTransform: 'uppercase' }}>{f.l}</div>
            </div>
          ))}
        </div>

        {/* INLINE DATE PICKER — moved above About so guests can pick dates
            right after seeing the bedrooms / sleeps / baths strip. */}
        <div id="dates-section" style={{ padding: '28px 20px 0' }}>
          <h2 style={{ fontFamily: TM2.serif, fontSize: 20, color: PM2.ink, fontWeight: 500, margin: 0 }}>Choose your dates</h2>
          <p style={{ fontFamily: TM2.sans, fontSize: 13, color: PM2.muted, marginTop: 6 }}>
            Tap to choose check-in and check-out before you reserve.
          </p>
          <button onClick={() => setPickerOpen(o => !o)} style={{
            marginTop: 12, width: '100%', padding: '14px 16px', textAlign: 'left',
            background: PM2.surface, border: `1px solid ${dates.checkIn && dates.checkOut ? PM2.accent : PM2.line}`,
            borderRadius: 14, cursor: 'pointer', fontFamily: 'inherit',
            display: 'flex', alignItems: 'center', gap: 12,
          }}>
            <MIco name="calendar" size={18} color={PM2.muted}/>
            <div style={{ flex: 1 }}>
              <div style={{ fontFamily: TM2.sans, fontSize: 11, color: PM2.muted, letterSpacing: '0.06em', textTransform: 'uppercase' }}>Check-in / check-out</div>
              <div style={{ fontFamily: TM2.sans, fontSize: 14, color: PM2.ink, fontWeight: 500, marginTop: 2 }}>{datesLabel}</div>
            </div>
            <span style={{ color: PM2.accent, fontFamily: TM2.sans, fontSize: 12, fontWeight: 500 }}>{pickerOpen ? 'Close' : 'Edit'}</span>
          </button>
          {pickerOpen && typeof window.MMiniCalendar === 'function' && (
            <div style={{
              marginTop: 12, padding: 16, borderRadius: 14,
              background: PM2.surface, border: `0.5px solid ${PM2.line}`,
            }}>
              {React.createElement(window.MMiniCalendar, {
                dates,
                minNights: p.minNights || 2,
                blockedRanges: Array.isArray(p.blockedRanges) ? p.blockedRanges : [],
                setDates: (next) => {
                  setDates(next);
                  // Persist immediately so a refresh keeps the choice.
                  try {
                    const sticky = JSON.parse(sessionStorage.getItem('dyra.search') || '{}') || {};
                    sticky.dates = next;
                    sessionStorage.setItem('dyra.search', JSON.stringify(sticky));
                  } catch (e) {}
                },
              })}
            </div>
          )}
        </div>

        {/* ABOUT */}
        <div style={{ padding: '24px 20px 0' }}>
          <h2 style={{ fontFamily: TM2.serif, fontSize: 20, color: PM2.ink, fontWeight: 500, margin: 0 }}>About the unit</h2>
          <p style={{
            fontFamily: TM2.sans, fontSize: 14, lineHeight: 1.65, color: PM2.inkSoft,
            marginTop: 12, textWrap: 'pretty',
          }}>{p.about}</p>
        </div>

        {/* WALK-THROUGH VIDEO — The Presidential only. Same `walkthrough.mp4`
            that backs the /presidential landing page hero. */}
        {(p.id === 'presidential' || p.slug === 'presidential') && (
          <div style={{ padding: '28px 20px 0' }}>
            <MEyebrow>Walk-through video</MEyebrow>
            <h2 style={{ fontFamily: TM2.serif, fontSize: 20, color: PM2.ink, fontWeight: 500, margin: '8px 0 12px' }}>See every room</h2>
            <div style={{ position: 'relative', aspectRatio: '16/9', borderRadius: 14, overflow: 'hidden', background: '#000' }}>
              <video
                src="/presidential/assets/presidential/walkthrough.mp4"
                controls
                playsInline
                preload="metadata"
                style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }}
              />
            </div>
            <p style={{ fontFamily: TM2.sans, fontSize: 13, color: PM2.muted, marginTop: 10 }}>
              Guided walkthrough — parlor, kitchen, bedrooms, sitting area.
            </p>
          </div>
        )}

        {/* AMENITIES */}
        {(() => {
          // Render exactly what the admin saved on the listing. If the list is
          // empty, hide the section entirely so admins can fully clear what
          // shows here. New listings get a default seed at create time
          // (see the DB backfill), so an empty list always means an explicit
          // admin clear.
          if (!Array.isArray(p.included) || p.included.length === 0) return null;
          const items = p.included.map(x => typeof x === 'string' ? { ico: 'sparkle', v: x } : x);
          return (
            <div style={{ padding: '28px 20px 0' }}>
              <h2 style={{ fontFamily: TM2.serif, fontSize: 20, color: PM2.ink, fontWeight: 500, margin: 0 }}>What's included</h2>
              <div style={{ marginTop: 14, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
                {(showAllAmenities ? items : items.slice(0, 8)).map((a, i) => (
                  <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 0' }}>
                    <span style={{ color: PM2.inkSoft }}><MIco name={a.ico || 'sparkle'} size={20}/></span>
                    <span style={{ fontFamily: TM2.sans, fontSize: 14, color: PM2.ink }}>{a.v}</span>
                  </div>
                ))}
              </div>
              {items.length > 8 && (
                <button
                  onClick={() => setShowAllAmenities(v => !v)}
                  style={{
                    marginTop: 8, background: 'transparent', border: 'none',
                    fontFamily: TM2.sans, fontSize: 13, color: PM2.accent, fontWeight: 500, padding: '8px 0',
                    cursor: 'pointer',
                  }}
                >
                  {showAllAmenities ? 'Show less' : `Show all ${items.length} amenities →`}
                </button>
              )}
            </div>
          );
        })()}

        {/* MAP TEASER */}
        <div style={{ padding: '28px 20px 0' }}>
          <h2 style={{ fontFamily: TM2.serif, fontSize: 20, color: PM2.ink, fontWeight: 500, margin: 0 }}>Where you'll be</h2>
          <p style={{ fontFamily: TM2.sans, fontSize: 13, color: PM2.muted, marginTop: 6 }}>
            We share the exact address after booking. The pin marks the
            approximate location.
          </p>
          {window.DyraListingsMap ? (
            <div style={{ marginTop: 14 }}>
              <window.DyraListingsMap
                listings={[p]}
                palette={{ ink: PM2.ink, accent: PM2.ink, surface: PM2.surface || PM2.card, muted: PM2.muted, line: PM2.line }}
                height={220}
              />
            </div>
          ) : (
            <div style={{
              marginTop: 14, height: 200, borderRadius: 14,
              border: `0.5px solid ${PM2.line}`,
              background: PM2.card,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              fontFamily: TM2.sans, fontSize: 12, color: PM2.muted,
            }}>Loading map…</div>
          )}
        </div>

        {/* REVIEWS */}
        <MReviewsSection p={p} PM2={PM2} TM2={TM2} MIco={MIco}/>

        {/* HOUSE RULES */}
        <div style={{ padding: '28px 20px 0' }}>
          <h2 style={{ fontFamily: TM2.serif, fontSize: 20, color: PM2.ink, fontWeight: 500, margin: 0 }}>House rules</h2>
          <div style={{ marginTop: 14, display: 'flex', flexDirection: 'column' }}>
            {[
              ['Check-in', '3:00 PM (self check-in)'],
              ['Check-out', '11:00 AM'],
              ['Min. nights', `${p.minNights} nights`],
              ['Smoking', 'No smoking'],
              ['Pets', 'No pets'],
              ['Shabbos', 'Quiet Shabbos household'],
            ].map((row, i) => (
              <div key={i} style={{
                display: 'flex', justifyContent: 'space-between',
                padding: '12px 0', borderBottom: i < 5 ? `0.5px solid ${PM2.lineSoft}` : 'none',
                fontFamily: TM2.sans, fontSize: 13,
              }}>
                <span style={{ color: PM2.muted }}>{row[0]}</span>
                <span style={{ color: PM2.ink, fontWeight: 500 }}>{row[1]}</span>
              </div>
            ))}
          </div>
        </div>

        {/* spacer for sticky CTA */}
        <div style={{ height: 120 }}/>
      </div>

      {/* STICKY RESERVE BAR */}
      <div style={{
        position: 'absolute', bottom: 0, left: 0, right: 0,
        background: 'rgba(245,239,227,0.96)',
        backdropFilter: 'saturate(180%) blur(14px)',
        WebkitBackdropFilter: 'saturate(180%) blur(14px)',
        borderTop: `0.5px solid ${PM2.line}`,
        padding: '14px 16px 28px',
        display: 'flex', alignItems: 'center', gap: 14, zIndex: 60,
      }}>
        <div>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 4 }}>
            <span style={{ fontFamily: TM2.serif, fontSize: 22, color: PM2.ink, fontWeight: 500 }}>${p.priceFrom}</span>
            <span style={{ fontFamily: TM2.sans, fontSize: 12, color: PM2.muted }}>/ night</span>
          </div>
          <div style={{ fontFamily: TM2.sans, fontSize: 11, color: PM2.muted }}>
            {datesLabel}
          </div>
        </div>
        <div style={{ flex: 1 }}>
          <MButton onClick={() => {
            // Carry the chosen dates into the booking flow.
            const sp = new URLSearchParams();
            if (dates.checkIn)  sp.set('checkIn',  dates.checkIn);
            if (dates.checkOut) sp.set('checkOut', dates.checkOut);
            if (guests)         sp.set('guests',  String(guests));
            const qs = sp.toString();
            window.location.hash = `booking:${p.id}${qs ? '?' + qs : ''}`;
          }}>Reserve</MButton>
        </div>
      </div>

      {/* GALLERY MODAL */}
      {galleryOpen && (
        <div style={{
          position: 'absolute', inset: 0, background: '#000', zIndex: 200,
          display: 'flex', flexDirection: 'column',
        }}>
          <div style={{
            padding: '60px 16px 12px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            background: 'rgba(0,0,0,0.6)', position: 'relative', zIndex: 1,
          }}>
            <MIconBtn onClick={() => setGalleryOpen(false)} label="Close">
              <MIco name="close" size={24} color="#fff"/>
            </MIconBtn>
            <div style={{ color: '#fff', fontFamily: TM2.sans, fontSize: 13 }}>{photoCount} photos</div>
            <div style={{ width: 44 }}/>
          </div>
          <div style={{ flex: 1, overflow: 'auto', padding: '4px 12px 32px' }}>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
              {Array.from({length: photoCount}).map((_, i) => (
                <div key={i} style={{ width: '100%', aspectRatio: '4/3', background: '#111' }}>
                  <MPhoto property={p} idx={i} style={{ width: '100%', height: '100%' }}/>
                </div>
              ))}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// Reviews section — fetches real published reviews from /api/m/reviews
// for the listing on mount. The summary line still uses the denormalised
// p.rating + p.reviews fields (filled by /api/m/data from properties.rating /
// properties.reviews_count) so the count is consistent with the listing
// card. The detail rows below are the actual published review bodies.
function MReviewsSection({ p, PM2, TM2, MIco }) {
  const [reviews, setReviews] = React.useState(null);
  const [loading, setLoading] = React.useState(false);

  React.useEffect(() => {
    const slug = p && (p.slug || p.id);
    if (!slug) { setReviews([]); return; }
    setLoading(true);
    fetch(`/api/m/reviews?property_slug=${encodeURIComponent(slug)}`)
      .then(r => r.json())
      .then(j => setReviews(Array.isArray(j.reviews) ? j.reviews : []))
      .catch(() => setReviews([]))
      .finally(() => setLoading(false));
  }, [p && (p.slug || p.id)]);

  return (
    <div style={{ padding: '28px 20px 0' }}>
      {p.rating && p.reviews > 0 ? (
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <MIco name="star" size={20} color={PM2.accent}/>
          <span style={{ fontFamily: TM2.serif, fontSize: 22, color: PM2.ink, fontWeight: 500 }}>{p.rating}</span>
          <span style={{ fontFamily: TM2.sans, fontSize: 14, color: PM2.muted }}>· {p.reviews} reviews</span>
        </div>
      ) : null}
      {loading ? (
        <div style={{ padding: 16, textAlign: 'center', color: '#9CA3AF', fontSize: 14 }}>Loading reviews…</div>
      ) : (!reviews || reviews.length === 0) ? (
        <div style={{ padding: 16, textAlign: 'center', color: '#9CA3AF', fontSize: 14 }}>No published reviews yet — guest reviews will appear here after their stay.</div>
      ) : (
        <div style={{ marginTop: 14, display: 'flex', flexDirection: 'column' }}>
          {reviews.map(r => (
            <div key={r.id} style={{ borderTop: `1px solid ${PM2.line}`, padding: '14px 0' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                <span style={{ color: PM2.accent, fontSize: 13 }}>{'★'.repeat(r.rating)}</span>
                <span style={{ fontFamily: TM2.sans, fontSize: 12, color: PM2.muted }}>
                  {r.guest_name}{r.created_at ? ` · ${new Date(r.created_at).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })}` : ''}
                </span>
              </div>
              {r.title ? <div style={{ fontFamily: TM2.serif, fontSize: 16, color: PM2.ink, marginBottom: 4 }}>{r.title}</div> : null}
              {r.body ? <div style={{ fontFamily: TM2.sans, fontSize: 14, color: PM2.ink, lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>{r.body}</div> : null}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── Cancellation policy section (checkout review step) ─────────────────
// Renders right before the Pay button so the guest sees the refund
// timeline before committing. Numbers come from window.DyraCancellation,
// which mirrors src/app/api/guest/cancel/route.ts.
function MCheckoutCancellationPolicy({ chargedNowCents, palette, type }) {
  const PM = palette, TM = type;
  const [open, setOpen] = React.useState(false);
  const policy = (window.DyraCancellation && window.DyraCancellation.previewForCheckout(chargedNowCents)) || null;
  if (!policy) return null;
  const fmt = window.DyraCancellation.formatRefundDollars;
  return (
    <div style={{
      marginTop: 16, padding: 14, background: PM.surface, borderRadius: 12,
      border: `0.5px solid ${PM.lineSoft}`,
    }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
        <span style={{ color: PM.success, marginTop: 2 }}><MIco name="shield" size={16}/></span>
        <div style={{ flex: 1 }}>
          <div style={{ fontFamily: TM.sans, fontSize: 12, letterSpacing: '0.12em', textTransform: 'uppercase', color: PM.muted, marginBottom: 6 }}>
            Cancellation policy
          </div>
          <div style={{ fontFamily: TM.sans, fontSize: 13, color: PM.ink, lineHeight: 1.5 }}>
            {policy.summary}
          </div>
          <div style={{ fontFamily: TM.sans, fontSize: 11.5, color: PM.muted, marginTop: 6, lineHeight: 1.45 }}>
            If you cancel inside that window, we’ll refund {fmt(policy.eligibleRefundCents)} back to your card. {policy.processingNote}
          </div>
          <button
            type="button"
            onClick={() => setOpen(v => !v)}
            style={{
              marginTop: 10, background: 'transparent', border: 'none', padding: 0,
              color: PM.accent, fontFamily: TM.sans, fontSize: 12, fontWeight: 500,
              cursor: 'pointer', textDecoration: 'underline',
            }}
          >{open ? 'Hide full policy' : 'Show full policy'}</button>
          {open && (
            <div style={{ marginTop: 10, paddingTop: 10, borderTop: `0.5px solid ${PM.lineSoft}` }}>
              {policy.tiers.map(tier => (
                <div key={tier.id} style={{
                  display: 'flex', gap: 10, padding: '8px 0',
                  borderBottom: `0.5px solid ${PM.lineSoft}`,
                  opacity: tier.activeNow ? 1 : 0.7,
                }}>
                  <div style={{
                    width: 6, marginTop: 6, alignSelf: 'flex-start',
                    height: 6, borderRadius: 3,
                    background: tier.activeNow ? PM.success : PM.line,
                  }}/>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontFamily: TM.sans, fontSize: 12.5, color: PM.ink, fontWeight: tier.activeNow ? 600 : 500 }}>
                      {tier.label}{tier.activeNow ? ' · Current' : ''}
                    </div>
                    <div style={{ fontFamily: TM.sans, fontSize: 12, color: PM.muted, marginTop: 2, lineHeight: 1.45 }}>
                      {tier.refundDescription}
                    </div>
                  </div>
                </div>
              ))}
              <div style={{ fontFamily: TM.sans, fontSize: 11, color: PM.muted, marginTop: 8, lineHeight: 1.5 }}>
                {policy.processingNote}
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════════
// BOOKING FLOW — multi-step
// ═══════════════════════════════════════════════════════════════════════
function MBooking({ propertyId, go }) {
  const props = window.DyraStore?.state?.properties || window.DYRA?.PROPERTIES || [];
  // Match by id OR slug so deep-links like /#listing:presidential resolve
  // (the /presidential landing CTA passes the slug, not the UUID).
  const p = props.find(x => x.id === propertyId || x.slug === propertyId) || props[0];
  const [step, setStep] = React.useState(1); // 1=review, 2=guest details, 3=pay+kashrus, 4=confirm

  // ── Idempotency key for /api/checkout ────────────────────────────────
  // A single UUID minted once per "checkout flow" and reused across
  // every POST to /api/checkout in this flow. The server uses it to
  // recognise retries (refresh, double-tap, slow Element mount, dev
  // hot-reload) and return the same PaymentIntent client_secret instead
  // of creating a fresh one — which is how chasya's card got charged
  // three times on a single booking attempt before this fix shipped.
  //
  // Persisted in sessionStorage keyed by property so the same key
  // survives a page refresh inside the booking flow but is fresh if the
  // user starts a totally new booking on a different listing.
  const requestId = React.useMemo(() => {
    try {
      const k = `dyra.checkoutRequestId:${propertyId || 'unknown'}`;
      let v = sessionStorage.getItem(k);
      if (!v) {
        v = (typeof crypto !== 'undefined' && crypto.randomUUID)
          ? crypto.randomUUID()
          : ('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx').replace(/[xy]/g, c => {
              const r = Math.random() * 16 | 0;
              return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
            });
        sessionStorage.setItem(k, v);
      }
      return v;
    } catch (e) {
      return null;
    }
  }, [propertyId]);

  // Read dates + guest count out of the URL ("#booking:slug?checkIn=…").
  // The listing page sets these before navigating; falls back to 3 nights
  // for legacy demo entry points without query params.
  const search = React.useMemo(() => {
    const h = (typeof window !== 'undefined' && window.location.hash) || '';
    const qi = h.indexOf('?');
    let checkIn = '', checkOut = '', guests = 1;
    if (qi >= 0) {
      const sp = new URLSearchParams(h.slice(qi+1));
      checkIn = sp.get('checkIn') || '';
      checkOut = sp.get('checkOut') || '';
      guests = Number(sp.get('guests') || 1) || 1;
    }
    return { checkIn, checkOut, guests };
  }, []);
  const nights = (search.checkIn && search.checkOut)
    ? Math.max(1, Math.round(
        (new Date(search.checkOut + 'T00:00:00') - new Date(search.checkIn + 'T00:00:00')) / 86400000
      ))
    : 3;
  const [guestInfo, setGuestInfo] = React.useState({ name: '', email: '', phone: '', notes: '' });
  const [upsells, setUpsells] = React.useState({});

  // ── Stripe Payment Element (replaces the placeholder card inputs) ──
  // We create the PaymentIntent + booking row on entry to step 3 (after
  // the guest has filled name/email/phone in step 2) and then mount the
  // Stripe Element so the card can be entered. Confirmation runs on
  // tap of the deposit CTA.
  const stripeMountRef = React.useRef(null);
  const [stripeReady, setStripeReady] = React.useState(false);
  const [stripeInstance, setStripeInstance] = React.useState(null);
  const [elementsInstance, setElementsInstance] = React.useState(null);
  const [paymentElementInstance, setPaymentElementInstance] = React.useState(null);
  const [pendingBookingId, setPendingBookingId] = React.useState(null);
  const [pendingClientSecret, setPendingClientSecret] = React.useState(null);
  const [stripeError, setStripeError] = React.useState('');
  const [creatingIntent, setCreatingIntent] = React.useState(false);
  const [processing, setProcessing] = React.useState(false);
  // ── Server-driven amounts + mode ─────────────────────────────────────
  // The server tells us whether this PaymentIntent is a deposit
  // (charge_full_amount=false → 30% today, 70% off-session later) or
  // the full amount today. The CTA label and the BNPL warning tile
  // both key off this. We refetch when the user toggles modes via the
  // "pay full amount" prompt below.
  const [chargeFullAmount, setChargeFullAmount] = React.useState(false);
  const [serverTotalCents, setServerTotalCents] = React.useState(null);
  const [serverDepositCents, setServerDepositCents] = React.useState(null);
  // Tracks the type currently selected inside the Payment Element
  // ('card', 'link', 'klarna', 'afterpay_clearpay', 'affirm',
  // 'cashapp', 'us_bank_account', 'apple_pay', 'google_pay', …).
  // Set by the Element's `change` event.
  const [selectedPmType, setSelectedPmType] = React.useState('');
  // Force a forceFullAmount=true POST on the next intent fetch (set
  // when the user clicks "Pay full amount" on the prompt below).
  const [forceFullPaymentMode, setForceFullPaymentMode] = React.useState(false);

  // ── Gematria kashrus check ────────────────────────────────────────────
  // Same idea as the desktop flow: prove the booker can read Hebrew before
  // confirming a kosher home. Aleph–Tes valued 1–9; guest enters the
  // 4-digit numerical value of the displayed letters. 3 wrong attempts
  // soft-locks the form (real impl would route to the host).
  const HEB_LETTERS = React.useMemo(() => [
    { letter: 'א', value: 1 }, { letter: 'ב', value: 2 }, { letter: 'ג', value: 3 },
    { letter: 'ד', value: 4 }, { letter: 'ה', value: 5 }, { letter: 'ו', value: 6 },
    { letter: 'ז', value: 7 }, { letter: 'ח', value: 8 }, { letter: 'ט', value: 9 },
  ], []);
  const genCaptcha = React.useCallback(() => {
    const out = [];
    for (let i = 0; i < 4; i++) out.push(HEB_LETTERS[Math.floor(Math.random() * 9)]);
    return out;
  }, [HEB_LETTERS]);
  const [captcha, setCaptcha] = React.useState(genCaptcha);
  const [captchaInput, setCaptchaInput] = React.useState('');
  const [captchaAttempts, setCaptchaAttempts] = React.useState(0);
  const captchaCorrect = captcha.map(l => l.value).join('');
  const captchaPassed = captchaInput.length === 4 && captchaInput === captchaCorrect;
  const captchaLocked = captchaAttempts >= 3 && !captchaPassed;
  const subtotal = p.priceFrom * nights;
  const cleaning = p.cleaningFee;
  const upsellsList = window.DYRA?.UPSELLS || [];
  const upsellTotal = Object.entries(upsells).filter(([_, v]) => v).reduce((sum, [id]) => {
    const u = upsellsList.find(x => x.id === id);
    return sum + (u?.priceFrom || 0);
  }, 0);

  // ── Promo code ──────────────────────────────────────────────────────
  // Server-validated against the promo_codes table via /api/promo-validate.
  // We send the discount-eligible subtotal in cents; the server applies
  // the rules (active, expiry, min nights, scope, max uses) and returns
  // discount_cents. uses are NOT incremented here — only at booking creation.
  const [promoOpen, setPromoOpen] = React.useState(false);
  const [promoInput, setPromoInput] = React.useState('');
  const [promoErr, setPromoErr] = React.useState('');
  const [promoBusy, setPromoBusy] = React.useState(false);
  const [appliedPromo, setAppliedPromo] = React.useState(null); // { code, discount_cents, kind, value }
  const discount = appliedPromo ? Math.round(appliedPromo.discount_cents / 100) : 0;

  const total = Math.max(0, subtotal + cleaning + upsellTotal - discount);

  const applyPromo = async () => {
    const code = promoInput.trim().toUpperCase();
    if (!code) return;
    setPromoBusy(true); setPromoErr('');
    try {
      const r = await fetch('/api/promo-validate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          code,
          property_id: p.id,
          nights,
          subtotal_cents: (subtotal + cleaning + upsellTotal) * 100,
        }),
      });
      const data = await r.json();
      if (data.ok) {
        setAppliedPromo(data);
        setPromoErr('');
      } else {
        setAppliedPromo(null);
        setPromoErr(data.error || "Couldn't apply that code.");
      }
    } catch (_) {
      setPromoErr("Network error. Try again.");
    } finally {
      setPromoBusy(false);
    }
  };
  const removePromo = () => { setAppliedPromo(null); setPromoInput(''); setPromoErr(''); };

  // Mount Stripe Element on entry to step 3, once contact info is valid.
  // Re-runs whenever forceFullPaymentMode flips — that happens when the
  // guest picks a non-card method on a long-lead booking and confirms
  // "pay the full amount today" from the prompt below.
  React.useEffect(() => {
    if (step !== 3) return;
    if (creatingIntent || pendingClientSecret) return;
    const nameOk = guestInfo.name.trim().length > 0;
    const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(guestInfo.email.trim());
    const phoneOk = guestInfo.phone.replace(/\D/g, '').length >= 7;
    if (!(nameOk && emailOk && phoneOk)) return;
    if (!search.checkIn || !search.checkOut) {
      setStripeError('Pick your dates first — go back and use the date picker.');
      return;
    }
    let cancelled = false;
    setCreatingIntent(true);
    setStripeError('');
    (async () => {
      try {
        const r = await fetch('/api/checkout', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            mode: 'inline',
            request_id: requestId,
            property_slug: p.id || p.slug,
            check_in: search.checkIn,
            check_out: search.checkOut,
            guests_count: search.guests,
            guest_name: guestInfo.name.trim(),
            guest_email: guestInfo.email.trim(),
            guest_phone: guestInfo.phone.trim(),
            notes: guestInfo.notes || null,
            promo_code: appliedPromo ? appliedPromo.code : null,
            // Tell the server to mint a full-amount PI (no
            // setup_future_usage) so non-card PMs are offered.
            force_full_amount: forceFullPaymentMode,
          }),
        });
        const j = await r.json().catch(() => ({}));
        if (cancelled) return;
        if (!r.ok || !j.client_secret) {
          setStripeError(j.error || 'Could not start checkout — try again.');
          setCreatingIntent(false);
          return;
        }
        const pubKey = j.publishable_key || window.DYRA_STRIPE_PUBLISHABLE_KEY || '';
        if (!window.Stripe || !pubKey) {
          setStripeError('Payments are not configured. Contact support.');
          setCreatingIntent(false);
          return;
        }
        setPendingBookingId(j.booking_id);
        setPendingClientSecret(j.client_secret);
        setChargeFullAmount(!!j.charge_full_amount);
        setServerTotalCents(typeof j.total_cents === 'number' ? j.total_cents : null);
        setServerDepositCents(typeof j.deposit_cents === 'number' ? j.deposit_cents : null);
        const sInst = window.Stripe(pubKey);
        const eInst = sInst.elements({ clientSecret: j.client_secret, appearance: { theme: 'stripe' } });
        const paymentEl = eInst.create('payment', { layout: 'tabs' });
        // Track the user's selected PM so we can warn them when they
        // pick something we can't charge off-session for the balance.
        try {
          paymentEl.on('change', (ev) => {
            const t = (ev && ev.value && ev.value.type) || '';
            setSelectedPmType(String(t || ''));
          });
        } catch (_) {}
        const tryMount = () => {
          if (stripeMountRef.current) {
            paymentEl.mount(stripeMountRef.current);
          } else {
            setTimeout(tryMount, 80);
          }
        };
        tryMount();
        setStripeInstance(sInst);
        setElementsInstance(eInst);
        setPaymentElementInstance(paymentEl);
        setStripeReady(true);
        setCreatingIntent(false);
      } catch (e) {
        if (cancelled) return;
        setStripeError((e && e.message) || 'Payment setup failed.');
        setCreatingIntent(false);
      }
    })();
    return () => { cancelled = true; };
  }, [step, guestInfo.name, guestInfo.email, guestInfo.phone, search.checkIn, search.checkOut, forceFullPaymentMode]);

  // ── Tear down Stripe Elements when we want to swap to a different
  // PaymentIntent (e.g. user accepted "pay the full amount"). The
  // effect above only fires when pendingClientSecret is null, so we
  // null it out here and let the effect re-run.
  const swapToFullAmountMode = React.useCallback(() => {
    try { if (paymentElementInstance) paymentElementInstance.unmount(); } catch (_) {}
    setPaymentElementInstance(null);
    setElementsInstance(null);
    setStripeInstance(null);
    setStripeReady(false);
    setPendingClientSecret(null);
    setSelectedPmType('');
    setForceFullPaymentMode(true);
  }, [paymentElementInstance]);
  const swapBackToDepositMode = React.useCallback(() => {
    try { if (paymentElementInstance) paymentElementInstance.unmount(); } catch (_) {}
    setPaymentElementInstance(null);
    setElementsInstance(null);
    setStripeInstance(null);
    setStripeReady(false);
    setPendingClientSecret(null);
    setSelectedPmType('');
    setForceFullPaymentMode(false);
  }, [paymentElementInstance]);

  // Off-session-incompatible methods. Picking one of these on a
  // long-lead booking triggers the "pay full now" prompt because we
  // can't auto-charge the balance with these PMs.
  const PM_NEEDS_FULL = ['klarna','afterpay_clearpay','affirm','cashapp','us_bank_account','customer_balance'];
  const needsFullAmountSwitch =
    !chargeFullAmount &&
    selectedPmType &&
    PM_NEEDS_FULL.indexOf(selectedPmType) >= 0;

  const handleConfirmPay = async () => {
    if (processing) return;
    if (!stripeInstance || !elementsInstance || !pendingClientSecret) {
      setStripeError('Payment form not ready yet — give it a moment.');
      return;
    }
    setProcessing(true);
    setStripeError('');
    try {
      const { error, paymentIntent } = await stripeInstance.confirmPayment({
        elements: elementsInstance,
        redirect: 'if_required',
      });
      if (error) {
        setProcessing(false);
        setStripeError(error.message || 'Card declined — try a different payment method.');
        return;
      }
      if (!paymentIntent || (paymentIntent.status !== 'succeeded' && paymentIntent.status !== 'processing')) {
        setProcessing(false);
        setStripeError('Unexpected payment status: ' + (paymentIntent ? paymentIntent.status : 'unknown'));
        return;
      }
      // Success — webhook will set the booking confirmed + email magic link.
      // Clear the per-flow checkout request_id so a future booking on
      // the same property starts a fresh dedupe window. Without this,
      // a returning guest would hit the "already_confirmed" 409 from
      // the server's idempotency check on /api/checkout.
      try { sessionStorage.removeItem(`dyra.checkoutRequestId:${propertyId || 'unknown'}`); } catch (e) {}
      // Mirror to the local admin store for instant feedback.
      if (window.DyraStore && window.DyraStore.api && window.DyraStore.api.addReservation) {
        try {
          window.DyraStore.api.addReservation({
            id: pendingBookingId,
            propertyId: p.id, guestName: guestInfo.name || 'Mobile guest',
            guestEmail: guestInfo.email, guestPhone: guestInfo.phone,
            checkIn: search.checkIn || '2026-05-08',
            checkOut: search.checkOut || '2026-05-11',
            adults: search.guests || 4, children: 0, total,
            depositPaid: Math.round(total * 0.3),
            status: 'confirmed', source: 'mobile',
          });
        } catch (e) {}
      }
      setProcessing(false);
      next(); // step 3 → step 4 (Confirmed)
    } catch (e) {
      setProcessing(false);
      setStripeError((e && e.message) || 'Payment failed — try again.');
    }
  };

  const next = () => setStep(s => Math.min(4, s + 1));
  const back = () => step > 1 ? setStep(s => s - 1) : go('listing:' + propertyId);

  return (
    <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: PM2.bg }}>
      <MAppBar
        title={['', 'Confirm trip', 'About you', 'Payment', 'Confirmed'][step]}
        scrolled
        leading={step < 4 && (
          <MIconBtn onClick={back} label="Back"><MIco name="back" size={22}/></MIconBtn>
        )}
      />
      {/* progress bar */}
      {step < 4 && (
        <div style={{ padding: '0 16px 12px', display: 'flex', gap: 4 }}>
          {[1,2,3].map(s => (
            <div key={s} style={{
              flex: 1, height: 3, borderRadius: 2,
              background: s <= step ? PM2.accent : PM2.lineSoft,
              transition: 'background 200ms',
            }}/>
          ))}
        </div>
      )}

      <div style={{ flex: 1, overflow: 'auto', WebkitOverflowScrolling: 'touch' }}>
        {/* STEP 1: REVIEW + UPSELLS */}
        {step === 1 && (
          <div style={{ padding: '4px 16px 24px' }}>
            <div style={{
              padding: 14, background: PM2.surface, borderRadius: 14,
              border: `0.5px solid ${PM2.lineSoft}`,
              display: 'flex', alignItems: 'center', gap: 12,
            }}>
              <MPhoto property={p} idx={0} style={{ width: 80, height: 80, borderRadius: 10, flexShrink: 0 }}/>
              <div style={{ flex: 1 }}>
                <div style={{ fontFamily: TM2.serif, fontSize: 17, color: PM2.ink, fontWeight: 500, lineHeight: 1.2 }}>{p.name}</div>
                <div style={{ fontFamily: TM2.sans, fontSize: 11, color: PM2.muted, marginTop: 2 }}>
                  {p.bedrooms} bed · {p.baths} bath · sleeps {p.sleeps}
                </div>
                {p.rating && p.reviews > 0 ? (
                  <div style={{ fontFamily: TM2.sans, fontSize: 11, color: PM2.accent, marginTop: 2, fontWeight: 500 }}>
                    ★ {p.rating} ({p.reviews})
                  </div>
                ) : null}
              </div>
            </div>

            {/* Trip details */}
            <div style={{ marginTop: 24 }}>
              <h3 style={{ fontFamily: TM2.serif, fontSize: 18, color: PM2.ink, fontWeight: 500, margin: 0 }}>Your trip</h3>
              <div style={{ marginTop: 12 }}>
                <button
                  onClick={() => {
                    // Send the user back to the listing detail with the date picker focused.
                    // The existing query string (checkIn/checkOut/guests) is preserved so
                    // the picker opens on their current selection.
                    const qs = window.location.hash.split('?')[1] || '';
                    const sep = qs ? '&' : '';
                    window.location.hash = `listing:${propertyId}?${qs}${sep}focus=dates`;
                  }}
                  style={{
                    width: '100%', padding: '14px 16px',
                    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                    background: PM2.surface, border: `0.5px solid ${PM2.line}`, borderRadius: 12,
                    marginBottom: 8, textAlign: 'left', cursor: 'pointer',
                  }}>
                  <div>
                    <div style={{ fontFamily: TM2.sans, fontSize: 12, color: PM2.muted }}>Dates</div>
                    <div style={{ fontFamily: TM2.sans, fontSize: 14, color: PM2.ink, fontWeight: 500, marginTop: 2 }}>{(()=>{const sp=new URLSearchParams((window.location.hash.split('?')[1]||''));const ci=sp.get('checkIn')||'';const co=sp.get('checkOut')||'';const F=window.DYRA_FRIENDLY_DATE||(s=>s);return (ci&&co)?(F(ci)+' – '+F(co)+' · '+nights+' night'+(nights===1?'':'s')):('Pick your dates · '+nights+' nights');})()}</div>
                  </div>
                  <span style={{ color: PM2.accent, fontFamily: TM2.sans, fontSize: 13, fontWeight: 500 }}>Edit</span>
                </button>
                <button
                  onClick={() => {
                    const qs = window.location.hash.split('?')[1] || '';
                    const sep = qs ? '&' : '';
                    window.location.hash = `listing:${propertyId}?${qs}${sep}focus=guests`;
                  }}
                  style={{
                    width: '100%', padding: '14px 16px',
                    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                    background: PM2.surface, border: `0.5px solid ${PM2.line}`, borderRadius: 12,
                    textAlign: 'left', cursor: 'pointer',
                  }}>
                  <div>
                    <div style={{ fontFamily: TM2.sans, fontSize: 12, color: PM2.muted }}>Guests</div>
                    <div style={{ fontFamily: TM2.sans, fontSize: 14, color: PM2.ink, fontWeight: 500, marginTop: 2 }}>{(()=>{const sp=new URLSearchParams((window.location.hash.split('?')[1]||''));const g=Number(sp.get('guests')||0)||0;return g>0?(g+' guest'+(g===1?'':'s')):'Add guests';})()}</div>
                  </div>
                  <span style={{ color: PM2.accent, fontFamily: TM2.sans, fontSize: 13, fontWeight: 500 }}>Edit</span>
                </button>
              </div>
            </div>

            {/* Upsells */}
            <div style={{ marginTop: 28 }}>
              <h3 style={{ fontFamily: TM2.serif, fontSize: 18, color: PM2.ink, fontWeight: 500, margin: 0 }}>Add to your stay</h3>
              <p style={{ fontFamily: TM2.sans, fontSize: 12, color: PM2.muted, marginTop: 4 }}>
                Optional kosher add-ons. Pay only for what you select.
              </p>
              <div style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 10 }}>
                {upsellsList.slice(0, 4).map(u => {
                  const on = upsells[u.id];
                  return (
                    <button key={u.id} onClick={() => setUpsells(s => ({ ...s, [u.id]: !s[u.id] }))} style={{
                      padding: 14, borderRadius: 12,
                      background: on ? PM2.accentSoft : PM2.surface,
                      border: `1.5px solid ${on ? PM2.accent : PM2.line}`,
                      display: 'flex', alignItems: 'center', gap: 12,
                      textAlign: 'left', cursor: 'pointer',
                    }}>
                      <div style={{ flex: 1 }}>
                        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 8 }}>
                          <div style={{ fontFamily: TM2.serif, fontSize: 15, color: PM2.ink, fontWeight: 500 }}>{u.name}</div>
                          <div style={{ fontFamily: TM2.sans, fontSize: 13, color: PM2.ink, fontWeight: 600 }}>${u.priceFrom}</div>
                        </div>
                        <div style={{ fontFamily: TM2.sans, fontSize: 11, color: PM2.muted, marginTop: 4, lineHeight: 1.4 }}>{u.desc}</div>
                      </div>
                      <span style={{
                        width: 24, height: 24, borderRadius: 12,
                        background: on ? PM2.accent : 'transparent',
                        border: `1.5px solid ${on ? PM2.accent : PM2.line}`,
                        display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
                      }}>{on && <MIco name="check" size={14} color="#fff"/>}</span>
                    </button>
                  );
                })}
              </div>
            </div>
          </div>
        )}

        {/* STEP 2: GUEST DETAILS — kosher preferences removed per user feedback;
             the kashrus / gematria check now lives on the payment step alongside
             the deposit breakdown so all the verification + commitment
             happens in one place. */}
        {step === 2 && (
          <div style={{ padding: '4px 16px 24px' }}>
            <h3 style={{ fontFamily: TM2.serif, fontSize: 18, color: PM2.ink, fontWeight: 500, margin: 0 }}>Your details</h3>
            <p style={{ fontFamily: TM2.sans, fontSize: 12, color: PM2.muted, marginTop: 4 }}>
              We'll send your confirmation here.
            </p>
            <div style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 12 }}>
              <MField label="Full name" value={guestInfo.name} onChange={v => setGuestInfo(g => ({ ...g, name: v }))} placeholder="Yossi Rosenberg" />
              <MField label="Email" type="email" value={guestInfo.email} onChange={v => setGuestInfo(g => ({ ...g, email: v }))} placeholder="yossi@example.com" />
              <MField label="Phone" type="tel" value={guestInfo.phone} onChange={v => setGuestInfo(g => ({ ...g, phone: v }))} placeholder="+1 (555) 555-5555" />
            </div>

            <h3 style={{ fontFamily: TM2.serif, fontSize: 18, color: PM2.ink, fontWeight: 500, margin: '28px 0 0' }}>Anything else?</h3>
            <textarea
              value={guestInfo.notes}
              onChange={(e) => setGuestInfo(g => ({ ...g, notes: e.target.value }))}
              placeholder="Coming in for a sheva brachos, late arrival, food allergies, etc."
              style={{
                marginTop: 12, width: '100%', minHeight: 100,
                padding: 14, borderRadius: 12,
                border: `1px solid ${PM2.line}`, background: PM2.surface,
                fontFamily: TM2.sans, fontSize: 14, color: PM2.ink,
                resize: 'vertical', boxSizing: 'border-box',
              }}/>
          </div>
        )}

        {/* STEP 3: PAYMENT — deposit + balance split, kashrus gematria check,
             then card capture. Mirrors the desktop D1Booking confirm step.
             Compute the 30% deposit and the remaining balance (paid 7 days
             before check-in) right inside this branch — no extra component. */}
        {step === 3 && (() => {
          // Prefer the server's total / deposit (in cents) — they
          // already encode the chargeFullAmount switch. Fall back to
          // the locally-computed 30% if we haven't heard from the
          // server yet (form still loading).
          const totalDollars = serverTotalCents != null ? Math.round(serverTotalCents / 100) : total;
          const depositDollars = serverDepositCents != null
            ? Math.round(serverDepositCents / 100)
            : Math.round(total * 0.3);
          const deposit = depositDollars;
          const balance = Math.max(0, totalDollars - depositDollars);
          return (
          <div style={{ padding: '4px 16px 24px' }}>
            <h3 style={{ fontFamily: TM2.serif, fontSize: 18, color: PM2.ink, fontWeight: 500, margin: 0 }}>Payment</h3>
            <p style={{ fontFamily: TM2.sans, fontSize: 12, color: PM2.muted, marginTop: 4 }}>
              {chargeFullAmount
                ? 'Full amount charged today.'
                : '30% deposit today locks your dates. The rest is due 7 days before check-in.'}
            </p>

            {/* Two-tile deposit / balance split — collapses to a single
                full-amount tile when the booking is paying in full today
                (short-lead, OR the guest opted into "pay full" because
                they picked a non-card PM). */}
            <div style={{
              marginTop: 14, padding: 16, borderRadius: 14,
              background: PM2.ink, color: '#fff',
            }}>
              {chargeFullAmount ? (
                <div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 10, marginBottom: 12 }}>
                  <div style={{ padding: 12, background: 'rgba(255,255,255,0.08)', borderRadius: 10 }}>
                    <div style={{ fontSize: 9, letterSpacing: '0.16em', textTransform: 'uppercase', opacity: 0.7, marginBottom: 4 }}>Charged today</div>
                    <div style={{ fontFamily: TM2.serif, fontSize: 22, fontWeight: 500 }}>${deposit}</div>
                    <div style={{ fontSize: 10, opacity: 0.7, marginTop: 3 }}>Paid in full · no balance due</div>
                  </div>
                </div>
              ) : (
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 12 }}>
                  <div style={{ padding: 12, background: 'rgba(255,255,255,0.08)', borderRadius: 10 }}>
                    <div style={{ fontSize: 9, letterSpacing: '0.16em', textTransform: 'uppercase', opacity: 0.7, marginBottom: 4 }}>Today · 30%</div>
                    <div style={{ fontFamily: TM2.serif, fontSize: 22, fontWeight: 500 }}>${deposit}</div>
                    <div style={{ fontSize: 10, opacity: 0.7, marginTop: 3 }}>Locks your dates</div>
                  </div>
                  <div style={{ padding: 12, background: 'rgba(255,255,255,0.04)', borderRadius: 10, border: '1px dashed rgba(255,255,255,0.2)' }}>
                    <div style={{ fontSize: 9, letterSpacing: '0.16em', textTransform: 'uppercase', opacity: 0.7, marginBottom: 4 }}>Apr 30 · Balance</div>
                    <div style={{ fontFamily: TM2.serif, fontSize: 22, fontWeight: 500, opacity: 0.85 }}>${balance}</div>
                    <div style={{ fontSize: 10, opacity: 0.7, marginTop: 3 }}>7 days before check-in</div>
                  </div>
                </div>
              )}
              <div style={{ fontSize: 10.5, opacity: 0.7, lineHeight: 1.5, paddingTop: 10, borderTop: '1px solid rgba(255,255,255,0.1)' }}>
                Dates are reserved only after the deposit is paid. Free cancellation up to 48h after booking.
              </div>
            </div>

            {/* KASHRUS / GEMATRIA CHECK ─── This home is for guests who keep
                kosher; guest enters the 4-digit numerical value of the
                displayed Hebrew letters (Aleph–Tes, valued 1–9). */}
            <div style={{ marginTop: 22 }}>
              <div style={{ fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase', color: PM2.accent, marginBottom: 6 }}>Kashrus check</div>
              <div style={{ fontFamily: TM2.sans, fontSize: 12.5, color: PM2.muted, marginBottom: 10, lineHeight: 1.55 }}>
                Enter 4 digit numerical code
              </div>
              <div style={{
                background: PM2.surface, border: `1px solid ${captchaPassed ? PM2.success : captchaLocked ? '#c0392b' : PM2.line}`,
                borderRadius: 12, padding: '14px 14px',
                opacity: captchaLocked ? 0.55 : 1,
                display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10,
              }}>
                <div style={{ display: 'flex', gap: 8, fontFamily: TM2.hebrew || TM2.serif, fontSize: 32, lineHeight: 1, direction: 'rtl', letterSpacing: '0.04em', color: PM2.ink, userSelect: 'none' }}>
                  {captcha.map((l, i) => <span key={i}>{l.letter}</span>)}
                </div>
                <button type="button" disabled={captchaLocked || captchaPassed}
                  onClick={() => { setCaptcha(genCaptcha()); setCaptchaInput(''); }}
                  style={{
                    background: 'transparent', border: `1px solid ${PM2.line}`, color: PM2.muted,
                    padding: '6px 10px', borderRadius: 999, fontSize: 10,
                    cursor: (captchaLocked || captchaPassed) ? 'not-allowed' : 'pointer',
                    letterSpacing: '0.06em',
                  }}
                >↻ New</button>
                <input
                  type="text" inputMode="numeric" pattern="[0-9]*" maxLength={4}
                  disabled={captchaLocked || captchaPassed}
                  value={captchaInput}
                  onChange={e => {
                    const v = e.target.value.replace(/\D/g, '').slice(0, 4);
                    setCaptchaInput(v);
                    if (v.length === 4 && v !== captchaCorrect) {
                      setCaptchaAttempts(a => a + 1);
                      setTimeout(() => setCaptchaInput(''), 600);
                    }
                  }}
                  placeholder="0000"
                  style={{
                    width: 92, padding: '10px 10px', border: `1px solid ${PM2.line}`,
                    background: PM2.bg, borderRadius: 8, fontFamily: 'monospace',
                    fontSize: 18, color: PM2.ink, textAlign: 'center',
                    letterSpacing: '0.2em', boxSizing: 'border-box',
                  }}
                />
              </div>
              {!captchaPassed && !captchaLocked && captchaAttempts > 0 && (
                <div style={{ fontSize: 11, color: '#c0392b', marginTop: 6, fontFamily: TM2.sans }}>
                  Doesn't match. {3 - captchaAttempts} attempt{3 - captchaAttempts === 1 ? '' : 's'} left — tap ↻ for a new code.
                </div>
              )}
              {captchaLocked && (
                <div style={{ fontSize: 11, color: '#c0392b', marginTop: 6, fontFamily: TM2.sans, lineHeight: 1.5 }}>
                  Too many incorrect attempts. This home is reserved for guests who keep kosher — please WhatsApp the host to discuss.
                </div>
              )}
              {captchaPassed && (
                <div style={{ fontSize: 11, color: PM2.success, marginTop: 6, fontFamily: TM2.sans, fontWeight: 500 }}>
                  ✓ Verified.
                </div>
              )}
            </div>

            <div style={{ marginTop: 22, fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: PM2.muted, fontFamily: TM2.sans, marginBottom: 10 }}>Payment method</div>
            <div>
              {!stripeReady && !stripeError && (
                <div style={{ padding: '14px 16px', background: PM2.surface, border: `1px solid ${PM2.line}`, borderRadius: 12, fontSize: 13, color: PM2.muted, fontFamily: TM2.sans }}>
                  Loading secure payment form…
                </div>
              )}
              <div ref={stripeMountRef} style={{ display: stripeReady ? 'block' : 'none', padding: 14, background: PM2.surface, border: `1px solid ${PM2.line}`, borderRadius: 12, minHeight: 60 }} />
              {stripeError && (
                <div style={{ marginTop: 10, padding: '10px 14px', background: '#fdf0ed', border: '1px solid #c0392b', borderRadius: 10, fontSize: 12, color: '#c0392b', fontFamily: TM2.sans }}>{stripeError}</div>
              )}
              {/* BNPL / non-card prompt — shown when the guest picks a
                  payment method we can't auto-charge for the balance
                  (Klarna / Affirm / Afterpay / Cash App / ACH). On
                  long-lead bookings the only way to use one of these
                  is to pay the entire amount up-front today. */}
              {needsFullAmountSwitch && (
                <div style={{
                  marginTop: 10, padding: '14px 14px',
                  background: '#fff8ec', border: '1px solid #d6a85a',
                  borderRadius: 12, fontFamily: TM2.sans,
                }}>
                  <div style={{ fontSize: 13, fontWeight: 600, color: PM2.ink, marginBottom: 6 }}>
                    This payment method requires paying the full ${totalDollars} now
                  </div>
                  <div style={{ fontSize: 12, color: PM2.inkSoft || PM2.muted, lineHeight: 1.5, marginBottom: 12 }}>
                    We can't auto-charge the ${balance} balance on this method 7 days before check-in. To use it, we'll charge the full ${totalDollars} today instead of the ${deposit} deposit.
                  </div>
                  <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                    <button onClick={swapToFullAmountMode} style={{
                      flex: 1, minWidth: 140, padding: '10px 14px', borderRadius: 10, border: 'none',
                      background: PM2.ink, color: '#fff', fontFamily: TM2.sans, fontSize: 13, fontWeight: 600, cursor: 'pointer',
                    }}>Pay ${totalDollars} now</button>
                    <button onClick={swapBackToDepositMode} style={{
                      flex: 1, minWidth: 140, padding: '10px 14px', borderRadius: 10,
                      border: `1px solid ${PM2.line}`, background: PM2.surface, color: PM2.ink,
                      fontFamily: TM2.sans, fontSize: 13, fontWeight: 500, cursor: 'pointer',
                    }}>Use a card instead</button>
                  </div>
                </div>
              )}
              {chargeFullAmount && forceFullPaymentMode && (
                <div style={{ marginTop: 10, fontSize: 11, color: PM2.muted, fontFamily: TM2.sans }}>
                  Switched to full-amount mode. <button onClick={swapBackToDepositMode} style={{ background: 'transparent', border: 'none', color: PM2.accent, padding: 0, cursor: 'pointer', textDecoration: 'underline' }}>Switch back to a 30% deposit</button>
                </div>
              )}
              <div style={{ marginTop: 10, fontSize: 11, color: PM2.muted, display: 'flex', gap: 6, alignItems: 'center', fontFamily: TM2.sans }}>
                <MIco name="shield" size={12}/>
                <span>Encrypted by Stripe · we never see your payment details.</span>
              </div>
            </div>

            {/* Promo code */}
            <div style={{ marginTop: 22 }}>
              {!promoOpen && !appliedPromo && (
                <button onClick={() => setPromoOpen(true)} style={{
                  background: 'transparent', border: 'none', padding: 0,
                  fontFamily: TM2.sans, fontSize: 13, color: PM2.accent, fontWeight: 500,
                  cursor: 'pointer', textDecoration: 'underline',
                }}>Have a promo code?</button>
              )}
              {(promoOpen || appliedPromo) && (
                <div>
                  <div style={{ fontSize: 11, letterSpacing: '0.06em', textTransform: 'uppercase', color: PM2.muted, marginBottom: 6, fontFamily: TM2.sans }}>Promo code</div>
                  {appliedPromo ? (
                    <div style={{
                      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                      padding: '12px 14px', borderRadius: 12,
                      background: PM2.surface, border: `1px solid ${PM2.success || '#3a6a32'}`,
                    }}>
                      <div style={{ fontFamily: TM2.sans, fontSize: 13, color: PM2.success || '#3a6a32', fontWeight: 500 }}>
                        ✓ {appliedPromo.code} applied — saved ${discount}
                      </div>
                      <button onClick={removePromo} aria-label="Remove code" style={{
                        background: 'transparent', border: 'none', color: PM2.muted, fontSize: 18,
                        cursor: 'pointer', padding: '0 4px', lineHeight: 1,
                      }}>×</button>
                    </div>
                  ) : (
                    <>
                      <div style={{ display: 'flex', gap: 8 }}>
                        <input
                          value={promoInput}
                          onChange={e => { setPromoInput(e.target.value.toUpperCase()); setPromoErr(''); }}
                          onKeyDown={e => { if (e.key === 'Enter') applyPromo(); }}
                          placeholder="WELCOME10"
                          style={{
                            flex: 1, padding: '12px 14px',
                            borderRadius: 10, border: `1px solid ${promoErr ? '#c0392b' : PM2.line}`,
                            background: PM2.surface, fontFamily: 'monospace', fontSize: 14,
                            color: PM2.ink, letterSpacing: '0.04em', outline: 'none', boxSizing: 'border-box',
                          }}/>
                        <button onClick={applyPromo} disabled={promoBusy || !promoInput.trim()} style={{
                          padding: '12px 18px', borderRadius: 10, border: 'none',
                          background: PM2.ink, color: '#fff', fontFamily: TM2.sans,
                          fontSize: 13, fontWeight: 500, cursor: 'pointer',
                          opacity: promoBusy || !promoInput.trim() ? 0.55 : 1,
                        }}>{promoBusy ? '…' : 'Apply'}</button>
                      </div>
                      {promoErr && (
                        <div style={{ marginTop: 6, fontSize: 12, color: '#c0392b', fontFamily: TM2.sans }}>{promoErr}</div>
                      )}
                    </>
                  )}
                </div>
              )}
            </div>

            {/* Price breakdown */}
            <div style={{
              marginTop: 24, padding: 16, borderRadius: 14,
              background: PM2.card, border: `0.5px solid ${PM2.line}`,
            }}>
              <div style={{ fontFamily: TM2.serif, fontSize: 16, color: PM2.ink, fontWeight: 500, marginBottom: 12 }}>Price details</div>
              <Row label={`$${p.priceFrom} × ${nights} nights`} value={`$${subtotal}`}/>
              <Row label="Cleaning fee" value={`$${cleaning}`}/>
              {upsellTotal > 0 && <Row label="Add-ons" value={`$${upsellTotal}`}/>}
              {appliedPromo && (
                <Row
                  label={<span style={{ color: PM2.success || '#3a6a32' }}>{`Promo · ${appliedPromo.code}`}</span>}
                  value={<span style={{ color: PM2.success || '#3a6a32' }}>{`−$${discount}`}</span>}
                />
              )}
              <div style={{ height: 0.5, background: PM2.line, margin: '8px 0' }}/>
              <Row label={<span style={{ fontWeight: 600, color: PM2.ink }}>Total (USD)</span>} value={<span style={{ fontWeight: 600, fontSize: 16, color: PM2.ink }}>${totalDollars}</span>} bold/>
              <div style={{ height: 0.5, background: PM2.lineSoft, margin: '8px 0' }}/>
              <Row
                label={<span style={{ color: PM2.accent }}>{chargeFullAmount ? 'Charged today' : 'Charged today (30%)'}</span>}
                value={<span style={{ color: PM2.accent, fontWeight: 600 }}>${deposit}</span>}/>
              {!chargeFullAmount && (
                <Row label={<span style={{ color: PM2.muted }}>Balance · 7d before check-in</span>} value={<span style={{ color: PM2.muted }}>${balance}</span>}/>
              )}
            </div>

            {/* Cancellation policy — surfaced before Pay so the guest sees
                the refund timeline (mirrors src/app/api/guest/cancel/route.ts
                via window.DyraCancellation). */}
            <MCheckoutCancellationPolicy
              chargedNowCents={(serverDepositCents != null ? serverDepositCents : Math.round((chargeFullAmount ? total : total * 0.3) * 100))}
              palette={PM2}
              type={TM2}
            />
          </div>
          );
        })()}

        {/* STEP 4: CONFIRMED */}
        {step === 4 && (
          <div style={{ padding: '40px 24px 32px', textAlign: 'center' }}>
            <div style={{
              width: 84, height: 84, borderRadius: 42, background: PM2.success,
              margin: '0 auto', display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              animation: 'mPop 400ms cubic-bezier(0.34, 1.56, 0.64, 1)',
            }}>
              <MIco name="check" size={48} color="#fff"/>
            </div>
            <div style={{
              fontFamily: TM2.serif, fontSize: 32, color: PM2.ink, fontWeight: 500,
              marginTop: 24, lineHeight: 1.1, letterSpacing: '-0.01em',
            }}>You're booked.</div>
            <div style={{ fontFamily: TM2.sans, fontSize: 14, color: PM2.muted, marginTop: 10, lineHeight: 1.5 }}>
              Confirmation sent to {guestInfo.email || 'your email'}.<br/>
              Check-in instructions arrive 48 hours before arrival.
            </div>

            <div style={{
              marginTop: 32, textAlign: 'left',
              padding: 18, borderRadius: 14,
              background: PM2.surface, border: `0.5px solid ${PM2.lineSoft}`,
            }}>
              <div style={{ fontFamily: TM2.sans, fontSize: 10, color: PM2.muted, letterSpacing: '0.18em', textTransform: 'uppercase' }}>Booking #DYR-26-0844</div>
              <div style={{ fontFamily: TM2.serif, fontSize: 18, color: PM2.ink, fontWeight: 500, marginTop: 6 }}>{p.name}</div>
              <div style={{ fontFamily: TM2.sans, fontSize: 13, color: PM2.muted, marginTop: 4 }}>May 8 – May 11 · 6 guests</div>
              <div style={{ height: 0.5, background: PM2.line, margin: '14px 0' }}/>
              <Row label="Deposit paid today" value={`$${Math.round(total * 0.3)}`} bold/>
              <Row label="Balance · Apr 30, 2026" value={`$${total - Math.round(total * 0.3)}`}/>
            </div>

            <div style={{ marginTop: 24, display: 'flex', flexDirection: 'column', gap: 10 }}>
              <MButton onClick={() => go('stays')}>View my stay</MButton>
              <MButton variant="secondary" onClick={() => go('home')}>Back to home</MButton>
            </div>
          </div>
        )}
        <style>{`@keyframes mPop { 0% { transform: scale(0); } to { transform: scale(1); } }`}</style>
      </div>

      {/* sticky CTA */}
      {step < 4 && (
        <div style={{
          padding: '12px 16px 28px',
          borderTop: `0.5px solid ${PM2.line}`,
          background: PM2.bg,
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <div>
            <div style={{ fontFamily: TM2.serif, fontSize: 18, color: PM2.ink, fontWeight: 500 }}>
              ${step === 3
                ? (serverDepositCents != null ? Math.round(serverDepositCents / 100) : Math.round(total * 0.3))
                : total}
            </div>
            <div style={{ fontFamily: TM2.sans, fontSize: 11, color: PM2.muted }}>
              {step === 3
                ? (chargeFullAmount
                    ? `Total · paid in full`
                    : `Deposit · total $${serverTotalCents != null ? Math.round(serverTotalCents / 100) : total}`)
                : `${nights} nights · total`}
            </div>
          </div>
          {(() => {
            // Step 2 requires the guest's name, email, and phone before
            // allowing Continue to payment. Email must look like an email
            // and phone must have at least 7 digits.
            const nameOk = guestInfo.name.trim().length > 0;
            const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(guestInfo.email.trim());
            const phoneOk = guestInfo.phone.replace(/\D/g, '').length >= 7;
            const step2Ready = nameOk && emailOk && phoneOk;
            // Step 3 requires the captcha to pass AND Stripe to be ready.
            // Card validity is enforced inside the Stripe Element — we only
            // gate the CTA on contact + captcha + element-mounted state and let
            // stripe.confirmPayment() surface specific errors.
            const step3Ready = captchaPassed && stripeReady && !processing;
            const ctaDisabled = (step === 3 && !step3Ready) || (step === 2 && !step2Ready);
            return (
          <div style={{ flex: 1 }}>
            <MButton
              disabled={ctaDisabled}
              onClick={() => {
                if (step === 3) {
                  handleConfirmPay();
                } else {
                  next();
                }
              }}>
              {step === 1 && 'Continue'}
              {step === 2 && 'Continue to payment'}
              {step === 3 && (() => {
                if (processing) return 'Processing…';
                const amt = serverDepositCents != null
                  ? Math.round(serverDepositCents / 100)
                  : Math.round(total * 0.3);
                return chargeFullAmount ? `Pay $${amt} now` : `Pay deposit · $${amt}`;
              })()}
            </MButton>
          </div>
            );
          })()}
        </div>
      )}
    </div>
  );
}

function MField({ label, value, onChange, placeholder, type = 'text', multiline, rows = 3 }) {
  const baseStyle = {
    width: '100%', padding: '14px 16px',
    borderRadius: 12, border: `1px solid ${PM2.line}`,
    background: PM2.surface,
    fontFamily: TM2.sans, fontSize: 16, color: PM2.ink,
    outline: 'none', boxSizing: 'border-box',
  };
  return (
    <label style={{ display: 'block' }}>
      <div style={{ fontFamily: TM2.sans, fontSize: 11, color: PM2.muted, letterSpacing: '0.04em', textTransform: 'uppercase', marginBottom: 6 }}>{label}</div>
      {multiline ? (
        <textarea
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder={placeholder}
          rows={rows}
          style={{ ...baseStyle, resize: 'vertical', minHeight: 88, lineHeight: 1.45 }}
        />
      ) : (
        <input
          type={type}
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder={placeholder}
          style={{ ...baseStyle, height: 50, padding: '0 16px' }}
        />
      )}
    </label>
  );
}

function Row({ label, value, bold }) {
  return (
    <div style={{
      display: 'flex', justifyContent: 'space-between', padding: '6px 0',
      fontFamily: TM2.sans, fontSize: 13, color: PM2.inkSoft,
    }}>
      <span>{label}</span><span style={{ color: PM2.ink, fontWeight: bold ? 600 : 400 }}>{value}</span>
    </div>
  );
}

Object.assign(window, { MListing, MBooking });
