// Direction 1 — Guest Dashboard + Host/Admin

// ─────────────────────────────────────────────────────────────────────────
// Magic-link token plumbing
// ─────────────────────────────────────────────────────────────────────────
//
// The Stripe webhook (src/lib/email.ts → buildMyStaysUrl) sends guests to
// either:
//   /?screen=dashboard&token=<HMAC-signed token>          (desktop)
//   /m/index.html#stays?token=<HMAC-signed token>         (mobile)
//
// On desktop the token lives in window.location.search. We persist it to
// sessionStorage (per-tab, cleared on tab close) so navigating between
// screens within the same session keeps the booking visible without
// re-passing the token on every internal link.
//
// We do NOT cache the booking object itself — every render of the dashboard
// re-fetches /api/guest/booking. Booking totals can change (added upsells,
// refund, balance paid) and stale browser state would be confusing.
const D1_GUEST_TOKEN_KEY = 'dyra_guest_token';

function readGuestToken() {
  try {
    const sp = new URLSearchParams(window.location.search || '');
    const fromUrl = sp.get('token');
    if (fromUrl) {
      try { sessionStorage.setItem(D1_GUEST_TOKEN_KEY, fromUrl); } catch (_) {}
      return fromUrl;
    }
  } catch (_) {}
  try {
    return sessionStorage.getItem(D1_GUEST_TOKEN_KEY) || '';
  } catch (_) {
    return '';
  }
}

function clearGuestToken() {
  try { sessionStorage.removeItem(D1_GUEST_TOKEN_KEY); } catch (_) {}
}

// Format an ISO date (YYYY-MM-DD) into "Thu, May 14, 2026".
function fmtDashDate(iso) {
  if (!iso) return '';
  try {
    const d = new Date(String(iso).slice(0, 10) + 'T00:00:00');
    if (isNaN(d.getTime())) return String(iso);
    return d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' });
  } catch (e) { return String(iso); }
}

// Map an /api/guest/booking response into the legacy `guest` shape the
// existing D1Dashboard JSX reads. The legacy shape has a few fields that
// the API doesn't return (cleaning fee broken out, guest count breakdown,
// check-in/check-out times) — we leave those undefined or default and the
// JSX already falls back gracefully. See the report at the end of this
// task for the full mismatch list.
function mapBookingToGuest(b) {
  if (!b) return null;
  const props = b.properties || {};
  const fullName = String(b.guest_name || '').trim();
  const firstName = fullName.split(/\s+/)[0] || 'Guest';
  const upsellLines = Array.isArray(b.upsells) ? b.upsells : [];
  const upsellsForUi = upsellLines.map((u) => ({
    name: u.name || u.label || 'Add-on',
    detail: [u.variant_label, u.qty > 1 ? `×${u.qty}` : null, u.added_after_booking ? 'Added after booking' : 'Selected at booking']
      .filter(Boolean).join(' · '),
    price: Math.round((Number(u.line_cents || 0)) / 100),
  }));
  const upsellsTotal = upsellsForUi.reduce((s, u) => s + u.price, 0);
  const totalDollars = Math.round(Number(b.total_cents || 0) / 100);
  const depositDollars = Math.round(Number(b.deposit_paid_cents || 0) / 100);
  // Cleaning fee isn't broken out in the booking row, so lump everything
  // non-upsell into nightlyTotal. The "Cleaning fee" row is hidden when 0.
  const nightlyTotal = Math.max(0, totalDollars - upsellsTotal);
  const checkInIso = String(b.check_in || '').slice(0, 10);
  const checkOutIso = String(b.check_out || '').slice(0, 10);
  // Balance-due date defaults to 7 days before check-in (matches the
  // confirmation email + audit copy).
  let balanceDueDate = null;
  if (checkInIso) {
    const d = new Date(checkInIso + 'T00:00:00');
    if (!isNaN(d.getTime())) {
      d.setDate(d.getDate() - 7);
      balanceDueDate = d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
    }
  }
  const depositDate = b.confirmed_at || b.created_at;
  return {
    id: b.id,
    firstName,
    guestName: fullName || firstName,
    propertyId: props.slug || null,
    property: props.name || 'Your stay',
    propertyTagline: props.tagline || props.neighborhood || '',
    address: props.address_public || '',
    checkIn: { date: fmtDashDate(b.check_in), time: '3:00pm', iso: checkInIso },
    checkOut: { date: fmtDashDate(b.check_out), time: '11:00am', iso: checkOutIso },
    check_in: b.check_in,    // raw ISO — consumed by access-info gate
    check_out: b.check_out,
    nights: Number(b.nights) || Math.max(1, Math.round((new Date(checkOutIso) - new Date(checkInIso)) / 86400000) || 1),
    guests: { adults: Number(b.guests_count) || 1, children: 0, infants: 0 },
    confirmation: String(b.id || '').slice(0, 8).toUpperCase(),
    bookedAt: b.created_at ? new Date(b.created_at).getTime() : Date.now(),
    status: b.status,
    keyCode: b.lock_code || null,
    lock_code: b.lock_code || null,
    wifi: b.wifi_name ? { name: b.wifi_name, password: b.wifi_password || '' } : null,
    wifi_name: b.wifi_name || null,
    wifi_password: b.wifi_password || null,
    arrival_instructions: b.arrival_instructions || null,
    upsells: upsellsForUi,
    rawUpsells: upsellLines,                // preserves variant_id etc. for purchasedNames lookup
    pricing: {
      nightlyTotal,
      cleaningFee: 0,                       // API doesn't break this out — see report
      depositPaid: depositDollars,
      depositDate: depositDate ? fmtDashDate(depositDate) : null,
      balanceDue: balanceDueDate ? { date: balanceDueDate } : null,
      total: totalDollars,
      balance: Math.round(Number(b.balance_cents || 0) / 100),
      refunded: Math.round(Number(b.refunded_cents || 0) / 100),
    },
  };
}

function D1DashboardGate({ p, serif, sans, hebFont, go }) {
  // Three modes:
  //  (a) magic-link token in URL/sessionStorage → fetch real booking, render dashboard.
  //  (b) no token → render the email-recovery form (always returns "check your inbox" —
  //      the API never reveals whether the email matched a guest).
  //  (c) token failed (expired / forged / network) → fall back to (b) but show a banner.
  const initialToken = (typeof window !== 'undefined') ? readGuestToken() : '';
  const [token, setToken] = React.useState(initialToken);
  const [booking, setBooking] = React.useState(null);
  const [loading, setLoading] = React.useState(!!initialToken);
  const [tokenError, setTokenError] = React.useState('');

  // Email-recovery form state
  const [email, setEmail] = React.useState('');
  const [error, setError] = React.useState('');
  const [checking, setChecking] = React.useState(false);
  const [linkSentTo, setLinkSentTo] = React.useState('');

  // Load the booking whenever the token changes.
  React.useEffect(() => {
    if (!token) { setBooking(null); setLoading(false); return; }
    let cancelled = false;
    setLoading(true);
    setTokenError('');
    (async () => {
      try {
        const r = await fetch('/api/guest/booking?token=' + encodeURIComponent(token));
        const j = await r.json().catch(() => ({}));
        if (cancelled) return;
        if (!r.ok) {
          // Invalid / expired / mismatch — drop the bad token so the email
          // recovery form replaces the dashboard.
          clearGuestToken();
          setToken('');
          setBooking(null);
          setTokenError(j.error || 'This link has expired. Enter your email below to get a fresh one.');
          setLoading(false);
          return;
        }
        setBooking(j.booking || null);
        setLoading(false);
      } catch (e) {
        if (cancelled) return;
        setTokenError('Could not load your stay — check your connection and try again.');
        setLoading(false);
      }
    })();
    return () => { cancelled = true; };
  }, [token]);

  // Reload after a self-mutating action (add upsell, cancel) so totals refresh.
  const reloadBooking = React.useCallback(async () => {
    if (!token) return;
    try {
      const r = await fetch('/api/guest/booking?token=' + encodeURIComponent(token));
      const j = await r.json().catch(() => ({}));
      if (r.ok && j.booking) setBooking(j.booking);
    } catch (_) {}
  }, [token]);

  if (loading) {
    return (
      <div style={{ maxWidth: 480, margin: '0 auto', padding: '120px 32px 64px', textAlign: 'center', fontSize: 14, color: p.muted }}>
        Loading your stay…
      </div>
    );
  }

  if (token && booking) {
    const guest = mapBookingToGuest(booking);
    return (
      <D1Dashboard
        p={p} serif={serif} sans={sans} hebFont={hebFont} go={go}
        guest={guest}
        token={token}
        onBookingChange={(updated) => updated && setBooking(updated)}
        reloadBooking={reloadBooking}
        onSignOut={() => {
          clearGuestToken();
          setToken('');
          setBooking(null);
        }}
      />
    );
  }

  const handleSubmit = async (e) => {
    e?.preventDefault();
    const normalized = email.trim().toLowerCase();
    setError('');
    setChecking(true);
    try {
      const r = await fetch('/api/guest/recover-link', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: normalized }),
      });
      // Privacy-safe endpoint: always ok:true regardless of whether the email
      // matched a booking. Show the same success state either way so we
      // never reveal which emails are guests.
      if (!r.ok) throw new Error('Network error');
      setLinkSentTo(normalized);
    } catch (err) {
      setError('Could not reach the server. Please try again or WhatsApp 862-520-7797.');
    } finally {
      setChecking(false);
    }
  };

  if (linkSentTo) {
    return (
      <div style={{ maxWidth: 480, margin: '0 auto', padding: '80px 32px 64px', textAlign: 'center' }}>
        <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 16 }}>
          My stay
        </div>
        <h1 style={{ fontFamily: serif, fontSize: 44, fontWeight: 400, letterSpacing: '-0.02em', margin: '0 0 14px', lineHeight: 1.1 }}>
          Check your inbox
        </h1>
        <p style={{ fontSize: 15, color: p.muted, lineHeight: 1.6, margin: '0 0 20px' }}>
          If we have a reservation under <strong style={{ color: p.ink }}>{linkSentTo}</strong>, we just sent you a link to your stay.
        </p>
        <p style={{ fontSize: 14, color: p.ink, lineHeight: 1.6, margin: '0 0 14px' }}>
          Don't see it? <strong>Check your spam folder.</strong>
        </p>
        <p style={{ fontSize: 13, color: p.muted, lineHeight: 1.6, margin: '0 0 28px' }}>
          Still nothing after 5 minutes? WhatsApp{' '}
          <a href="https://wa.me/18625207797" style={{ color: p.accent }}>862-520-7797</a>.
        </p>
        <button onClick={() => { setLinkSentTo(''); setEmail(''); }} style={{
          background: 'transparent', border: `1px solid ${p.line}`, color: p.muted,
          fontSize: 12, letterSpacing: '0.12em', textTransform: 'uppercase',
          padding: '10px 18px', borderRadius: 999, cursor: 'pointer', fontFamily: 'inherit',
        }}>Use a different email</button>
      </div>
    );
  }

  return (
    <div style={{ maxWidth: 480, margin: '0 auto', padding: '80px 32px 64px' }}>
      <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 16, textAlign: 'center' }}>
        My stay
      </div>
      <h1 style={{ fontFamily: serif, fontSize: 44, fontWeight: 400, letterSpacing: '-0.02em', margin: '0 0 14px', textAlign: 'center', lineHeight: 1.1 }}>
        Find your reservation
      </h1>
      <p style={{ fontSize: 15, color: p.muted, lineHeight: 1.6, margin: '0 0 36px', textAlign: 'center' }}>
        Enter the email you used at booking. We'll send a fresh link to your stay — even if you've lost the original confirmation.
      </p>

      {tokenError && (
        <div style={{
          background: '#fbeae5', border: '1px solid #e9c4bd', borderRadius: 8,
          padding: '12px 14px', fontSize: 13, color: '#8c2c1f', lineHeight: 1.5, marginBottom: 18,
        }}>
          {tokenError}
        </div>
      )}

      <form onSubmit={handleSubmit} style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 12, padding: 28 }}>
        <label style={{ display: 'block' }}>
          <div style={{ fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 8 }}>Email on reservation</div>
          <input
            type="email" required
            value={email}
            onChange={e => { setEmail(e.target.value); if (error) setError(''); }}
            placeholder="you@example.com"
            style={{
              width: '100%', padding: '13px 14px', border: `1px solid ${error ? '#c0392b' : p.line}`,
              borderRadius: 8, fontSize: 15, fontFamily: 'inherit', background: p.bg, color: p.ink,
              boxSizing: 'border-box',
            }}
          />
        </label>
        {error && <div style={{ fontSize: 12, color: '#c0392b', marginTop: 10, lineHeight: 1.5 }}>{error}</div>}
        <button type="submit" disabled={checking} style={{
          width: '100%', background: p.accent, color: p.bg, border: 'none', padding: '14px', borderRadius: 8,
          fontSize: 14, cursor: checking ? 'wait' : 'pointer', marginTop: 18, opacity: checking ? 0.7 : 1,
        }}>
          {checking ? 'Sending link…' : 'Email me my access link'}
        </button>
      </form>

      <div style={{ fontSize: 12, color: p.muted, marginTop: 24, textAlign: 'center', lineHeight: 1.6 }}>
        Don't have a reservation yet? <a onClick={() => go('search')} style={{ color: p.accent, cursor: 'pointer', textDecoration: 'none' }}>Browse homes</a>
      </div>
    </div>
  );
}

function D1Dashboard({ p, serif, sans, hebFont, go, guest, token, reloadBooking, onSignOut }) {
  // `guest` comes from D1DashboardGate as the mapped /api/guest/booking
  // payload — the source of truth lives in Supabase. We just call
  // reloadBooking() after any mutation so the parent re-fetches.

  // Add-on cart. ADD only adds to a local cart — it does NOT charge.
  // Two explicit commit modes ("Add to balance" vs "Pay now") are the
  // only paths that mutate the booking, with a server-side request_id
  // for idempotency so a double-tap doesn't double-charge. Same model
  // as mobile MStayDetail.
  const [cart, setCart] = React.useState([]);
  const [showCart, setShowCart] = React.useState(false);
  const [committing, setCommitting] = React.useState(false);
  const [addUpsellMsg, setAddUpsellMsg] = React.useState('');
  const [showReceipt, setShowReceipt] = React.useState(false);
  const [showDates, setShowDates] = React.useState(false);
  const [showGuests, setShowGuests] = React.useState(false);

  function unitPriceCents(u) {
    if (typeof u?.price_cents === 'number') return u.price_cents;
    if (typeof u?.priceFrom === 'number') return u.priceFrom * 100;
    return 0;
  }
  function addToCart(u) {
    setAddUpsellMsg('');
    setCart(prev => {
      if (prev.some(x => x.upsell_id === u.id)) return prev;
      const perNight = !!(u.per_night || u.perNight);
      const nights = Math.max(1, Number(guest?.nights || 1));
      const unit = unitPriceCents(u);
      return [
        ...prev,
        {
          upsell_id: u.id,
          variant_id: null,
          qty: 1,
          name: u.name || 'Add-on',
          unit_cents: unit,
          per_night: perNight,
          line_cents: unit * (perNight ? nights : 1),
        },
      ];
    });
    setShowCart(true);
  }
  function removeFromCart(id) {
    setCart(prev => prev.filter(x => x.upsell_id !== id));
  }
  function genReqId() {
    if (typeof crypto !== 'undefined' && crypto.randomUUID) {
      try { return crypto.randomUUID(); } catch (_) {}
    }
    return 'r-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
  }
  async function commitCart(mode) {
    if (!token) { setAddUpsellMsg('Open this page from the link in your booking email to commit add-ons.'); return; }
    if (cart.length === 0 || committing) return;
    setCommitting(true);
    setAddUpsellMsg('');
    const requestId = genReqId();
    try {
      const r = await fetch('/api/guest/upsells', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          token, mode, request_id: requestId,
          cart: cart.map(c => ({ upsell_id: c.upsell_id, variant_id: c.variant_id || undefined, qty: c.qty || 1 })),
        }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) { setAddUpsellMsg(j.error || 'Could not add — try again.'); setCommitting(false); return; }
      setCart([]);
      setShowCart(false);
      if (typeof reloadBooking === 'function') await reloadBooking();
      setAddUpsellMsg(mode === 'pay_now'
        ? 'Charged to card on file · receipt emailed.'
        : 'Added to your balance · the host has been notified.');
    } catch (e) {
      setAddUpsellMsg((e && e.message) || 'Network error.');
    } finally {
      setCommitting(false);
    }
  }
  const cartCents = cart.reduce((s, x) => s + (x.line_cents || 0), 0);

  const guestName = guest?.firstName || 'Yosef';
  const propertyName = guest?.property || "The Rebbe's View";
  const nextParsha = window.DYRA.PARSHA[0];

  // True if any day from check-in through check-out (inclusive) is a Saturday.
  // Prefer ISO fields when present; fall back to parsing the display string
  // ("Thu, May 14, 2026") which `new Date()` handles natively.
  const stayHasShabbos = (() => {
    const startSrc = guest?.check_in_iso || guest?.checkIn?.iso || guest?.checkIn?.date;
    const endSrc   = guest?.check_out_iso || guest?.checkOut?.iso || guest?.checkOut?.date;
    if (!startSrc || !endSrc) return true; // unknown → don't hide
    const start = new Date(startSrc);
    const end   = new Date(endSrc);
    if (isNaN(start.getTime()) || isNaN(end.getTime())) return true;
    const ONE_DAY = 86400000;
    for (let t = start.getTime(); t <= end.getTime(); t += ONE_DAY) {
      if (new Date(t).getDay() === 6) return true;
    }
    return false;
  })();
  return (
    <>
    <div style={{ padding: '40px 48px 72px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 10 }}>
        <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent }}>
          Looking forward to your arrival, {guestName}
        </div>
        {onSignOut && (
          <button onClick={onSignOut} style={{
            background: 'transparent', border: `1px solid ${p.line}`, color: p.muted,
            fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase',
            padding: '6px 12px', borderRadius: 999, cursor: 'pointer', fontFamily: 'inherit',
          }}>Sign out</button>
        )}
      </div>
      <h1 style={{ fontFamily: serif, fontSize: 56, fontWeight: 400, letterSpacing: '-0.02em', margin: '0 0 8px' }}>
        Your stay at <em>{propertyName}</em>
      </h1>
      <div style={{ fontSize: 14, color: p.muted, marginBottom: 32 }}>
        {guest?.checkIn ? `Arriving ${guest.checkIn.date} · ${guest.nights} nights · Check-in ${guest.checkIn.time} · Check-out ${guest.checkOut.time}` : 'Arriving Thu, May 14 · 3 nights · Check-in 4:00pm · Check-out 11:00am'}
      </div>

      {/* RESERVATION DETAILS */}
      {guest && (
        <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 12, padding: 28, marginBottom: 32 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 20, gap: 12 }}>
            <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent }}>Reservation</div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              {guest.status && (() => {
                const s = String(guest.status).toLowerCase();
                const map = {
                  confirmed:    { label: 'Confirmed',  bg: '#e7f1d7', fg: '#385c11' },
                  pending:      { label: 'Pending',    bg: '#fdf3d2', fg: '#7a5c0d' },
                  cancelled:    { label: 'Cancelled',  bg: '#fbeae5', fg: '#8c2c1f' },
                  checked_in:   { label: 'Checked-in', bg: '#dde8f3', fg: '#214974' },
                  checked_out:  { label: 'Checked-out',bg: '#ece6da', fg: '#5d4d29' },
                  completed:    { label: 'Completed',  bg: '#ece6da', fg: '#5d4d29' },
                };
                const meta = map[s] || { label: guest.status, bg: p.bg, fg: p.muted };
                return (
                  <span style={{
                    fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase',
                    background: meta.bg, color: meta.fg,
                    padding: '4px 10px', borderRadius: 999, fontWeight: 600,
                  }}>{meta.label}</span>
                );
              })()}
              <div style={{ fontSize: 11, color: p.muted, fontFamily: 'monospace' }}>#{guest.confirmation}</div>
            </div>
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20, marginBottom: 24 }}>
            <div>
              <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 6 }}>Check-in</div>
              <div style={{ fontFamily: serif, fontSize: 22, lineHeight: 1.2 }}>{guest.checkIn.date}</div>
              <div style={{ fontSize: 13, color: p.muted, marginTop: 3 }}>From {guest.checkIn.time}</div>
            </div>
            <div>
              <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 6 }}>Check-out</div>
              <div style={{ fontFamily: serif, fontSize: 22, lineHeight: 1.2 }}>{guest.checkOut.date}</div>
              <div style={{ fontSize: 13, color: p.muted, marginTop: 3 }}>By {guest.checkOut.time}</div>
            </div>
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 16, paddingTop: 20, borderTop: `1px solid ${p.line}`, marginBottom: 24 }}>
            <div>
              <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 4 }}>Guests</div>
              <div style={{ fontSize: 14 }}>
                {guest.guests.adults} adults
                {guest.guests.children ? ` · ${guest.guests.children} children` : ''}
                {guest.guests.infants ? ` · ${guest.guests.infants} infants` : ''}
              </div>
            </div>
            <div>
              <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 4 }}>Length</div>
              <div style={{ fontSize: 14 }}>{guest.nights} nights</div>
            </div>
            <div>
              <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 4 }}>Address</div>
              <div style={{ fontSize: 13, lineHeight: 1.4 }}>{guest.address}</div>
            </div>
          </div>

          {guest.upsells && guest.upsells.length > 0 && (
            <div style={{ paddingTop: 20, borderTop: `1px solid ${p.line}` }}>
              <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 12 }}>Add-ons purchased</div>
              {guest.upsells.map((u, i) => (
                <div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '10px 0', borderTop: i === 0 ? 'none' : `1px solid ${p.line}` }}>
                  <div>
                    <div style={{ fontSize: 14 }}>{u.name}</div>
                    <div style={{ fontSize: 12, color: p.muted, marginTop: 2 }}>{u.detail}</div>
                  </div>
                  <div style={{ fontSize: 14, fontFamily: serif }}>${u.price}</div>
                </div>
              ))}
            </div>
          )}

          {guest.pricing && (() => {
            const upsellsTotal = (guest.upsells || []).reduce((s, u) => s + u.price, 0);
            const grandTotal = guest.pricing.nightlyTotal + upsellsTotal + guest.pricing.cleaningFee;
            const deposit = guest.pricing.depositPaid || 0;
            const finalized = deposit >= grandTotal;
            const remaining = Math.max(0, grandTotal - deposit);
            return (
              <div style={{ paddingTop: 20, borderTop: `1px solid ${p.line}`, marginTop: 6 }}>
                <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 10 }}>
                  {finalized ? 'Total paid' : 'Payment'}
                </div>
                <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, color: p.muted, padding: '4px 0' }}>
                  <span>{guest.nights} nights</span>
                  <span>${guest.pricing.nightlyTotal}</span>
                </div>
                {upsellsTotal > 0 && (
                  <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, color: p.muted, padding: '4px 0' }}>
                    <span>Add-ons</span>
                    <span>${upsellsTotal}</span>
                  </div>
                )}
                {guest.pricing.cleaningFee > 0 && (
                  <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, color: p.muted, padding: '4px 0' }}>
                    <span>Cleaning fee</span>
                    <span>${guest.pricing.cleaningFee}</span>
                  </div>
                )}
                <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 16, fontFamily: serif, paddingTop: 10, marginTop: 8, borderTop: `1px solid ${p.line}` }}>
                  <span>{finalized ? 'Total paid' : 'Reservation total'}</span>
                  <span><strong>${grandTotal}</strong></span>
                </div>
                {!finalized && (
                  <div style={{ marginTop: 14, padding: 14, background: p.bg, borderRadius: 8 }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, padding: '4px 0' }}>
                      <span style={{ color: p.muted }}>Deposit paid{guest.pricing.depositDate ? ` · ${guest.pricing.depositDate}` : ''}</span>
                      <span>− ${deposit}</span>
                    </div>
                    <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 14, fontWeight: 500, paddingTop: 8, marginTop: 4, borderTop: `1px solid ${p.line}` }}>
                      <span>Balance due{guest.pricing.balanceDue && guest.pricing.balanceDue.date ? ` by ${guest.pricing.balanceDue.date}` : ''}</span>
                      <span style={{ color: p.accent }}>${remaining}</span>
                    </div>
                    <button onClick={() => go('payBalance')} style={{
                      width: '100%', background: p.accent, color: p.bg, border: 'none', padding: '11px', borderRadius: 6,
                      fontSize: 13, cursor: 'pointer', marginTop: 12,
                    }}>Pay remaining balance</button>
                  </div>
                )}

                {/* Cancellation policy + cancel action (Manage trip). */}
                <D1CancelBookingCard p={p} serif={serif} guest={guest} token={token} reloadBooking={reloadBooking}/>
              </div>
            );
          })()}
        </div>
      )}

      <div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: 32 }}>
        <div>
          {/* SHABBOS CARD — only if stay covers a Saturday */}
          {stayHasShabbos && (
            <div style={{ background: p.ink, color: p.bg, borderRadius: 12, padding: 32, marginBottom: 24 }}>
              <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', opacity: 0.65, marginBottom: 16 }}>The shabbos of your stay</div>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 20 }}>
                <div>
                  <div style={{ fontFamily: serif, fontSize: 44, fontWeight: 400 }}><em style={{ color: p.accent }}>Parshas {nextParsha.parsha}</em></div>
                  <div style={{ fontFamily: hebFont, fontSize: 18, opacity: 0.85, direction: 'rtl', marginTop: 4 }}>פרשת אמור</div>
                </div>
                <div style={{ textAlign: 'right', fontSize: 13, opacity: 0.8 }}>
                  {nextParsha.date}<br/>{nextParsha.hebDate}
                </div>
              </div>
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 16, padding: 18, background: 'rgba(255,255,255,0.06)', borderRadius: 8 }}>
                <div><div style={{ fontSize: 10, opacity: 0.6, letterSpacing: '0.15em', textTransform: 'uppercase' }}>Candle</div><div style={{ fontSize: 20, fontFamily: serif, marginTop: 4 }}>{nextParsha.candleLight}</div></div>
                <div><div style={{ fontSize: 10, opacity: 0.6, letterSpacing: '0.15em', textTransform: 'uppercase' }}>Shkiah</div><div style={{ fontSize: 20, fontFamily: serif, marginTop: 4 }}>7:56pm</div></div>
                <div><div style={{ fontSize: 10, opacity: 0.6, letterSpacing: '0.15em', textTransform: 'uppercase' }}>Havdalah</div><div style={{ fontSize: 20, fontFamily: serif, marginTop: 4 }}>{nextParsha.havdalah}</div></div>
              </div>
            </div>
          )}

          {/* PREPARING removed per spec */}

          {/* HOUSE GUIDE — 3 boxes */}
          <div>
            <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 14 }}>House guide</div>
            <div style={{ display: 'grid', gap: 12 }}>

              {/* 1. INSTRUCTIONS & ACCESS — released 24h before arrival once the
                  booking is confirmed. Build #4 will tighten this with payment status. */}
              {(() => {
                const checkInISO = (() => {
                  if (guest?.check_in) return String(guest.check_in).slice(0, 10);
                  if (guest?.checkIn?.iso) return String(guest.checkIn.iso).slice(0, 10);
                  if (guest?.checkIn?.date) {
                    const d = new Date(guest.checkIn.date);
                    if (!isNaN(d.getTime())) return d.toISOString().slice(0, 10);
                  }
                  return '';
                })();
                const isConfirmed = String(guest?.status || '').toLowerCase() === 'confirmed'
                                 || String(guest?.status || '').toLowerCase() === 'checked_in'
                                 || String(guest?.status || '').toLowerCase() === 'checked_out';
                let withinWindow = false;
                if (checkInISO) {
                  const ms = new Date(checkInISO + 'T00:00:00').getTime() - Date.now();
                  withinWindow = ms <= 24 * 60 * 60 * 1000; // 24h before check-in or later
                }
                const unlocked = isConfirmed && withinWindow;

                if (!unlocked) {
                  return (
                    <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10, padding: 22 }}>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
                        <Ico name="key" size={18} style={{ color: p.accent }}/>
                        <div style={{ fontFamily: serif, fontSize: 20 }}>Instructions &amp; access</div>
                      </div>
                      <div style={{
                        padding: 18, background: p.bg, borderRadius: 8, fontSize: 13, color: p.muted, lineHeight: 1.6,
                        display: 'flex', alignItems: 'flex-start', gap: 12,
                      }}>
                        <Ico name="clock" size={16} style={{ color: p.accent, flexShrink: 0, marginTop: 2 }}/>
                        <span>Released 24h before arrival once payment is complete.</span>
                      </div>
                    </div>
                  );
                }
                return (
                  <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10, padding: 22 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
                      <Ico name="key" size={18} style={{ color: p.accent }}/>
                      <div style={{ fontFamily: serif, fontSize: 20 }}>Instructions &amp; access</div>
                    </div>
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, fontSize: 13, lineHeight: 1.5 }}>
                      <div>
                        <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 3 }}>Check-in</div>
                        <div>{guest?.checkIn?.date} · from {guest?.checkIn?.time}</div>
                      </div>
                      <div>
                        <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 3 }}>Check-out</div>
                        <div>{guest?.checkOut?.date} · by {guest?.checkOut?.time}</div>
                      </div>
                      <div>
                        <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 3 }}>Door code</div>
                        <div style={{ fontFamily: 'monospace', fontSize: 15 }}>{guest?.lock_code || guest?.keyCode || '—'}</div>
                      </div>
                      <div>
                        <div style={{ fontSize: 10, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 3 }}>Wifi</div>
                        <div>{guest?.wifi_name || guest?.wifi?.name || '—'}</div>
                        <div style={{ color: p.muted, fontSize: 12 }}>pw: {guest?.wifi_password || guest?.wifi?.password || '—'}</div>
                      </div>
                    </div>
                    {(guest?.arrival_instructions) && (
                      <div style={{ marginTop: 14, paddingTop: 14, borderTop: `1px solid ${p.line}`, fontSize: 13, lineHeight: 1.55, whiteSpace: 'pre-wrap' }}>
                        {guest.arrival_instructions}
                      </div>
                    )}
                  </div>
                );
              })()}

              {/* 2. NEARBY SHULS & MINYANIM */}
              <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10, padding: 22 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
                  <Ico name="pin" size={18} style={{ color: p.accent }}/>
                  <div style={{ fontFamily: serif, fontSize: 20 }}>Nearby shuls &amp; minyanim</div>
                </div>
                <div style={{ display: 'grid', gap: 8 }}>
                  {[
                    { name: '770 Eastern Pkwy',           detail: 'Main shul · across the street',       times: 'Shacharis 7am, 8am, 9am, 10am' },
                    { name: 'Empire Shtiebel',            detail: '1 block · Empire Blvd & Albany',      times: 'Mincha/Maariv every 20 min from 6pm' },
                    { name: 'Heichal Menachem',           detail: '2 blocks · 599 Empire',                times: 'Vasikin minyan · sunrise' },
                    { name: 'Mikvah Chaya Mushka',        detail: '5 min walk · 1525 Carroll',            times: 'Open 4:30pm – 11pm Sun–Thu' },
                  ].map((m, i) => (
                    <div key={i} style={{ padding: '10px 12px', background: p.bg, borderRadius: 6 }}>
                      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                        <div style={{ fontSize: 14, fontWeight: 500 }}>{m.name}</div>
                        <div style={{ fontSize: 11, color: p.muted }}>{m.detail}</div>
                      </div>
                      <div style={{ fontSize: 12, color: p.muted, marginTop: 3 }}>{m.times}</div>
                    </div>
                  ))}
                </div>
              </div>

              {/* 3. EMERGENCY CONTACT */}
              <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10, padding: 22 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
                  <Ico name="shield" size={18} style={{ color: p.accent }}/>
                  <div style={{ fontFamily: serif, fontSize: 20 }}>Emergency contact</div>
                </div>
                <div style={{ display: 'grid', gap: 10, fontSize: 13 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '8px 0' }}>
                    <div>
                      <div style={{ fontWeight: 500 }}>Abe (host)</div>
                      <div style={{ fontSize: 11, color: p.muted, marginTop: 2 }}>WhatsApp only</div>
                    </div>
                    <a href="https://wa.me/18625207797" style={{ color: p.accent, textDecoration: 'none', fontFamily: 'monospace' }}>862-520-7797</a>
                  </div>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '8px 0', borderTop: `1px solid ${p.line}` }}>
                    <div>
                      <div style={{ fontWeight: 500 }}>Hatzalah · Crown Heights</div>
                      <div style={{ fontSize: 11, color: p.muted, marginTop: 2 }}>Medical emergencies</div>
                    </div>
                    <a href="tel:+17188947676" style={{ color: p.accent, textDecoration: 'none', fontFamily: 'monospace' }}>718-387-1750</a>
                  </div>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '8px 0', borderTop: `1px solid ${p.line}` }}>
                    <div>
                      <div style={{ fontWeight: 500 }}>Shomrim · Crown Heights</div>
                      <div style={{ fontSize: 11, color: p.muted, marginTop: 2 }}>Safety &amp; security patrol</div>
                    </div>
                    <a href="tel:+17184083100" style={{ color: p.accent, textDecoration: 'none', fontFamily: 'monospace' }}>718-774-3333</a>
                  </div>
                  <div style={{ paddingTop: 10, marginTop: 4, borderTop: `1px solid ${p.line}`, fontSize: 11, color: p.muted, lineHeight: 1.5 }}>
                    For life-threatening emergencies, call <strong style={{ color: p.ink }}>911</strong> first, then notify the host.
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>

        {/* RIGHT COLUMN */}
        <div>
          {/* PROPERTY */}
          {(() => {
            const propData = (window.DYRA?.PROPERTIES || []).find(pp => pp.id === guest?.propertyId);
            const heroPhoto = propData?.photos?.[0];
            return (
              <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10, overflow: 'hidden', marginBottom: 20 }}>
                <Placeholder
                  src={heroPhoto?.src}
                  objectPosition={heroPhoto?.pos || 'center'}
                  label={propData?.hero || propertyName}
                  tone="clay" density="tight"
                  style={{ aspectRatio: '4/3' }}
                />
                <div style={{ padding: 16 }}>
                  <div style={{ fontFamily: serif, fontSize: 20, fontWeight: 500 }}>{propertyName}</div>
                  <div style={{ fontSize: 12, color: p.muted, marginTop: 2 }}>{guest?.propertyTagline || 'Penthouse · directly across from 770'}</div>
                  <div style={{ display: 'flex', gap: 6, marginTop: 14 }}>
                    {(() => {
                      const ctxName = propertyName || guest?.confirmation || 'your reservation';
                      const waHref = `https://wa.me/18625207797?text=${encodeURIComponent(`Hi, I'm messaging about my reservation at ${ctxName}...`)}`;
                      return (
                        <a href={waHref} target="_blank" rel="noopener" style={{
                          flex: 1, background: p.ink, color: p.bg, border: 'none', padding: '10px', borderRadius: 6,
                          fontSize: 12, cursor: 'pointer', textAlign: 'center', textDecoration: 'none',
                        }}>Message host</a>
                      );
                    })()}
                  </div>
                </div>
              </div>
            );
          })()}

          {/* ADD MORE — only upsells available at booking that weren't purchased; gated to ≥3 days before arrival */}
          {(() => {
            const msPerDay = 86400000;
            const checkInISO = guest?.check_in
              ? String(guest.check_in).slice(0, 10)
              : (guest?.checkIn?.iso ? String(guest.checkIn.iso).slice(0, 10) : null);
            let upgradesOpen = true;
            if (checkInISO) {
              const daysUntil = Math.round((new Date(checkInISO + 'T00:00:00') - Date.now()) / msPerDay);
              upgradesOpen = daysUntil >= 3;
            }
            // Match purchased upsells by id (preferred) or name (fallback).
            const purchasedIds = new Set(
              (guest?.rawUpsells || []).map(u => String(u.id || '')).filter(Boolean)
            );
            const purchasedNames = new Set(
              (guest?.upsells || []).map(u => String(u.name || '').toLowerCase())
            );
            const available = (window.DYRA?.UPSELLS || []).filter(u => {
              if (purchasedIds.has(String(u.id))) return false;
              return !purchasedNames.has(String(u.name || '').toLowerCase());
            }).slice(0, 4);
            if (available.length === 0) return null;
            return (
              <div style={{ border: `1px dashed ${p.line}`, borderRadius: 10, padding: 20, opacity: upgradesOpen ? 1 : 0.65 }}>
                <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.muted, marginBottom: 8 }}>
                  {upgradesOpen ? 'Add before you arrive' : 'Upgrades closed'}
                </div>
                <div style={{ fontFamily: serif, fontSize: 20, marginBottom: 14 }}>
                  {upgradesOpen ? 'Still time to add' : 'Window has closed'}
                </div>
                {!upgradesOpen && (
                  <div style={{ fontSize: 12, color: p.muted, lineHeight: 1.6, marginBottom: 14 }}>
                    Add-ons close 3 days before arrival so we have time to prepare. Contact your host directly if you need something last-minute.
                  </div>
                )}
                {available.map((u, i) => {
                  const inCart = cart.some(c => c.upsell_id === u.id);
                  return (
                    <div key={u.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 0', borderTop: `1px solid ${p.line}` }}>
                      <span style={{ fontSize: 13 }}>{u.name}</span>
                      <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
                        <span style={{ fontSize: 12, color: p.muted }}>+${u.priceFrom}</span>
                        {inCart ? (
                          <button disabled={!upgradesOpen} onClick={() => removeFromCart(u.id)} style={{
                            background: 'transparent', border: `1px solid ${p.line}`, padding: '3px 10px', borderRadius: 999,
                            fontSize: 11, cursor: upgradesOpen ? 'pointer' : 'not-allowed', color: p.muted,
                          }}>Remove</button>
                        ) : (
                          <button disabled={!upgradesOpen} onClick={() => addToCart(u)} style={{
                            background: 'transparent', border: `1px solid ${p.line}`, padding: '3px 10px', borderRadius: 999,
                            fontSize: 11, cursor: upgradesOpen ? 'pointer' : 'not-allowed', color: upgradesOpen ? p.ink : p.muted,
                          }}>Add</button>
                        )}
                      </div>
                    </div>
                  );
                })}
                {cart.length > 0 && (
                  <button onClick={() => setShowCart(true)} style={{
                    width: '100%', marginTop: 12, padding: '10px 14px', borderRadius: 8,
                    background: p.ink, color: p.bg, border: 'none', cursor: 'pointer',
                    display: 'flex', justifyContent: 'space-between', alignItems: 'center',
                    fontSize: 13, fontFamily: 'inherit',
                  }}>
                    <span>{cart.length} item{cart.length === 1 ? '' : 's'} in cart · ${Math.round(cartCents/100)}</span>
                    <span style={{ opacity: 0.85 }}>Review →</span>
                  </button>
                )}
                {addUpsellMsg && (
                  <div style={{ marginTop: 10, fontSize: 11, color: p.muted, lineHeight: 1.5 }}>{addUpsellMsg}</div>
                )}
              </div>
            );
          })()}

          {/* Manage trip — wires Receipt + Modify dates + Guest count alongside the existing Cancel card. */}
          <div style={{ marginTop: 24, padding: 20, border: `1px solid ${p.line}`, borderRadius: 10 }}>
            <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.muted, marginBottom: 14 }}>Manage trip</div>
            {[
              { l: 'Modify dates',       onClick: () => setShowDates(true) },
              { l: 'Update guest count', onClick: () => setShowGuests(true) },
              { l: 'Receipt & charges',  onClick: () => setShowReceipt(true) },
            ].map((row, i) => (
              <button key={i} onClick={row.onClick} style={{
                width: '100%', padding: '12px 0', textAlign: 'left',
                background: 'transparent', border: 'none',
                borderTop: i === 0 ? 'none' : `1px solid ${p.line}`,
                fontSize: 13, color: p.ink, cursor: 'pointer',
                display: 'flex', justifyContent: 'space-between', alignItems: 'center',
              }}>
                <span>{row.l}</span>
                <span style={{ color: p.muted, fontSize: 12 }}>›</span>
              </button>
            ))}
          </div>
        </div>
      </div>

    </div>
    <D1CartModal open={showCart} onClose={() => setShowCart(false)} cart={cart} cartCents={cartCents}
      committing={committing} onRemove={removeFromCart} onCommit={commitCart} guest={guest} p={p} serif={serif}/>
    <D1ReceiptModal open={showReceipt} onClose={() => setShowReceipt(false)} token={token} p={p} serif={serif}/>
    <D1ModifyDatesModal open={showDates} onClose={() => setShowDates(false)} token={token} guest={guest} p={p} serif={serif}/>
    <D1GuestCountModal open={showGuests} onClose={() => setShowGuests(false)} token={token} guest={guest}
      onUpdated={() => { if (typeof reloadBooking === 'function') reloadBooking(); }} p={p} serif={serif}/>
    </>
  );
}

// ─────── Add-on cart + Manage trip modals ───────

function D1Modal({ open, onClose, title, children, p, serif, maxWidth = 480 }) {
  if (!open) return null;
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, zIndex: 200,
      background: 'rgba(20,15,10,0.45)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: p.card, borderRadius: 12, padding: 28,
        maxWidth, width: '100%', maxHeight: '85vh', overflow: 'auto',
        border: `1px solid ${p.line}`,
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 18 }}>
          <div style={{ fontFamily: serif, fontSize: 22, color: p.ink }}>{title}</div>
          <button onClick={onClose} style={{
            background: 'transparent', border: 'none', cursor: 'pointer', fontSize: 22, color: p.muted, padding: 0,
          }} aria-label="Close">×</button>
        </div>
        {children}
      </div>
    </div>
  );
}

function D1CartModal({ open, onClose, cart, cartCents, committing, onRemove, onCommit, guest, p, serif }) {
  const balanceFullyPaid = !guest?.pricing?.balanceDue;
  return (
    <D1Modal open={open} onClose={onClose} title="Your add-on cart" p={p} serif={serif}>
      {cart.length === 0 ? (
        <div style={{ padding: '20px 0', color: p.muted, fontSize: 13 }}>Your cart is empty.</div>
      ) : (
        <>
          <div style={{ background: p.bg, borderRadius: 8 }}>
            {cart.map((c, i) => (
              <div key={c.upsell_id} style={{
                display: 'flex', justifyContent: 'space-between', alignItems: 'center',
                padding: '12px 14px', borderTop: i === 0 ? 'none' : `1px solid ${p.line}`, gap: 12,
              }}>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 14 }}>{c.name}</div>
                  <div style={{ fontSize: 11, color: p.muted, marginTop: 2 }}>
                    {c.per_night ? `$${Math.round(c.unit_cents/100)} / night` : `$${Math.round(c.unit_cents/100)}`}
                  </div>
                </div>
                <div style={{ fontFamily: serif }}>${Math.round(c.line_cents/100)}</div>
                <button onClick={() => onRemove(c.upsell_id)} style={{
                  background: 'transparent', border: 'none', cursor: 'pointer', color: p.muted, fontSize: 18,
                }} aria-label="Remove">×</button>
              </div>
            ))}
          </div>
          <div style={{
            marginTop: 14, padding: '12px 14px', background: p.bg, borderRadius: 8,
            display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
          }}>
            <span style={{ color: p.muted }}>Cart total</span>
            <strong style={{ fontFamily: serif }}>${Math.round(cartCents/100)}</strong>
          </div>
          <div style={{ marginTop: 18, display: 'flex', flexDirection: 'column', gap: 10 }}>
            {!balanceFullyPaid && (
              <button disabled={committing} onClick={() => onCommit('add_to_balance')} style={{
                background: 'transparent', color: p.ink, border: `1px solid ${p.line}`,
                padding: '12px 18px', borderRadius: 8, fontSize: 13, cursor: committing ? 'wait' : 'pointer',
                opacity: committing ? 0.6 : 1,
              }}>{committing ? 'Adding…' : 'Add to balance'}</button>
            )}
            <button disabled={committing} onClick={() => onCommit('pay_now')} style={{
              background: p.accent, color: p.bg, border: 'none',
              padding: '12px 18px', borderRadius: 8, fontSize: 13, cursor: committing ? 'wait' : 'pointer',
              opacity: committing ? 0.6 : 1,
            }}>{committing ? 'Charging…' : (balanceFullyPaid ? `Pay $${Math.round(cartCents/100)}` : `Pay now $${Math.round(cartCents/100)}`)}</button>
          </div>
          <div style={{ marginTop: 12, fontSize: 11, color: p.muted, lineHeight: 1.5 }}>
            Pay now charges your card on file. Add to balance bumps your reservation total — no charge until your normal balance-due date.
          </div>
        </>
      )}
    </D1Modal>
  );
}

function D1ReceiptModal({ open, onClose, token, p, serif }) {
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  React.useEffect(() => {
    if (!open || !token) return;
    let cancelled = false;
    (async () => {
      setLoading(true); setErr('');
      try {
        const r = await fetch('/api/guest/receipt?token=' + encodeURIComponent(token));
        const j = await r.json().catch(() => ({}));
        if (cancelled) return;
        if (!r.ok) { setErr(j.error || 'Could not load receipt.'); setLoading(false); return; }
        setData(j); setLoading(false);
      } catch (e) {
        if (!cancelled) { setErr((e && e.message) || 'Network error.'); setLoading(false); }
      }
    })();
    return () => { cancelled = true; };
  }, [open, token]);
  return (
    <D1Modal open={open} onClose={onClose} title="Receipt & charges" p={p} serif={serif}>
      {loading ? (
        <div style={{ padding: 30, textAlign: 'center', color: p.muted }}>Loading…</div>
      ) : err ? (
        <div style={{ padding: 20, fontSize: 13, color: '#c0392b' }}>{err}</div>
      ) : !data ? null : (
        <>
          <div style={{ fontSize: 12, color: p.muted, marginBottom: 14 }}>
            {data.booking.property} · {data.booking.check_in} → {data.booking.check_out}
          </div>
          <div style={{ background: p.bg, borderRadius: 8 }}>
            {data.lineItems.map((row, i) => (
              <div key={i} style={{
                display: 'flex', justifyContent: 'space-between', padding: '12px 14px',
                borderTop: i === 0 ? 'none' : `1px solid ${p.line}`, gap: 12,
              }}>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 14 }}>{row.label}</div>
                  {row.sublabel && <div style={{ fontSize: 11, color: p.muted, marginTop: 2 }}>{row.sublabel}</div>}
                </div>
                <div style={{ fontSize: 14 }}>${Math.round(row.cents/100)}</div>
              </div>
            ))}
            <div style={{
              display: 'flex', justifyContent: 'space-between', padding: '12px 14px',
              borderTop: `1px solid ${p.line}`, fontFamily: serif, fontSize: 16,
            }}>
              <span>Total</span>
              <span><strong>${Math.round(data.totals.total_cents/100)}</strong></span>
            </div>
          </div>
          <div style={{ marginTop: 18, fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: p.muted }}>Payments</div>
          <div style={{ background: p.bg, borderRadius: 8, marginTop: 8 }}>
            {data.payments.length === 0 ? (
              <div style={{ padding: '12px 14px', color: p.muted, fontSize: 13 }}>No charges yet.</div>
            ) : data.payments.map((pay, i) => (
              <div key={pay.id || i} style={{
                display: 'flex', justifyContent: 'space-between', padding: '10px 14px',
                borderTop: i === 0 ? 'none' : `1px solid ${p.line}`, fontSize: 13,
              }}>
                <div>
                  <div>{pay.label}</div>
                  <div style={{ color: p.muted, fontSize: 11, marginTop: 2 }}>{(pay.date || '').slice(0,10)}</div>
                </div>
                <div style={{ color: pay.cents < 0 ? '#c0392b' : p.ink }}>
                  {pay.cents < 0 ? '−' : ''}${Math.round(Math.abs(pay.cents)/100)}
                </div>
              </div>
            ))}
            <div style={{
              display: 'flex', justifyContent: 'space-between', padding: '10px 14px',
              borderTop: `1px solid ${p.line}`, fontFamily: serif, fontSize: 14,
            }}>
              <span>Balance remaining</span>
              <span>${Math.round((data.balanceRemainingCents || 0)/100)}</span>
            </div>
          </div>
          {data.paymentMethod && (
            <div style={{ marginTop: 14, fontSize: 11, color: p.muted, textAlign: 'center' }}>
              Card on file: {data.paymentMethod.brand || 'card'} ····{data.paymentMethod.last4 || '—'}
            </div>
          )}
        </>
      )}
    </D1Modal>
  );
}

function D1ModifyDatesModal({ open, onClose, token, guest, p, serif }) {
  const [ci, setCi] = React.useState('');
  const [co, setCo] = React.useState('');
  const [notes, setNotes] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [msg, setMsg] = React.useState('');
  React.useEffect(() => {
    if (!open) return;
    setCi((guest?.check_in || guest?.checkIn?.iso || '').slice(0,10));
    setCo((guest?.check_out || guest?.checkOut?.iso || '').slice(0,10));
    setNotes(''); setMsg('');
  }, [open, guest]);
  async function submit() {
    if (!token) { setMsg('Open this page from your booking email to request changes.'); return; }
    if (!ci || !co || co <= ci) { setMsg('Pick a valid check-in / check-out.'); return; }
    setBusy(true); setMsg('');
    try {
      const r = await fetch('/api/guest/modify-dates', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token, check_in: ci, check_out: co, notes }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) { setMsg(j.error || 'Could not file the request.'); setBusy(false); return; }
      setMsg('Request received — the host will reply within a few hours.');
    } catch (e) {
      setMsg((e && e.message) || 'Network error.');
    } finally {
      setBusy(false);
    }
  }
  const inputStyle = {
    width: '100%', boxSizing: 'border-box', padding: '11px 13px',
    border: `1px solid ${p.line}`, borderRadius: 6, background: p.bg,
    fontSize: 14, color: p.ink, fontFamily: 'inherit', marginTop: 6,
  };
  return (
    <D1Modal open={open} onClose={onClose} title="Request a date change" p={p} serif={serif}>
      <div style={{ fontSize: 13, color: p.muted, lineHeight: 1.6, marginBottom: 14 }}>
        We'll check availability + pricing for the new dates and reply directly. Nothing changes on your booking until the host confirms.
      </div>
      <label style={{ display: 'block', fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted }}>
        New check-in
        <input type="date" value={ci} onChange={(e) => setCi(e.target.value)} style={inputStyle}/>
      </label>
      <label style={{ display: 'block', marginTop: 12, fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted }}>
        New check-out
        <input type="date" value={co} onChange={(e) => setCo(e.target.value)} style={inputStyle}/>
      </label>
      <label style={{ display: 'block', marginTop: 12, fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted }}>
        Note for the host (optional)
        <textarea value={notes} onChange={(e) => setNotes(e.target.value)} rows={3}
          style={{ ...inputStyle, resize: 'vertical' }}
          placeholder="Anything we should know — flight changes, simcha dates, etc."/>
      </label>
      <button disabled={busy} onClick={submit} style={{
        width: '100%', marginTop: 18, padding: '11px', borderRadius: 8,
        background: p.accent, color: p.bg, border: 'none', fontSize: 13,
        cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1,
      }}>{busy ? 'Sending…' : 'Send request to host'}</button>
      {msg && <div style={{ marginTop: 12, fontSize: 12, color: p.muted, lineHeight: 1.5 }}>{msg}</div>}
    </D1Modal>
  );
}

function D1GuestCountModal({ open, onClose, token, guest, onUpdated, p, serif }) {
  const cap = (guest?.properties?.max_guests ?? guest?.properties?.sleeps) || null;
  const initial = Number(guest?.guests?.adults || 1);
  const [count, setCount] = React.useState(initial);
  const [busy, setBusy] = React.useState(false);
  const [msg, setMsg] = React.useState('');
  React.useEffect(() => {
    if (!open) return;
    setCount(Number(guest?.guests?.adults || 1));
    setMsg('');
  }, [open, guest]);
  async function submit() {
    if (!token) { setMsg('Open this page from your booking email to update.'); return; }
    if (cap && count > cap) { setMsg(`This home sleeps ${cap}. WhatsApp the host for more capacity.`); return; }
    setBusy(true); setMsg('');
    try {
      const r = await fetch('/api/guest/guest-count', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token, guests_count: count }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) { setMsg(j.error || 'Could not update.'); setBusy(false); return; }
      if (typeof onUpdated === 'function') onUpdated(j.guests_count || count);
      setMsg('Updated · the host has been notified.');
    } catch (e) {
      setMsg((e && e.message) || 'Network error.');
    } finally {
      setBusy(false);
    }
  }
  return (
    <D1Modal open={open} onClose={onClose} title="Update guest count" p={p} serif={serif}>
      <div style={{ fontSize: 13, color: p.muted, lineHeight: 1.6, marginBottom: 14 }}>
        Lets the host plan towels, parking, and key handoff.{cap ? ` This home sleeps ${cap}.` : ''}
      </div>
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        background: p.bg, borderRadius: 8, padding: 16,
      }}>
        <span style={{ fontSize: 14 }}>Guests</span>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          <button onClick={() => setCount(c => Math.max(1, c - 1))} aria-label="Decrease" style={{
            width: 36, height: 36, borderRadius: 18, border: `1px solid ${p.line}`, background: p.card,
            fontSize: 18, cursor: 'pointer',
          }}>−</button>
          <span style={{ fontFamily: serif, fontSize: 22, minWidth: 24, textAlign: 'center' }}>{count}</span>
          <button onClick={() => setCount(c => Math.min(cap || 30, c + 1))} aria-label="Increase" style={{
            width: 36, height: 36, borderRadius: 18, border: `1px solid ${p.line}`, background: p.card,
            fontSize: 18, cursor: 'pointer',
          }}>+</button>
        </div>
      </div>
      <button disabled={busy} onClick={submit} style={{
        width: '100%', marginTop: 18, padding: '11px', borderRadius: 8,
        background: p.accent, color: p.bg, border: 'none', fontSize: 13,
        cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1,
      }}>{busy ? 'Saving…' : 'Update guest count'}</button>
      {msg && <div style={{ marginTop: 12, fontSize: 12, color: p.muted, lineHeight: 1.5 }}>{msg}</div>}
    </D1Modal>
  );
}

// ─────── CANCELLATION CARD (Manage trip) ───────
//
// Backed by /api/guest/cancel — the API is the source of truth for the
// refund amount. The policy displayed mirrors src/lib/cancellation.ts via
// window.DyraCancellation, so the dollar amount shown to the guest before
// they confirm matches what the cancel endpoint would actually refund.
// On success we ask the parent to re-fetch /api/guest/booking so the rest
// of the dashboard reflects the new "cancelled" status.
function D1CancelBookingCard({ p, serif, guest, token, reloadBooking }) {
  const [now, setNow] = React.useState(Date.now());
  const [confirmOpen, setConfirmOpen] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [resultMsg, setResultMsg] = React.useState('');
  const [errMsg, setErrMsg] = React.useState('');
  const [showFullPolicy, setShowFullPolicy] = React.useState(false);

  React.useEffect(() => {
    const t = setInterval(() => setNow(Date.now()), 60000); // tick every minute
    return () => clearInterval(t);
  }, []);

  // Synthesize a booking-shape for the policy helper from the `guest`
  // record. The shapes are different (guest is dollars, helper expects
  // cents) but every field the policy needs is derivable.
  const bookingShape = guest ? {
    created_at: guest.bookedAt ? new Date(guest.bookedAt).toISOString() : new Date().toISOString(),
    total_cents: Math.round((guest.pricing?.total || 0) * 100),
    deposit_paid_cents: Math.round((guest.pricing?.depositPaid || 0) * 100),
    balance_paid_at: (guest.pricing?.balance || 0) === 0 && (guest.pricing?.total || 0) > (guest.pricing?.depositPaid || 0) ? new Date().toISOString() : null,
    refunded_cents: Math.round((guest.pricing?.refunded || 0) * 100),
    status: guest.status,
  } : null;
  const policy = (bookingShape && window.DyraCancellation)
    ? window.DyraCancellation.getCancellationPolicy(bookingShape, new Date(now))
    : null;
  const fmtRefund = window.DyraCancellation ? window.DyraCancellation.formatRefundDollars : (c => `$${Math.round((c || 0) / 100)}`);

  // Already cancelled — short-circuit.
  if (String(guest?.status || '').toLowerCase() === 'cancelled') {
    return (
      <div style={{ marginTop: 14, padding: 14, background: p.bg, border: `1px solid ${p.line}`, borderRadius: 8, textAlign: 'center' }}>
        <div style={{ fontSize: 13, fontFamily: serif, marginBottom: 4 }}>Booking cancelled</div>
        <div style={{ fontSize: 12, color: p.muted }}>{(guest.pricing?.refunded || 0) > 0 ? `Refund of $${guest.pricing.refunded} sent to your card. Allow 5–10 business days.` : 'A host will be in touch about your refund.'}</div>
      </div>
    );
  }

  if (resultMsg) {
    return (
      <div style={{ marginTop: 14, padding: 14, background: p.bg, border: `1px solid ${p.line}`, borderRadius: 8, textAlign: 'center' }}>
        <div style={{ fontSize: 13, fontFamily: serif, marginBottom: 4 }}>Booking cancelled</div>
        <div style={{ fontSize: 12, color: p.muted }}>{resultMsg}</div>
      </div>
    );
  }

  if (!guest?.bookedAt || !policy) return null;

  const remainingMs = Math.max(0, policy.graceHoursRemaining * 60 * 60 * 1000);
  const hours = Math.floor(remainingMs / (60 * 60 * 1000));
  const mins = Math.floor((remainingMs % (60 * 60 * 1000)) / (60 * 1000));
  const remainingLabel = hours >= 1 ? `${hours}h ${mins}m` : `${mins} min`;

  const doCancel = async () => {
    if (!token) {
      setErrMsg('Open this page from the link in your booking email to cancel.');
      return;
    }
    setBusy(true);
    setErrMsg('');
    try {
      const r = await fetch('/api/guest/cancel', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) {
        setErrMsg(j.error || 'Cancel failed. Please WhatsApp the host.');
        setBusy(false);
        return;
      }
      const refundDollars = Math.round(Number(j.refunded_cents || 0) / 100);
      setResultMsg(refundDollars > 0
        ? `Cancelled · refunding $${refundDollars} to your card. Allow 5–10 business days.`
        : 'Cancelled. Your deposit isn’t auto-refundable past 48h, but the host will be in touch.');
      setConfirmOpen(false);
      setBusy(false);
      if (typeof reloadBooking === 'function') reloadBooking();
    } catch (e) {
      setErrMsg((e && e.message) || 'Cancel failed.');
      setBusy(false);
    }
  };

  const refundLine = policy.withinGrace
    ? `Cancelling now refunds ${fmtRefund(policy.eligibleRefundCents)} to your card.`
    : 'Cancelling now will not auto-refund any amount. Message the host to discuss exceptions.';

  return (
    <>
      <div style={{ marginTop: 14, padding: 14, background: p.bg, borderRadius: 8, border: `1px solid ${p.line}` }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
          <span style={{ fontSize: 12, fontWeight: 500 }}>Cancellation policy</span>
          {policy.withinGrace && (
            <span style={{ fontSize: 11, color: p.accent, fontFamily: 'monospace' }}>{remainingLabel} left in free window</span>
          )}
        </div>
        <div style={{ fontSize: 12, color: p.ink, lineHeight: 1.55 }}>{policy.summary}</div>
        <div style={{
          fontSize: 12, marginTop: 6, lineHeight: 1.5, fontWeight: 500,
          color: policy.withinGrace ? '#3a7a4a' : p.muted,
        }}>{refundLine}</div>
        <div style={{ fontSize: 11, color: p.muted, marginTop: 4 }}>{policy.processingNote}</div>
        <button
          type="button"
          onClick={() => setShowFullPolicy(v => !v)}
          style={{
            marginTop: 8, background: 'transparent', border: 'none', padding: 0,
            color: p.accent, fontSize: 12, cursor: 'pointer', textDecoration: 'underline',
          }}
        >{showFullPolicy ? 'Hide full policy' : 'Show full policy'}</button>
        {showFullPolicy && (
          <div style={{ marginTop: 8, paddingTop: 8, borderTop: `1px solid ${p.line}` }}>
            {policy.tiers.map(tier => (
              <div key={tier.id} style={{
                display: 'grid', gridTemplateColumns: '10px 1fr', gap: 10,
                padding: '6px 0', opacity: tier.activeNow ? 1 : 0.7,
              }}>
                <div style={{
                  width: 8, height: 8, marginTop: 6, borderRadius: 4,
                  background: tier.activeNow ? p.accent : p.line,
                }}/>
                <div>
                  <div style={{ fontSize: 12, color: p.ink, fontWeight: tier.activeNow ? 600 : 500 }}>
                    {tier.label}{tier.activeNow ? ' · Current' : ''}
                  </div>
                  <div style={{ fontSize: 11.5, color: p.muted, marginTop: 2, lineHeight: 1.5 }}>
                    {tier.refundDescription}
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}
        <button onClick={() => setConfirmOpen(true)} style={{
          marginTop: 12, width: '100%', background: 'transparent', color: p.ink, border: `1px solid ${p.line}`,
          padding: '10px', borderRadius: 6, fontSize: 12, cursor: 'pointer',
        }}>Cancel this booking</button>
        {errMsg && <div style={{ marginTop: 8, fontSize: 11, color: '#c0392b', lineHeight: 1.5 }}>{errMsg}</div>}
      </div>

      {confirmOpen && (
        <div onClick={() => !busy && setConfirmOpen(false)} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.45)', zIndex: 200, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20 }}>
          <div onClick={e => e.stopPropagation()} style={{ background: p.card, padding: 28, borderRadius: 12, maxWidth: 460, width: '100%', border: `1px solid ${p.line}` }}>
            <div style={{ fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: p.muted, marginBottom: 6 }}>Cancel booking</div>
            <div style={{ fontFamily: serif, fontSize: 24, marginBottom: 8 }}>Cancel #{guest.confirmation}?</div>
            <div style={{ fontSize: 13, color: p.muted, lineHeight: 1.6, marginBottom: 14 }}>
              {guest.property} · {guest.checkIn.date} → {guest.checkOut.date}
            </div>
            <div style={{
              padding: 14, marginBottom: 18, borderRadius: 8,
              background: policy.withinGrace ? p.bg : p.card,
              border: `1px solid ${p.line}`,
            }}>
              <div style={{ fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase', color: p.muted }}>
                {policy.withinGrace ? 'Refund' : 'No automatic refund'}
              </div>
              {policy.withinGrace && (
                <div style={{ fontFamily: serif, fontSize: 28, color: p.ink, fontWeight: 500, marginTop: 4 }}>
                  {fmtRefund(policy.eligibleRefundCents)}
                </div>
              )}
              <div style={{ fontSize: 12.5, color: p.muted, marginTop: 6, lineHeight: 1.5 }}>
                {policy.withinGrace
                  ? `Sent back to your card. ${policy.processingNote}`
                  : `This booking is past the ${window.DyraCancellation ? window.DyraCancellation.GRACE_PERIOD_HOURS : 48}-hour free-cancellation window. Message the host to discuss exceptions.`}
              </div>
            </div>
            <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end' }}>
              <button disabled={busy} onClick={() => setConfirmOpen(false)} style={{ background: 'transparent', color: p.ink, border: `1px solid ${p.line}`, padding: '9px 16px', borderRadius: 6, fontSize: 12, cursor: busy ? 'not-allowed' : 'pointer', opacity: busy ? 0.6 : 1 }}>Keep my booking</button>
              <button disabled={busy} onClick={doCancel} style={{ background: p.ink, color: p.bg, border: 'none', padding: '9px 16px', borderRadius: 6, fontSize: 12, cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.7 : 1 }}>{busy ? 'Cancelling…' : 'Yes, cancel'}</button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

// ─────── ADMIN ───────
function D1Admin({ p, serif, sans, go }) {
  const [tab, setTab] = React.useState('calendar');
  return (
    <div style={{ padding: '32px 48px 72px' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 28 }}>
        <div>
          <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 8 }}>Host</div>
          <h1 style={{ fontFamily: serif, fontSize: 44, fontWeight: 400, letterSpacing: '-0.02em', margin: 0 }}>Portfolio</h1>
        </div>
        <div style={{ display: 'flex', gap: 20, fontSize: 12 }}>
          <KPI l="Occupancy (30d)" v="87%" d="+4%" p={p} serif={serif}/>
          <KPI l="ADR" v="$412" d="+$18" p={p} serif={serif}/>
          <KPI l="RevPAR" v="$358" d="+$31" p={p} serif={serif}/>
          <KPI l="Upsell attach" v="62%" d="+8%" p={p} serif={serif}/>
        </div>
      </div>

      <div style={{ display: 'flex', gap: 6, marginBottom: 20, borderBottom: `1px solid ${p.line}` }}>
        {['calendar', 'pricing', 'upsells', 'inbox'].map(t => (
          <button key={t} onClick={() => setTab(t)} style={{
            background: 'transparent', color: tab === t ? p.ink : p.muted,
            border: 'none', borderBottom: `2px solid ${tab === t ? p.accent : 'transparent'}`,
            padding: '12px 18px', fontSize: 13, cursor: 'pointer', marginBottom: -1, textTransform: 'capitalize',
          }}>{t}</button>
        ))}
      </div>

      {tab === 'calendar' && <D1AdminCalendar p={p} serif={serif}/>}
      {tab === 'pricing' && <D1AdminPricing p={p} serif={serif}/>}
      {tab === 'upsells' && <D1AdminUpsells p={p} serif={serif}/>}
      {tab === 'inbox' && <D1AdminInbox p={p} serif={serif}/>}
    </div>
  );
}

function KPI({ l, v, d, p, serif }) {
  return (
    <div style={{ padding: '8px 0 8px 24px', borderLeft: `1px solid ${p.line}` }}>
      <div style={{ fontSize: 10, letterSpacing: '0.15em', textTransform: 'uppercase', color: p.muted }}>{l}</div>
      <div style={{ fontFamily: serif, fontSize: 22, fontWeight: 500, marginTop: 2 }}>{v} <span style={{ fontSize: 11, color: p.accent, marginLeft: 4, fontFamily: 'inherit', fontWeight: 400 }}>{d}</span></div>
    </div>
  );
}

function D1AdminCalendar({ p, serif }) {
  const props = window.DYRA.PROPERTIES.slice(0, 8);
  const days = Array.from({ length: 30 }, (_, i) => i + 1);
  const bookingMap = {
    'ep-4br-2ba': [[3, 7, 'booked'], [14, 17, 'booked'], [22, 28, 'hold']],
    'ep-4br-15ba': [[1, 5, 'booked'], [10, 14, 'booked'], [20, 25, 'booked']],
    'ep-1br': [[5, 9, 'booked'], [15, 21, 'hold'], [25, 28, 'booked']],
    'crown-510': [[2, 8, 'booked'], [18, 23, 'booked']],
    'union-albany': [[1, 4, 'booked'], [12, 17, 'hold'], [24, 29, 'booked']],
    'empire-2br': [[6, 10, 'booked'], [20, 27, 'booked']],
  };
  const shabbosDays = [2, 9, 16, 23, 30];
  return (
    <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10, overflow: 'auto' }}>
      <div style={{ display: 'grid', gridTemplateColumns: `180px repeat(30, minmax(26px, 1fr))`, fontSize: 10, letterSpacing: '0.1em', textTransform: 'uppercase', color: p.muted, borderBottom: `1px solid ${p.line}` }}>
        <div style={{ padding: '14px 16px', fontWeight: 500 }}>May 2026</div>
        {days.map(d => (
          <div key={d} style={{ padding: '14px 0', textAlign: 'center', borderLeft: `1px solid ${p.line}`, background: shabbosDays.includes(d) ? p.bg : 'transparent' }}>{d}</div>
        ))}
      </div>
      {props.map(prop => (
        <div key={prop.id} style={{ display: 'grid', gridTemplateColumns: `180px repeat(30, minmax(26px, 1fr))`, borderBottom: `1px solid ${p.line}`, position: 'relative' }}>
          <div style={{ padding: '14px 16px' }}>
            <div style={{ fontSize: 13, fontWeight: 500 }}>{prop.name}</div>
            <div style={{ fontSize: 10, color: p.muted, letterSpacing: '0.12em', textTransform: 'uppercase', marginTop: 2 }}>{prop.tier}</div>
          </div>
          {days.map(d => (
            <div key={d} style={{ height: 56, borderLeft: `1px solid ${p.line}`, background: shabbosDays.includes(d) ? p.bg : 'transparent' }}/>
          ))}
          {(bookingMap[prop.id] || []).map(([s, e, type], i) => {
            const colW = `calc((100% - 180px) / 30)`;
            return (
              <div key={i} style={{
                position: 'absolute', top: 10, bottom: 10,
                left: `calc(180px + (${s-1}) * ${colW})`,
                width: `calc(${e - s + 1} * ${colW} - 2px)`,
                background: type === 'booked' ? p.accent : type === 'hold' ? p.accent2 : p.line,
                opacity: type === 'hold' ? 0.5 : 1,
                borderRadius: 4, color: p.bg, fontSize: 10, padding: '0 8px',
                display: 'flex', alignItems: 'center', cursor: 'pointer',
              }}>{type === 'booked' ? 'Confirmed' : 'Hold'}</div>
            );
          })}
        </div>
      ))}
    </div>
  );
}

function D1AdminPricing({ p, serif }) {
  const rules = [
    { id: 1, name: 'Base rate (weekday)', rate: '$395', applies: 'Sun–Thu', active: true },
    { id: 2, name: 'Weekend premium', rate: '+22%', applies: 'Fri–Sat', active: true },
    { id: 3, name: 'Shabbos lock-in', rate: '3-night min', applies: 'Stays crossing Shabbos', active: true },
    { id: 4, name: 'Yom Tov surge', rate: '+65%', applies: 'First/last days of chag', active: true },
    { id: 5, name: 'Chol Hamoed', rate: '+35%', applies: 'Chol Hamoed Pesach & Succos', active: true },
    { id: 6, name: 'Pesach lockout', rate: 'Closed', applies: 'Erev Pesach – Isru chag', active: true },
    { id: 7, name: 'Simcha week (Chabad)', rate: '+40%, 5-night min', applies: 'Gimmel Tammuz, Yud Shvat, Chof Beis Shvat', active: true },
    { id: 8, name: 'No-check-in windows', rate: 'Block', applies: 'Fri 2pm – Sat 9pm', active: true },
  ];
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: 24 }}>
      <div>
        <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 14 }}>Dynamic rules · The Rebbe's View</div>
        <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10 }}>
          {rules.map((r, i) => (
            <div key={r.id} style={{ display: 'grid', gridTemplateColumns: '1.4fr 0.8fr 1.4fr auto', gap: 16, padding: '16px 20px', alignItems: 'center', borderTop: i === 0 ? 'none' : `1px solid ${p.line}` }}>
              <div>
                <div style={{ fontSize: 14 }}>{r.name}</div>
                <div style={{ fontSize: 11, color: p.muted, marginTop: 2 }}>{r.applies}</div>
              </div>
              <div style={{ fontFamily: serif, fontSize: 18 }}>{r.rate}</div>
              <div style={{ fontSize: 12, color: p.muted }}>Auto-applied on calendar</div>
              <button style={{
                width: 44, height: 24, borderRadius: 999, border: 'none', cursor: 'pointer', position: 'relative',
                background: r.active ? p.accent : p.line,
              }}>
                <span style={{ position: 'absolute', top: 3, left: r.active ? 22 : 3, width: 18, height: 18, borderRadius: 999, background: '#fff' }}/>
              </button>
            </div>
          ))}
        </div>
        <button style={{ marginTop: 14, background: 'transparent', color: p.accent, border: `1px dashed ${p.line}`, padding: '12px 20px', borderRadius: 8, fontSize: 13, cursor: 'pointer', width: '100%' }}>
          + Add pricing rule
        </button>
      </div>

      <div>
        <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 14 }}>Upcoming chagim</div>
        <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10, padding: 18, marginBottom: 18 }}>
          {[
            ['Shavuos', 'May 21–23', '3 homes fully booked'],
            ['Gimmel Tammuz', 'Jun 29', '6 homes booked · 4 waitlist'],
            ['Tishrei 5787', 'Sep 11–Oct 5', '6 homes open for booking'],
          ].map(([n, d, s], i) => (
            <div key={i} style={{ padding: '12px 0', borderTop: i === 0 ? 'none' : `1px solid ${p.line}` }}>
              <div style={{ fontFamily: serif, fontSize: 18 }}>{n}</div>
              <div style={{ fontSize: 12, color: p.muted, marginTop: 2 }}>{d} · {s}</div>
            </div>
          ))}
        </div>
        <div style={{ background: p.ink, color: p.bg, borderRadius: 10, padding: 20 }}>
          <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', opacity: 0.6, marginBottom: 8 }}>Tip</div>
          <div style={{ fontFamily: serif, fontSize: 18, lineHeight: 1.4 }}>Shavuos demand is running 2.3× normal. Consider a 5-night min and +55% surge for the remaining 4 homes.</div>
          <button style={{ marginTop: 14, background: p.accent, color: p.bg, border: 'none', padding: '9px 16px', borderRadius: 999, fontSize: 12, cursor: 'pointer' }}>Apply suggestion</button>
        </div>
      </div>
    </div>
  );
}

function D1AdminUpsells({ p, serif }) {
  return (
    <div>
      <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 14 }}>Package performance · last 30d</div>
      <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10 }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1.6fr 0.6fr 0.8fr 0.8fr 0.8fr auto', gap: 16, padding: '14px 20px', fontSize: 10, letterSpacing: '0.15em', textTransform: 'uppercase', color: p.muted, borderBottom: `1px solid ${p.line}` }}>
          <div>Package</div><div>Price</div><div>Attach rate</div><div>Revenue</div><div>Margin</div><div></div>
        </div>
        {window.DYRA.UPSELLS.map((u, i) => (
          <div key={u.id} style={{ display: 'grid', gridTemplateColumns: '1.6fr 0.6fr 0.8fr 0.8fr 0.8fr auto', gap: 16, padding: '14px 20px', alignItems: 'center', borderTop: i === 0 ? 'none' : `1px solid ${p.line}` }}>
            <div>
              <div style={{ fontSize: 14 }}>{u.name}</div>
              <div style={{ fontSize: 11, color: p.muted }}>{u.category}</div>
            </div>
            <div style={{ fontFamily: serif }}>${u.priceFrom}</div>
            <div><span style={{ background: p.bg, padding: '3px 8px', borderRadius: 999, fontSize: 11 }}>{[72,41,58,28,12,19,34,48,8][i]}%</span></div>
            <div style={{ fontSize: 13 }}>${[4200, 1850, 1650, 890, 1200, 780, 540, 920, 0][i].toLocaleString()}</div>
            <div style={{ fontSize: 13, color: p.accent }}>{[38,42,55,48,35,40,62,55,'—'][i]}{i < 8 ? '%' : ''}</div>
            <span style={{ fontSize: 11, color: p.muted, cursor: 'pointer' }}>Edit</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function D1AdminInbox({ p, serif }) {
  const msgs = [
    { name: 'Housekeeping request', prop: 'The 770 Four-Bedroom Two-Bathroom', preview: '4-hour clean ($120) requested for Sat May 16 — booking #DY-8841. Sent from listing add-on.', time: 'just now', unread: true, badge: 'Housekeeping' },
    { name: 'Rivka Goldstein', prop: 'The Rebbe\'s View', preview: 'Can you add an extra crib? We\'re bringing twins b"H.', time: '12m', unread: true },
    { name: 'Shmuli Katz', prop: 'Kingston Brownstone', preview: 'Is the kitchen CY only or can I use my own CY cheese?', time: '1h', unread: true },
    { name: 'Chaya Loeb', prop: 'Crown Garden Suite', preview: 'Confirming our Shavuos booking — checking in Wed?', time: '3h', unread: false },
    { name: 'Moshe Werner', prop: 'Empire Loft', preview: 'Left a pair of tefillin in the bedroom drawer.', time: 'yesterday', unread: false },
  ];
  return (
    <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 10 }}>
      {msgs.map((m, i) => (
        <div key={i} style={{ display: 'grid', gridTemplateColumns: '40px 1fr auto', gap: 14, padding: '18px 20px', alignItems: 'center', borderTop: i === 0 ? 'none' : `1px solid ${p.line}`, cursor: 'pointer' }}>
          <div style={{ width: 40, height: 40, borderRadius: 999, background: p.bg, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, fontFamily: serif }}>{m.name[0]}</div>
          <div>
            <div style={{ display: 'flex', gap: 10, alignItems: 'baseline' }}>
              <span style={{ fontSize: 14, fontWeight: m.unread ? 500 : 400 }}>{m.name}</span>
              {m.badge && <span style={{ fontSize: 9, letterSpacing: '0.1em', textTransform: 'uppercase', background: p.accent, color: p.bg, padding: '2px 8px', borderRadius: 999 }}>{m.badge}</span>}
              <span style={{ fontSize: 11, color: p.muted }}>· {m.prop}</span>
            </div>
            <div style={{ fontSize: 13, color: m.unread ? p.ink : p.muted, marginTop: 3 }}>{m.preview}</div>
          </div>
          <div style={{ fontSize: 11, color: p.muted, display: 'flex', alignItems: 'center', gap: 8 }}>
            {m.unread && <span style={{ width: 8, height: 8, background: p.accent, borderRadius: 999 }}/>}
            {m.time}
          </div>
        </div>
      ))}
    </div>
  );
}

// ─────── PAY REMAINING BALANCE ───────
function D1PayBalance({ p, serif, sans, go }) {
  const [card, setCard] = React.useState({ number: '', exp: '', cvc: '', name: '', zip: '' });
  const [stage, setStage] = React.useState('form'); // 'form' | 'processing' | 'done'

  // Demo reservation data — in production this would come from session/API.
  const reservation = {
    confirmation: 'DY-8841',
    property: "The Rebbe's View",
    checkInDate: 'Thu, May 14, 2026',
    nights: 3,
    total: 1815,
    deposit: 600,
  };
  const balance = reservation.total - reservation.deposit;

  const submit = (e) => {
    e.preventDefault();
    setStage('processing');
    setTimeout(() => setStage('done'), 1400);
  };

  if (stage === 'done') {
    return (
      <div style={{ maxWidth: 520, margin: '0 auto', padding: '80px 32px 64px', textAlign: 'center' }}>
        <div style={{
          width: 72, height: 72, borderRadius: 999, background: p.accent, color: p.bg,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 24,
        }}>
          <Ico name="check" size={32}/>
        </div>
        <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 12 }}>Payment received</div>
        <h1 style={{ fontFamily: serif, fontSize: 40, fontWeight: 400, letterSpacing: '-0.02em', margin: '0 0 14px', lineHeight: 1.1 }}>
          You're all paid up
        </h1>
        <p style={{ fontSize: 15, color: p.muted, lineHeight: 1.6, margin: '0 0 32px' }}>
          We charged <strong style={{ color: p.ink }}>${balance}</strong> to your card. A receipt is on its way to your email. See you on {reservation.checkInDate}.
        </p>
        <button onClick={() => go('dashboard')} style={{
          background: p.ink, color: p.bg, border: 'none', padding: '12px 28px', borderRadius: 999, cursor: 'pointer', fontSize: 13,
        }}>Back to my stay</button>
      </div>
    );
  }

  return (
    <div style={{ maxWidth: 560, margin: '0 auto', padding: '48px 32px 64px' }}>
      <div onClick={() => go('dashboard')} style={{ fontSize: 12, color: p.muted, cursor: 'pointer', marginBottom: 20, display: 'inline-flex', alignItems: 'center', gap: 6 }}>
        ← Back to my stay
      </div>
      <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: p.accent, marginBottom: 14 }}>
        Pay remaining balance · #{reservation.confirmation}
      </div>
      <h1 style={{ fontFamily: serif, fontSize: 44, fontWeight: 400, letterSpacing: '-0.02em', margin: '0 0 28px', lineHeight: 1.1 }}>
        Finalize your stay
      </h1>

      <div style={{ background: p.ink, color: p.bg, borderRadius: 12, padding: 24, marginBottom: 24 }}>
        <div style={{ fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', opacity: 0.7, marginBottom: 14 }}>Balance summary</div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, opacity: 0.85, padding: '4px 0' }}>
          <span>{reservation.property} · {reservation.nights} nights</span>
          <span>${reservation.total}</span>
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13, opacity: 0.85, padding: '4px 0' }}>
          <span>Deposit paid</span>
          <span>− ${reservation.deposit}</span>
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontFamily: serif, fontSize: 24, paddingTop: 14, marginTop: 10, borderTop: '1px solid rgba(255,255,255,0.15)' }}>
          <span>Charge today</span>
          <span><strong>${balance}</strong></span>
        </div>
      </div>

      <form onSubmit={submit} style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 12, padding: 24 }}>
        <div style={{ fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase', color: p.muted, marginBottom: 14 }}>Card details</div>

        <label style={{ display: 'block', marginBottom: 14 }}>
          <div style={{ fontSize: 11, color: p.muted, marginBottom: 6 }}>Card number</div>
          <input
            type="text" inputMode="numeric" required placeholder="•••• •••• •••• ••••"
            value={card.number} onChange={e => setCard({ ...card, number: e.target.value })}
            style={{ width: '100%', padding: '12px 14px', fontSize: 14, background: p.bg, border: `1px solid ${p.line}`, borderRadius: 6, fontFamily: 'monospace', boxSizing: 'border-box' }}
          />
        </label>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 14 }}>
          <label>
            <div style={{ fontSize: 11, color: p.muted, marginBottom: 6 }}>Expiry</div>
            <input
              type="text" required placeholder="MM/YY"
              value={card.exp} onChange={e => setCard({ ...card, exp: e.target.value })}
              style={{ width: '100%', padding: '12px 14px', fontSize: 14, background: p.bg, border: `1px solid ${p.line}`, borderRadius: 6, fontFamily: 'monospace', boxSizing: 'border-box' }}
            />
          </label>
          <label>
            <div style={{ fontSize: 11, color: p.muted, marginBottom: 6 }}>CVC</div>
            <input
              type="text" required placeholder="•••"
              value={card.cvc} onChange={e => setCard({ ...card, cvc: e.target.value })}
              style={{ width: '100%', padding: '12px 14px', fontSize: 14, background: p.bg, border: `1px solid ${p.line}`, borderRadius: 6, fontFamily: 'monospace', boxSizing: 'border-box' }}
            />
          </label>
        </div>

        <label style={{ display: 'block', marginBottom: 14 }}>
          <div style={{ fontSize: 11, color: p.muted, marginBottom: 6 }}>Name on card</div>
          <input
            type="text" required placeholder="Yosef Cohen"
            value={card.name} onChange={e => setCard({ ...card, name: e.target.value })}
            style={{ width: '100%', padding: '12px 14px', fontSize: 14, background: p.bg, border: `1px solid ${p.line}`, borderRadius: 6, boxSizing: 'border-box' }}
          />
        </label>

        <label style={{ display: 'block', marginBottom: 22 }}>
          <div style={{ fontSize: 11, color: p.muted, marginBottom: 6 }}>Billing zip</div>
          <input
            type="text" required placeholder="11213"
            value={card.zip} onChange={e => setCard({ ...card, zip: e.target.value })}
            style={{ width: '60%', padding: '12px 14px', fontSize: 14, background: p.bg, border: `1px solid ${p.line}`, borderRadius: 6, fontFamily: 'monospace', boxSizing: 'border-box' }}
          />
        </label>

        <button type="submit" disabled={stage === 'processing'} style={{
          width: '100%', background: p.accent, color: p.bg, border: 'none',
          padding: '14px', borderRadius: 8, fontSize: 14, cursor: stage === 'processing' ? 'wait' : 'pointer',
          opacity: stage === 'processing' ? 0.7 : 1,
        }}>
          {stage === 'processing' ? 'Processing…' : `Pay $${balance}`}
        </button>

        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 14, fontSize: 11, color: p.muted, justifyContent: 'center' }}>
          <Ico name="shield" size={12}/>
          <span>Secured by Stripe · 256-bit encryption</span>
        </div>
      </form>
    </div>
  );
}

window.D1Dashboard = D1Dashboard;
window.D1DashboardGate = D1DashboardGate;
window.D1PayBalance = D1PayBalance;
window.D1Admin = D1Admin;

// Legacy demo-flow shim: the booking flow used to register a fake reservation
// against a local in-memory dict. Bookings now live in Supabase; the gate
// fetches them via /api/guest/booking with the magic-link token. We keep
// the global as a no-op so any older callers don't throw.
window.D1_RESERVATIONS_REGISTER = function() { /* no-op: bookings come from /api/guest/booking */ };

// ─────── BOOKING CONFIRMATION (post-payment) ───────
function D1Confirmation({ p, serif, sans, hebFont, property, data, go }) {
  // Use real values from booking flow if present, otherwise fall back to demo defaults.
  const fallbackConf = React.useMemo(() => 'DYR-' + Math.floor(100000 + Math.random() * 900000), []);
  const conf = data?.confirmation || fallbackConf;
  const name = property?.name || 'Your stay';
  const nights = data?.nights || 3;
  const total = data?.total || (property ? property.priceFrom * nights : 0);
  const deposit = data?.deposit || Math.round(total * 0.3);
  const balance = total - deposit;
  const guestEmail = data?.guestEmail || '';

  return (
    <div style={{ maxWidth: 640, margin: '0 auto', padding: '64px 32px 80px' }}>
      {/* Mark + headline */}
      <div style={{ textAlign: 'center', marginBottom: 40 }}>
        <div style={{
          width: 80, height: 80, borderRadius: 999, background: p.accent, color: p.bg,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 22,
        }}>
          <Ico name="check" size={36}/>
        </div>
        <div style={{ fontSize: 11, letterSpacing: '0.24em', textTransform: 'uppercase', color: p.accent, marginBottom: 14 }}>
          Reservation confirmed
        </div>
        <h1 style={{ fontFamily: serif, fontSize: 52, fontWeight: 400, letterSpacing: '-0.02em', margin: '0 0 14px', lineHeight: 1.05 }}>
          You're all set, <span style={{ fontFamily: hebFont, opacity: 0.85 }}>ברוכים הבאים</span>
        </h1>
        <p style={{ fontSize: 15, color: p.muted, lineHeight: 1.7, margin: '0 auto', maxWidth: 480 }}>
          Your deposit of <strong style={{ color: p.ink }}>${deposit}</strong> has been processed. A confirmation email is on its way with arrival instructions, the home guide, and your kashrus protocol.
        </p>
      </div>

      {/* Reservation card */}
      <div style={{ background: p.card, border: `1px solid ${p.line}`, borderRadius: 12, padding: 28, marginBottom: 24 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 18 }}>
          <div>
            <div style={{ fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: p.muted, marginBottom: 6 }}>Confirmation</div>
            <div style={{ fontFamily: serif, fontSize: 26, color: p.ink }}>#{conf}</div>
          </div>
          <div style={{ textAlign: 'right' }}>
            <div style={{ fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: p.muted, marginBottom: 6 }}>Status</div>
            <div style={{ fontSize: 13, color: p.accent, fontWeight: 500 }}>Confirmed · paid deposit</div>
          </div>
        </div>

        <div style={{ borderTop: `1px solid ${p.line}`, paddingTop: 18, marginBottom: 16 }}>
          <div style={{ fontFamily: serif, fontSize: 22, marginBottom: 4 }}>{name}</div>
          {property && <div style={{ fontSize: 13, color: p.muted }}>{property.location || 'Crown Heights, Brooklyn'}</div>}
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, padding: '14px 0', borderTop: `1px solid ${p.line}` }}>
          <div>
            <div style={{ fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase', color: p.muted, marginBottom: 4 }}>Check-in</div>
            <div style={{ fontSize: 14 }}>Thu, May 14, 2026 · 3pm</div>
          </div>
          <div>
            <div style={{ fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase', color: p.muted, marginBottom: 4 }}>Check-out</div>
            <div style={{ fontSize: 14 }}>Sun, May 17, 2026 · 11am</div>
          </div>
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 14, padding: '14px 0', borderTop: `1px solid ${p.line}`, fontSize: 13 }}>
          <div><span style={{ color: p.muted }}>Nights · </span>{nights}</div>
          <div><span style={{ color: p.muted }}>Total · </span>${total}</div>
          <div><span style={{ color: p.muted }}>Balance due · </span>${balance}</div>
        </div>
      </div>

      {/* Where to find it */}
      <div style={{
        border: `1px solid ${p.line}`, borderRadius: 12, padding: 22, marginBottom: 28,
        display: 'flex', gap: 16, alignItems: 'flex-start', background: p.bg,
      }}>
        <div style={{
          width: 36, height: 36, borderRadius: 999, background: p.bg, border: `1px solid ${p.line}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, color: p.accent,
        }}>
          <Ico name="key" size={16}/>
        </div>
        <div style={{ flex: 1 }}>
          <div style={{ fontFamily: serif, fontSize: 18, marginBottom: 4 }}>Look it up anytime in <em>My Stay</em></div>
          <div style={{ fontSize: 13, color: p.muted, lineHeight: 1.6 }}>
            Find this reservation, manage upsells, message your host, and pay your remaining balance from the <strong style={{ color: p.ink }}>My Stay</strong> button at the top of any page. We'll send a reminder 14 days before check-in.
          </div>
        </div>
      </div>

      {/* Actions */}
      <div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
        <button onClick={() => go('dashboard')} style={{
          background: p.ink, color: p.bg, border: 'none', padding: '13px 28px', borderRadius: 999, cursor: 'pointer', fontSize: 13,
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>Open My Stay</button>
        <button onClick={() => go('home')} style={{
          background: 'transparent', color: p.ink, border: `1px solid ${p.line}`, padding: '13px 28px', borderRadius: 999, cursor: 'pointer', fontSize: 13,
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>Back to home</button>
      </div>

      <div style={{ marginTop: 36, textAlign: 'center', fontSize: 12, color: p.muted, lineHeight: 1.7 }}>
        {guestEmail
          ? <>Confirmation email sent to <strong style={{ color: p.ink }}>{guestEmail}</strong>. Questions? <a href="mailto:hello@dyra.co" style={{ color: p.accent, textDecoration: 'none' }}>hello@dyra.co</a></>
          : <>Questions? Reach Mendel at <a href="mailto:hello@dyra.co" style={{ color: p.accent, textDecoration: 'none' }}>hello@dyra.co</a> or message through My Stay.</>}
      </div>
    </div>
  );
}

window.D1Confirmation = D1Confirmation;

// ─────── PAYMENT FAILED ───────
function D1PaymentFailed({ p, serif, sans, property, data, go }) {
  const reason = data?.reason || 'Your card was declined. Please try again or use a different payment method.';
  return (
    <div style={{ maxWidth: 520, margin: '0 auto', padding: '80px 32px 64px', textAlign: 'center' }}>
      <div style={{
        width: 72, height: 72, borderRadius: 999, background: '#fbeae5', color: '#c0392b',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center', marginBottom: 22,
        border: `1px solid #e9c4bd`,
      }}>
        <Ico name="close" size={32}/>
      </div>
      <div style={{ fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase', color: '#c0392b', marginBottom: 12 }}>Payment failed</div>
      <h1 style={{ fontFamily: serif, fontSize: 40, fontWeight: 400, letterSpacing: '-0.02em', margin: '0 0 14px', lineHeight: 1.1 }}>
        Your reservation was not booked
      </h1>
      <p style={{ fontSize: 15, color: p.muted, lineHeight: 1.7, margin: '0 auto 8px', maxWidth: 440 }}>
        {reason}
      </p>
      <p style={{ fontSize: 13, color: p.muted, lineHeight: 1.7, margin: '0 auto 32px', maxWidth: 440 }}>
        Don't worry — <strong style={{ color: p.ink }}>no charge was made</strong>. Your dates are still available; you can retry below.
      </p>
      <div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap', marginBottom: 28 }}>
        <button onClick={() => go('booking', property?.id)} style={{
          background: p.ink, color: p.bg, border: 'none', padding: '13px 28px', borderRadius: 999, cursor: 'pointer', fontSize: 13,
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>Try again</button>
        <button onClick={() => go('listing', property?.id)} style={{
          background: 'transparent', color: p.ink, border: `1px solid ${p.line}`, padding: '13px 28px', borderRadius: 999, cursor: 'pointer', fontSize: 13,
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>Back to listing</button>
      </div>
      <div style={{ fontSize: 12, color: p.muted, lineHeight: 1.7 }}>
        Trouble persisting? Email <a href="mailto:hello@dyra.co" style={{ color: p.accent, textDecoration: 'none' }}>hello@dyra.co</a> and we'll help you book directly.
      </div>
    </div>
  );
}

window.D1PaymentFailed = D1PaymentFailed;
