// sections.jsx — composable portal sections. Each takes a `stay` view-model.
// Adapted from the prototype to render live Firestore content:
//   • "Living here" renders grouped items as cards → a modal with the item's
//     Q&A (rich-HTML answers from the CMS), plus a trailing "services" section.
//   • "Around the corner" renders the property's `information` guide content.
//   • Weather/address/Wi-Fi/guide hide gracefully when the backend omits them.
const { useState: useStateS, useEffect: useEffectS } = React;

// Section header icon: the admin-assigned icon-bank SVG for this section (if any),
// else the built-in doodle fallback.
function SectionIcon({ stay, name, fallback, size = 32 }) {
  const svg = stay && stay.sectionIcons && stay.sectionIcons[name];
  if (svg) return <span className="svg-ico" style={{ display: 'inline-block', width: size, height: size, color: 'var(--illustration)' }} dangerouslySetInnerHTML={{ __html: svg }} />;
  return fallback;
}

// ── Weather chip ──────────────────────────────────────────────────
function WeatherChip({ stay, style }) {
  if (!stay.weather) return null;
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10, color: 'var(--fg)', ...style }}>
      <span style={{ color: 'var(--illustration)' }}><window.DoodleWeather size={30} /></span>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 22, letterSpacing: '0.02em' }}>{stay.weather.tempC}°</span>
      <span style={{ fontFamily: 'var(--font-accent)', fontStyle: 'italic', fontSize: 13, color: 'var(--fg-faint)' }}>{stay.weather.label}</span>
    </div>);

}

// ── Hero stay card (image + key facts) ────────────────────────────
function StayHero({ stay, showImage = true, tall = false }) {
  const t = window.useT();
  return (
    <div style={{ background: 'var(--surface)', boxShadow: 'var(--shadow-card)', overflow: 'hidden' }}>
      {showImage &&
      <div style={{ position: 'relative', height: tall ? 280 : 200, background: stay.photo }}>
          <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(180deg, rgba(0,0,0,0.28), transparent 40%, transparent 70%, rgba(0,0,0,0.20))' }} />
          {stay.reservation.today &&
        <div style={{ position: 'absolute', top: 18, right: 18, display: 'inline-flex', alignItems: 'center', gap: 7, background: 'var(--brand)', color: 'var(--on-brand)', padding: '8px 16px', fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase' }}>
              <window.DoodleSparkle size={15} /> {t('today')}
            </div>
        }
          {stay.city &&
        <div style={{ position: 'absolute', left: 20, bottom: 18 }}>
              <window.Accent style={{ fontSize: 14, color: '#fff', opacity: 0.92 }}>{stay.city}</window.Accent>
            </div>
        }
        </div>
      }
      <div style={{ padding: showImage ? '24px 24px 26px' : '26px 24px' }}>
        {!showImage && stay.reservation.today &&
        <div style={{ display: 'inline-flex', alignItems: 'center', gap: 7, background: 'var(--brand)', color: 'var(--on-brand)', padding: '7px 14px', fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase', marginBottom: 16 }}>
            <window.DoodleSparkle size={14} /> {t('today')}
          </div>
        }
        <div style={{ fontFamily: 'var(--font-serif)', fontWeight: 300, fontSize: 30, lineHeight: 1.05, color: 'var(--fg)', marginBottom: 20, textWrap: 'pretty' }}>{stay.unitName}</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '20px 36px' }}>
          {stay.room && <Fact label={t('room')} value={stay.room} />}
          {stay.floor && <Fact label={t('floor')} value={stay.floor} />}
          {stay.buildingLabel && <Fact label={t('building')} value={stay.buildingLabel} />}
          {stay.buildingCode && <Fact label={t('buildingCode')} value={stay.buildingCode} />}
          {stay.doorPin &&
          <window.SecretField label={t('personalPin')} value={stay.doorPin} expand={{
            title: stay.unitName,
            lines: [t('room') + ' ' + stay.room, stay.buildingLabel, t('floor') + ' ' + stay.floor].filter((x) => x && x.trim() && !/\s$/.test(x)),
            filename: stay.room,
            suffix: stay.pinSuffix,
            note: t('pinNote')
          }} />
          }
        </div>
      </div>
    </div>);

}

function Fact({ label, value }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <window.Eyebrow>{label}</window.Eyebrow>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 17, color: 'var(--fg)' }}>{value}</span>
    </div>);
}

// Section wrapper: a plain SectionHead + content, or — when `collapsible` (used
// on mobile) — a tappable header with a chevron that collapses the content,
// collapsed by default. Shared by Connect / Living here / Where you are /
// Breakfast so they behave identically.
function CollapsibleSection({ icon, title, sub, collapsible, children }) {
  const [open, setOpen] = useStateS(!collapsible);
  if (!collapsible) {
    return (
      <div>
        <SectionHead icon={icon} title={title} sub={sub} />
        {children}
      </div>);

  }
  return (
    <div>
      <button onClick={() => setOpen((o) => !o)} style={{ width: '100%', background: 'none', border: 'none', padding: 0, cursor: 'pointer', textAlign: 'start', font: 'inherit', display: 'flex', alignItems: 'center', gap: 14 }}>
        <span style={{ color: 'var(--illustration)', flexShrink: 0 }}>{icon}</span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontFamily: 'var(--font-serif)', fontWeight: 300, fontSize: 22, color: 'var(--fg)', lineHeight: 1.1 }}>{title}</div>
          {sub && <window.Accent style={{ fontSize: 13 }}>{sub}</window.Accent>}
        </div>
        <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" style={{ color: 'var(--fg-muted)', flexShrink: 0, transform: open ? 'rotate(90deg)' : 'none', transition: 'transform .2s ease' }}><path d="M5 2l6 6-6 6" /></svg>
      </button>
      <div style={{ display: 'grid', gridTemplateRows: open ? '1fr' : '0fr', transition: 'grid-template-rows .26s ease' }}>
        <div style={{ overflow: 'hidden' }}>
          <div style={{ marginTop: 20 }}>{children}</div>
        </div>
      </div>
    </div>);

}

// ── Access / codes block ──────────────────────────────────────────
function AccessBlock({ stay, columns = 3, collapsible }) {
  const t = window.useT();
  const hasNet = stay.wifi && stay.wifi.network;
  const hasPw = stay.wifi && stay.wifi.password;
  if (!hasNet && !hasPw) return null;
  return (
    <CollapsibleSection icon={<SectionIcon stay={stay} name="connect" fallback={<window.DoodleKey size={32} />} />} title={t('connect')} sub={t('wifiAccess')} collapsible={collapsible}>
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${columns}, minmax(0,1fr))`, gap: '24px 28px' }}>
        {hasNet && <window.CopyValue label={t('wifiNetwork')} value={stay.wifi.network} noCopy />}
        {hasPw && <window.CopyValue label={t('wifiPassword')} value={stay.wifi.password} />}
      </div>
    </CollapsibleSection>);

}

// ── Address + navigation ──────────────────────────────────────────
// Brand marks (monochrome, tint to the button text via currentColor).
function GmapsIcon({ size = 16 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 598.56 849.44" fill="currentColor" aria-hidden="true">
      <path d="M598.26,282.73l-.04-.72v-.02c-2.02-39.27-14.53-80.45-24.7-108.16-19.1-52.04-46.56-86.28-65.88-100.97C469.94,44.2,394.76-3.37,314.95.19c-27.13,1.23-44.99,3.9-72.48,9.05-70.79,13.27-108.83,34.71-156.58,88.24C24.24,166.61-2.97,235.92.26,315.61c2.04,50.35,16.31,86.91,37.84,136.94,2.55,5.92,4.95,12.22,7.27,18.3,2.04,5.33,4.14,10.85,6.36,16.15,9.01,21.78,19.45,43.36,31.02,64.12,9.45,17.02,19.6,32.13,30.34,48.12,3.04,4.52,6.09,9.07,9.15,13.69,16.63,25.29,33.09,50.99,48.94,76.38,4.79,7.72,9.63,15.66,14.32,23.34,16.53,27.12,33.62,55.16,52.98,80.64,1.33,1.75,2.78,3.68,4.32,5.74,12.29,16.4,29.12,38.87,43.77,47.15l.25.13c4.52,2.09,9.04,3.13,13.52,3.13,10.97,0,21.67-6.23,31.5-18.47,27.82-34.63,50.57-64.8,69.56-92.24,8.87-12.82,16.25-26.51,23.38-39.75,7.79-14.46,15.84-29.41,25.79-43.03,14.13-19.83,25.88-41.74,37.25-62.93,6.23-11.61,12.67-23.61,19.39-35.06,2.57-4.44,5.18-8.86,7.79-13.29,10.9-18.52,22.17-37.67,31.11-57.73,6.97-15.79,12.97-32.2,18.78-48.06,2.17-5.93,4.41-12.06,6.67-18.06,20.49-54.46,28.72-97.04,26.7-138.08ZM546.74,432.2c-14.73,42.01-31.53,76.44-57.96,118.85-6.97,11.19-13.31,23.34-19.44,35.1-6.36,12.2-12.93,24.81-20.21,36.32-5.68,8.98-11.69,18.15-17.5,27.01-3.71,5.66-7.42,11.32-11.07,16.95l-.18.31c-21.73,42.54-46.53,80.59-75.82,116.32-3.82,4.67-7.52,9.75-11.1,14.66-5.18,7.12-10.53,14.47-16.4,20.77-11.16,11.98-16.77,15.9-27.33,6.25-7.87-7.78-15.22-16.8-21.5-24.65-28.73-35.94-53.13-75.92-76.74-114.59-4.52-7.41-9.04-14.82-13.62-22.23-12.99-20.64-26.38-41.46-39.8-61.9-2.38-3.58-4.75-7.11-7.11-10.63-9.62-14.35-18.71-27.91-27.41-42.76-12.12-20.85-23.07-42.71-32.55-64.95-1.85-4.38-3.61-9.03-5.31-13.53-1.74-4.59-3.53-9.34-5.44-13.83-1.15-2.71-2.29-5.38-3.41-8.03-19.3-45.35-35.96-84.52-37.4-136.57-2.06-74.58,25.01-139.88,85.19-205.53,42.93-46.83,74.01-64.12,138.56-77.05,26.59-5.33,46.43-8.39,75.06-9.27,55.14.22,92.05,15.54,137.17,41.91,15.11,8.83,29.39,17.18,43.66,29.93,26.99,24.11,47.93,61.08,64.04,113.04,8.85,28.52,14.59,52.1,15.92,81.54,2.69,45-10.62,85.23-25.5,127.33v-.17l-6.8,19.4Z"/>
      <path d="M414.55,254.47c-9.28-26.06-19.61-37.32-43.36-52.57-21.01-14.66-40.39-27.19-67.33-26.63-1.87.02-6.97.11-8.69.41-15.69,1.76-23.86,5.25-38.07,11.62-4.17,1.87-11.05,3.85-15.72,5.78-13.47,5.65-25.33,14.53-34.54,25.87-21.44,26.64-37.22,72.56-33.34,106.75.83,7.91,2.79,15.67,5.82,23.03,6.33,15.09,28.66,47.24,41.75,57.16,20.92,15.84,58.11,26.89,84.11,28.45,35.35.42,55.82-13.18,79.35-35.94,44.18-42.74,49.99-87.83,30.02-143.92h0ZM307.39,191.68c2.3-.14,7.01-.53,8.58.71-2.43-.03-7.12,1.01-8.58-.71Z"/>
    </svg>);

}
function WazeIcon({ size = 16 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 290.14 285.34" fill="currentColor" aria-hidden="true">
      <path d="M277.23,66.24l-2.27-4.5-.23.48c-4.46-6.86-13.21-19.48-15.13-21.44-12.71-13.08-35.94-29.71-53.95-34.75-13.96-3.9-40.22-7.18-55.51-5.65-32.87,2.12-64.66,17.79-87.2,42.99-13.87,15.51-23.96,45.83-26.42,66.48-.48,4.07-.64,8.65-.81,13.5-.57,16.72-1.22,35.67-15.37,43.27-1.18.63-3.6.99-5.94,1.34-6.1.91-15.33,2.28-14.32,11.4l.02.17c1.77,9.77,11.73,20.83,17.6,25.51,4.12,3.29,7.61,6.27,10.68,8.9,11.22,9.59,18.24,15.59,36.47,20.87-.61.84-1.29,1.76-2.01,2.74-4.76,6.47-10.69,14.52-11.52,19.79-.1.67.03,1.35.3,2.05-1.14.53-2.29,1.22-3.15,2.4l-.79,1.07.45,1.25c1.8,4.98,9.08,4.73,14.92,4.53.5-.02.99-.03,1.46-.05-.78,3.95-.43,7.24,1.03,9.83,1.46,2.58,3.94,4.3,7.39,5.12,3.31.75,6.74.2,9.67-1.58,4.97-3.06,5.95-7.19,6.25-11.4,5.82.28,10.59.51,16.47.3-.23,4.63.63,8.11,2.6,10.57,1.79,2.23,4.39,3.52,7.72,3.83.36.04.72.05,1.08.05,2.79,0,5.5-1.06,7.56-2.98,3.72-3.47,4.19-7.61,4.17-11.18v-.02c.38,0,.76.02,1.1.03,4.48.14,10.63.34,13.17-3.32,1.31-1.88,1.38-4.29.21-7.15-1.51-3.69-4.69-7.99-7.49-11.78-.09-.12-.18-.24-.26-.36.31,0,.62,0,.92,0,1.71.01,3.33.03,4.59.25,10.33,1.89,19.91.68,29.17-.5,3.8-.48,7.71-.98,11.65-1.25.75,2.87,1.5,5.59,3.33,8.5,4.67,7.45,11.92,11.5,19.38,10.84,7.54-.66,14.03-5.97,17.35-14.2,1.77-4.39,2.99-8.69,4.16-12.85.83-2.92,1.61-5.7,2.57-8.5,5.99-3.42,18.52-11.71,23.26-16.81,11.79-12.68,27.31-32.66,34.33-55.77,1.57-5.15,1.98-9.08,2.42-13.23.17-1.6.34-3.25.59-5.04,3.52-25.63-.63-51.83-11.68-73.78ZM215.46,236.64c-.06.21-.13.41-.19.63-.26.81-.5,1.62-.75,2.42-2.15,6.99-3.86,12.56-9.78,16.18l-.26-.03c-3.79-.47-5.9-.74-8.5-2.98-2.29-2.75-3.1-4.68-4.16-7.68l1.04-.29c9.51-2.65,13.74-3.83,22.61-8.24ZM116.72,273.36s0,0,0,0c-.17-.51-.15-.94.03-1.63.06.05.08.08.08.08.07.12.13.48-.11,1.55ZM256.34,188.97c-.21.29-.41.59-.62.88-3.44,4.9-7.33,10.46-11.22,14.17-20.08,19.11-42.78,29.99-65.66,31.48-2.95.19-6.06.66-9.06,1.12-4.71.71-9.58,1.46-14.01,1.15-7.61-.52-15.44-1.06-23.66-1.06-1.61,0-3.24.02-4.89.07-1.71-2.01-3.03-4.03-4.54-6.31l-.77-1.16c-4.87-8.7-16.14-23.78-26.07-30.26l-1.38-.9-1.37.91c-7.83,5.21-16.72,18.15-21.98,26.57-2.24-1.02-4.51-1.91-6.72-2.78-2.8-1.1-5.44-2.14-8.01-3.41-7.26-3.57-13.18-8.63-19.45-14-3.16-2.7-6.42-5.49-9.86-8.06-6.45-4.81-11.69-11.07-14.51-17.21,5.84-.86,13.35-2.12,17.62-5.63,15.2-12.45,16.14-32.9,16.98-50.94.28-6.05.55-11.77,1.3-16.99,2.67-18.4,10.8-38.57,21.73-53.94,2.8-3.95,11.69-11.43,14.34-13.63,14.94-12.33,27.1-18.8,41.97-22.36,1.74-.42,3.56-.98,5.49-1.58,2.97-.92,6.04-1.88,8.71-2.22,2.94-.37,6.02-.66,8.99-.94,1.98-.18,3.96-.37,5.84-.57,4.56-.34,11.84.41,17.69,1.01,2.19.23,4.27.44,6.02.58,24.2,1.93,46.22,12.38,67.3,31.96l.21.17c3.64,2.71,5.99,6.27,8.47,10.05,1.12,1.71,2.29,3.48,3.59,5.18,12.3,16.09,18.44,34.36,19.33,57.51,1.04,27.22-5.16,49.17-18.94,67.11l-3.09,4.02h.23ZM110.88,233.93c-3.77.14-7.51.22-11.18.24.38-4.21.71-9.46.84-14.29,3.29,4.19,7.73,10.25,10.33,14.04ZM90.97,217.59l-1.37,16c-.39,4.25.18,6.94,1.84,8.71,2.23,2.38,5.52,2.31,9.7,2.21.52-.01,1.07-.03,1.64-.03,3.46-.05,7-.15,10.42-.25,1.4-.04,2.8-.08,4.19-.12l1.66,2.55c2.76,4.24,5.24,8.06,8.03,12.13-2.79-.24-5.72-.44-7.36-.35-1.57.08-3.05.17-4.48.26-4.84.29-9.03.54-14.33.4h-.08s-.08,0-.08,0c-1.89.07-3.92,0-6.07-.07-3.26-.11-6.63-.22-9.73.15-.31-.18-.6-.36-.89-.54-2.33-1.41-4.7-2.85-7.37-2.85-1.78,0-3.68.64-5.79,2.36-.06-.01-.12-.02-.18-.03-2.25-.41-4.94-.88-7.31-.8,4.69-8.14,19.27-28.58,27.56-39.71ZM77.32,270.64c-.05.43-.12.66-.3,1.06-.85.36-1.34.44-1.57.46-.08-.22-.18-.71-.16-1.7,1.23-.04,1.76.02,2.03.19Z"/>
      <path d="M210.31,141.93c-2.16.4-4.32,2.8-7.67,6.69-1.22,1.42-2.61,3.03-3.23,3.49l-.32.28c-11.28,11.8-31.34,12.88-45.32,7.86l-2.34-.84c-11.03-3.92-13.65-4.85-21.19-14.9-.85-1.13-4.54-5.16-6.55-6.3l-1.43-.81-1.31.99c-2.16,1.62-3.36,3.54-3.58,5.69-.41,4,2.66,7.51,5.37,10.61l.68.78c9.79,11.27,26.05,18.61,43.49,19.62,1.37.08,2.73.12,4.07.12,14.06,0,26.49-4.33,35.31-12.39,3.89-3.55,6.74-7.11,9.01-10.13l.13-.2c1.75-2.86,1.89-6.27.36-8.5-1.2-1.74-3.2-2.49-5.48-2.07Z"/>
      <path d="M224.95,87.01v-.71l-.68-.72c-4.06-4.29-6.77-6.1-13.05-6.15-.6,0-1.01.04-1.32.07-.12.01-.21.03-.32.02,0,0-.13-.02-.51-.18l-1.16-.49-1.08.64c-3.11,1.84-3.86,2.32-6.47,4.87l-.48.47-.18.65c-1.15,4.14-1.47,6.26-1.64,10.76l-.02.62.26.56c2.89,6.06,6.32,10.14,12.6,10.14,1.8,0,3.84-.34,6.16-1.06l.31-.1.28-.17c7.13-4.49,9.51-9.92,7.69-17.6l-.38-1.62Z"/>
      <path d="M129.05,108.85c1.29,0,2.58-.16,3.8-.46l.18-.05c3.41-1.12,5.43-2.18,7.71-5.22,2.52-3.37,3.3-7.73,2.14-11.97-1.2-4.38-4.28-8.03-8.29-9.87l-.49-.22c-2.21-.92-4.62-.89-6.73-.87h-1.34s-.57.34-.57.34c-9.33,5.35-10.99,14.29-8.57,20.33,2.27,5.69,7.26,7.99,12.15,7.99Z"/>
    </svg>);

}

// Hand-drawn location pin (brand asset) for "Where you are".
function PinMark({ size = 26 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 124.39 196.01" fill="currentColor" aria-hidden="true" style={{ display: 'block' }}>
      <path d="M114.64,56.84c-1.39-7.76-7.96-20.46-13.59-26.05-7.02-6.97-19.74-13.88-29.7-14.72-3.28-.28-4.35-1.11-7.95-.77-10.43.9-25.95,5.79-34.28,12.36-13.37,10.54-21.29,35.56-19.82,52.17,1.42,16.11,10.63,33.18,17.92,47.5,2.7,5.28,6.65,10.07,9.8,15l4.73,7.47c1.03,1.65,1.92,3.27,3.02,4.89,1.4,2.06,3.28,3.9,4.69,5.88-6.96-.29-30.28,6.57-20.29,16.98,1.24,1.3,4.28,2.3,5.98,2.89,7.72,2.71,16.64,3.43,24.77,4.38,2.12-.19,4.27-.38,6.41-.55,2-.26,4.87-.04,6.77-.44,7.34-1.55,16.88-3.5,22.07-9.26,4.49-4.98.22-11.13-5.03-12.98-1.66-.58-3.49-.62-5.2-.96-2.84-.57-6.01-1.58-8.91-1.72,1.68-2.5,3.63-4.88,5.42-7.31,2.24-3.1,4.4-6.25,6.48-9.46,4.3-6.59,7.91-14.8,12.26-21.57,2.36-3.67,4.26-8.79,6.58-12.58,6.85-15.37,10.86-34.45,7.87-51.14ZM82.11,163.83c5.12,1.11,7.45-.04,11.24,4.77.1.6.12,1.46.15,2.09-3.97,6.32-14.16,8.27-21.1,9.68-1.86.38-4.48.12-6.37.38-4.21.58-7.81.51-11.97-.37-4.62-.62-8.78-.78-13.44-1.79-2.23-.66-5.19-1.79-7.38-2.64-1.88-.73-2.46-1.81-3.09-3.53,2.15-4.81,10.99-6.97,15.65-8.33,1.64-.48,3.84-.65,5.31-1.47,4.12,4.74,5.08,7.96,11.13,10.53.44.66.81,1.35,1.71.98,4.36-1.77,7.48-8.55,10.19-12.27,3.4.26,4.97,1.32,7.97,1.97ZM81.9,88.08c-3.29,4.42-10.39,7.4-15.83,8.1-13.93.95-25.36-5.89-27.01-20.73-1.21-9.5,2.31-23.36,12.39-26.89.96-.34,8.67-3.12,9.13-3.19.99-.11,3.64.34,4.69.48,5.25.57,9.76,1.64,14.11,4.83,4.1,3.01,7.94,11.7,8.69,16.6,1.04,6.8-2.12,15.39-6.15,20.81ZM90.65,129.69l-.27-.08c.65-1.35,3.98-7.72,4.83-8.11-.46,1.34-3.69,6.77-4.56,8.19Z"></path>
      <path d="M83.58,47.58l.1.13c.52-.46,1.23-2.12,1.55-2.83l-.27-.08c-.44.67-1.25,2.01-1.38,2.78Z"></path>
    </svg>);

}

function AddressBlock({ stay, collapsible }) {
  const t = window.useT();
  const coords = stay.lat != null && stay.lng != null ? `${stay.lat},${stay.lng}` : null;
  const gmaps = stay.googleMaps || (coords ? `https://www.google.com/maps/dir/?api=1&destination=${coords}` : '');
  const waze = stay.waze || (coords ? `https://waze.com/ul?ll=${coords}&navigate=yes` : '');
  if (!stay.address && !gmaps && !waze) return null;
  return (
    <CollapsibleSection icon={<SectionIcon stay={stay} name="where" fallback={<PinMark size={32} />} />} title={t('whereYouAre')} sub={stay.mapHint} collapsible={collapsible}>
      {stay.address &&
      <>
        <window.Eyebrow style={{ marginBottom: 6 }}>{t('address')}</window.Eyebrow>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 15, color: 'var(--fg)', lineHeight: 1.5, marginBottom: 18 }}>{stay.address.replace(/\s*\n\s*/g, ' ')}</div>
      </>
      }
      <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
        {gmaps && <window.Btn variant="solid" onClick={() => window.open(gmaps, '_blank')}><GmapsIcon /> {t('googleMaps')}</window.Btn>}
        {waze && <window.Btn variant="solid" onClick={() => window.open(waze, '_blank')}><WazeIcon /> {t('waze')}</window.Btn>}
      </div>
    </CollapsibleSection>);

}

// ── Section heading with doodle ───────────────────────────────────
function SectionHead({ icon, title, sub, style }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 20, ...style }}>
      {icon && <span style={{ color: 'var(--illustration)', flexShrink: 0 }}>{icon}</span>}
      <div>
        <div style={{ fontFamily: 'var(--font-serif)', fontWeight: 300, fontSize: 22, color: 'var(--fg)', lineHeight: 1.1 }}>{title}</div>
        {sub && <window.Accent style={{ fontSize: 13 }}>{sub}</window.Accent>}
      </div>
    </div>);

}

// ── "Living here": grouped items → cards → Q&A modal ──────────────
// Pick a doodle from the (possibly localized) section title via keywords.
function essIcon(title, size) {
  const s = String(title || '').toLowerCase();
  let C = window.DoodleSparkle;
  if (/room|essential|חדר|חיוני/.test(s)) C = window.DoodleBed;
  else if (/kitchen|applianc|coffee|מטבח|מכשיר/.test(s)) C = window.DoodleCoffee;
  else if (/laundr|wash|כביס/.test(s)) C = window.DoodleBroom;
  else if (/rule|service|שירות|כלל/.test(s)) C = window.DoodleHeart;
  return <C size={size} />;
}

// Render a card's icon: prefer the CMS item SVG (icon bank), else a doodle.
function CardIcon({ card, section, size }) {
  if (card && card.icon) {
    return <span className="svg-ico" style={{ display: 'inline-block', width: size, height: size, color: 'var(--illustration)' }} dangerouslySetInnerHTML={{ __html: card.icon }} />;
  }
  return essIcon(section.title, size);
}

function EssentialCard({ card, section }) {
  const t = window.useT();
  const [open, setOpen] = useStateS(false);
  const hasDetail = (card.qa && card.qa.length > 0) || !!card.video;
  return (
    <>
      <button onClick={() => hasDetail && setOpen(true)} style={{
        border: '1px solid var(--line)', background: 'var(--bg-2)', padding: 16, display: 'flex', flexDirection: 'column', gap: 12,
        textAlign: 'start', cursor: hasDetail ? 'pointer' : 'default', font: 'inherit', transition: 'border-color .15s ease, background .15s ease'
      }}
      onMouseEnter={(e) => {if (!hasDetail) return;e.currentTarget.style.borderColor = 'var(--line-strong)';e.currentTarget.style.background = 'var(--surface)';}}
      onMouseLeave={(e) => {e.currentTarget.style.borderColor = 'var(--line)';e.currentTarget.style.background = 'var(--bg-2)';}}>
        <span style={{ color: 'var(--illustration)' }}><CardIcon card={card} section={section} size={36} /></span>
        <div>
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 14, color: 'var(--fg)', letterSpacing: '0.03em', fontWeight: 400 }}>{card.label}</div>
          {hasDetail &&
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 9.5, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'var(--fg-faint)', marginTop: 10, display: 'inline-flex', alignItems: 'center', gap: 6 }}>
            {t('readMore')}
            <svg width="12" height="9" viewBox="0 0 12 9" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"><path d="M1 4.5h9M7 1l3.5 3.5L7 8" /></svg>
          </div>
          }
        </div>
      </button>
      {open && <DetailModal title={card.label} eyebrow={section.title} icon={<CardIcon card={card} section={section} size={44} />} qa={card.qa} video={card.video} onClose={() => setOpen(false)} />}
    </>);

}

function EssentialsBlock({ stay, dense, collapsible }) {
  const t = window.useT();
  const sections = stay.sections || [];
  if (!sections.length) return null;
  return (
    <CollapsibleSection icon={<SectionIcon stay={stay} name="living" fallback={<window.DoodleDoor size={32} />} />} title={t('livingHere')} sub={t('everythingInApt')} collapsible={collapsible}>
      <div>
        {sections.map((sec, i) =>
        <window.Accordion key={sec.key} title={sec.title}
          summary={sec.cards.map((c) => c.label).slice(0, 3).join(', ').replace(/,([^,]*)$/, ' &$1')}
          dense={dense} defaultOpen={false} last={i === sections.length - 1}>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(168px,1fr))', gap: 12 }}>
              {sec.cards.map((card) => <EssentialCard key={card.id} card={card} section={sec} />)}
            </div>
          </window.Accordion>
        )}
      </div>
    </CollapsibleSection>);

}

// Videos we've already auto-fullscreened, so it's a one-time thing PER VIDEO
// (keyed by URL, minus the token query). Persisted so reopening the item — or
// reloading the page — won't fullscreen the same video again.
const FS_SEEN_KEY = 'mp_fs_seen';
const fsSeenVideos = (() => {
  try { return new Set(JSON.parse(localStorage.getItem(FS_SEEN_KEY) || '[]')); } catch (e) { return new Set(); }
})();
function markVideoFsSeen(key) {
  fsSeenVideos.add(key);
  try { localStorage.setItem(FS_SEEN_KEY, JSON.stringify(Array.from(fsSeenVideos).slice(-200))); } catch (e) {}
}

// ── Fullscreen detail modal (renders item Q&A, answers are CMS HTML) ──
function DetailModal({ title, eyebrow, icon, qa, video, html, children, onClose }) {
  const contentRef = React.useRef(null);
  useEffectS(() => {
    const onKey = (e) => {if (e.key === 'Escape') onClose();};
    window.addEventListener('keydown', onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => {window.removeEventListener('keydown', onKey);document.body.style.overflow = prev;};
  }, []);
  // On mobile, take an answer video fullscreen the first time THAT VIDEO plays —
  // once ever, tracked by URL in fsSeenVideos. (Browsers block fullscreen without
  // a user gesture, so we hook the play event, which is user-initiated.)
  // Marking the video seen BEFORE entering fullscreen also breaks the exit loop:
  // iOS re-fires `play` when you tap "Done" and playback resumes inline, but by
  // then the video is already seen, so it stays inline instead of snapping back.
  useEffectS(() => {
    const isMobile = typeof window !== 'undefined' && ((window.matchMedia && window.matchMedia('(max-width: 859px)').matches) || 'ontouchstart' in window);
    const root = contentRef.current;
    if (!isMobile || !root) return;
    const videos = Array.prototype.slice.call(root.querySelectorAll('video'));
    const goFs = (e) => {
      const v = e.currentTarget;
      const key = (v.currentSrc || v.src || '').split('?')[0]; // stable across token refresh
      if (!key || fsSeenVideos.has(key)) return;               // already auto-fullscreened this video
      if (document.fullscreenElement || document.webkitFullscreenElement) return;
      markVideoFsSeen(key);
      try {
        if (v.webkitEnterFullscreen) v.webkitEnterFullscreen();       // iOS Safari
        else if (v.requestFullscreen) v.requestFullscreen();
        else if (v.webkitRequestFullscreen) v.webkitRequestFullscreen();
      } catch (err) {}
    };
    videos.forEach((v) => { v.setAttribute('playsinline', ''); v.addEventListener('play', goFs); });
    return () => videos.forEach((v) => v.removeEventListener('play', goFs));
  }, []);
  const items = qa && qa.length ? qa : html ? [{ q: '', a: html }] : null;
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 200, background: 'rgba(18,28,20,0.55)', backdropFilter: 'blur(5px)', WebkitBackdropFilter: 'blur(5px)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
      <div onClick={(e) => e.stopPropagation()} style={{ position: 'relative', width: 'min(680px, 100%)', maxHeight: '90vh', overflow: 'auto', background: 'var(--surface)', border: '1px solid var(--line)', boxShadow: 'var(--shadow-medium)' }}>
        <button onClick={onClose} aria-label="Close" style={{ position: 'absolute', top: 18, right: 18, zIndex: 2, width: 38, height: 38, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(249,244,234,0.9)', border: '1px solid var(--line-strong)', color: 'var(--fg-muted)', cursor: 'pointer' }}>
          <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"><path d="M3 3l10 10M13 3L3 13" /></svg>
        </button>
        <div ref={contentRef} style={{ padding: '48px 48px 52px' }}>
          {icon && <span style={{ color: 'var(--illustration)', display: 'inline-block', marginBottom: 18 }}>{icon}</span>}
          {eyebrow && <window.Eyebrow style={{ marginBottom: 10 }}>{eyebrow}</window.Eyebrow>}
          <div style={{ fontFamily: 'var(--font-serif)', fontWeight: 300, fontSize: 34, lineHeight: 1.1, color: 'var(--fg)', marginBottom: 22, textWrap: 'pretty' }}>{title}</div>
          {video &&
          <video src={video} controls playsInline preload="metadata" style={{ width: '100%', display: 'block', background: '#000', marginBottom: items ? 24 : 0 }} />
          }
          {items ?
          <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
              {items.map((it, i) =>
            <div key={i} style={i > 0 ? { paddingTop: 22, borderTop: '1px solid var(--line)' } : null}>
                  {it.q && <div style={{ fontFamily: 'var(--font-accent)', fontStyle: 'italic', fontSize: 16, color: 'var(--fg)', marginBottom: 10 }}>{it.q}</div>}
                  <div className="answer-html" style={{ fontFamily: 'var(--font-mono)', fontSize: 15, lineHeight: 1.65, color: 'var(--fg-muted)', letterSpacing: '0.01em' }} dangerouslySetInnerHTML={{ __html: it.a }} />
                </div>
            )}
            </div> :
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 16, lineHeight: 1.65, color: 'var(--fg-muted)' }}>{children}</div>
          }
        </div>
      </div>
    </div>);

}

// Hand-drawn compass (brand asset) for "Around the corner".
function CompassMark({ size = 26, paper = 'var(--surface)' }) {
  return (
    <svg width={size} height={size} viewBox="0 0 217.42 261.83" aria-hidden="true" style={{ display: 'block' }}>
      <g fill="currentColor" fillRule="evenodd">
      <path d="M217.02,233.59c-1.47-5.46-4.6-11.5-9.52-14.55-6.23-3.87-10.77-6.14-18.21-4.17-.84.2-1.09.25-1.89.74-1.61-.79-1.89-.98-3.73-.88,9.25-12.28,10.7-27.91,9.82-42.79-.13-4.76.07-8.44-1.37-13.08-1.28-4.13-1.95-8.54-3.4-12.72-3.67-10.61-11.49-18.99-19.51-26.54-6.35-5.98-15.4-9.11-23.51-11.69-7.37-2.27-13.81-5.25-21.69-5.12-12.88-.59-24.78-.72-37.15,3.61.56-3.68.08-4.85-3.54-6.03,2.27-2.91,4.58-5.8,6.92-8.66,1.82-2.19,4.07-4.73,5.72-6.99,2.43-3.4,4.68-6.94,6.72-10.59,1.16-2.11,2.43-5.06,3.63-6.89,5.65-8.62,8.58-15.26,9.51-25.65.54-5.99,1.57-11.45-.54-17.6-.27-.93.42-2.8-.11-3.9-1.26-5.34-7.05-10.93-11.17-14.27-4.87-3.95-12.99-6.45-19.33-5.66-4.87.55-8.95,2.27-13.56,3.73-3.07.97-5.81,1.9-8.79,3.16-10.54,4.46-18.24,12.78-26.82,19.99-5.18,4.36-9.84,9.3-14.09,14.58-2.94,3.74-5.33,8.48-6.96,12.89C9.16,66.94.54,78.14.19,92.18c-.19,2.56-.25,5.01-.1,7.59.21,3.45,1.18,6.65,1.96,10.03,1.34,5.83,6.04,10.88,10.92,14.08,2.01,1.32,3.87,2.9,6.02,3.93,1.18.51,2.31.13,3.54.25,4.21.9,8.65,1.68,12.96,1.16,2.38-.29,4.87-1.45,7.2-2.07,1.92-.55,4.07-.98,5.92-1.58-.16,3.42.35,4.92,3.86,5.94-3.32,4.14-7.25,11.57-8.4,16.82-1.12,5.1-2.39,10.98-3.42,16.17-.03.15.18.8.22.98.16,1.27-.05,2.88-.05,4.18,0,2.51-.06,5.01.11,7.5l.69,8.81c.49,6.44.94,10.11,4.31,15.82,1.07,1.97,1.99,5.3,3.25,7.08,3.6,5.09,8.95,13.64,14.32,16.82,10.8,6.38,21.97,15.15,34.42,17.73,5.64,1.17,11.95,1.42,17.68,1.98,3.1.22,7.38-1,10.57-1.03,5.28-.34,10.34-.06,15.58-1.03,1.32-.24,2.61-.61,3.86-1.09,4.36-1.67,9.73-4.86,14.27-6.6,1.58-.6,9.46-5.38,10.44-6.3-1.13,3.64-1.95,8.74-1.5,12.55.93,5.17,2.91,7.67,6.25,11.57,1.07,1.25,2.98,3.53,4.4,4.38,5.4,3.25,12.23,4.79,18.42,3.57,5.85-1.13,12.56-6.46,15.77-11.23,2.71-4.02,4.63-11.84,3.36-16.59ZM184.66,217.01c2.12,1.04,2.67,1.54,3.89,3.54.11,1.52.27,3.92-.23,5.34-1,2.84-5.62,6.12-8.69,5.08,1.84-3.61-.06-7.08-3.94-7.94-1.71-.38-2.36.4-3.68,1.35-3.2-.51-1.87-4.24-.75-6.13,1.7-2.88,3.37-4.64,6.67-5.54,4.36-.17-.17,5.71,6.72,4.29ZM57.07,118.7c-2.39.62-4.7,1.62-7.2,1.92-4.31.52-8.75-.26-12.96-1.16-1.23-.12-2.36.26-3.54-.25-2.15-1.03-4.01-2.61-6.02-3.93-4.88-3.2-9.58-8.25-10.92-14.08-.78-3.38-1.75-6.58-1.96-10.03-.15-2.58-.09-5.03.1-7.59.35-14.04,8.97-25.24,19.83-32.74,1.63-4.41,4.02-9.15,6.96-12.89,4.25-5.28,8.91-10.22,14.09-14.58,8.58-7.21,16.28-15.53,26.82-19.99,2.98-1.26,5.72-2.19,8.79-3.16,4.61-1.46,8.69-3.18,13.56-3.73,6.34-.79,14.46,1.71,19.33,5.66,4.12,3.34,9.91,8.93,11.17,14.27.53,1.1-.16,2.97.11,3.9,2.11,6.15,1.08,11.61.54,17.6-.93,10.39-3.86,17.03-9.51,25.65-1.2,1.83-2.47,4.78-3.63,6.89-2.04,3.65-4.29,7.19-6.72,10.59-1.65,2.26-3.9,4.8-5.72,6.99-2.34,2.86-4.65,5.75-6.92,8.66-12.31-4.02-25.45-3.45-37.79,1.06-1.85.6-4,1.03-5.92,1.58-2.33.62-4.82,1.78-7.2,2.07-4.31.52-8.75-.26-12.96-1.16Z"></path>
      <path d="M166.01,127.82c-12.84-12.36-30.07-19.11-47.89-18.76-.29,0-.52.02-.8.04-15.81,1.2-27.69,2.02-41.08,12.08-3.78,2.66-7.77,5.79-10.76,9.34-5.83,6.93-11.5,18.69-10.77,27.8.81,10.16,3.96,19.62,11.16,26.97,2.2,2.25,4.26,4.96,6.77,6.98,3.59,2.78,7.1,6.22,11.22,8.2,5.53,2.65,11.9,3.78,17.68,5.64,5.94,1.9,9.28,2.79,15.66,2.8,1.97,0,6.19.11,8.02-.17,4.4-.67,9.47-2.08,13.75-3.36,2.32-.87,5.22-.95,7.5-1.7,7.83-2.58,15.35-8.36,21.36-13.91,8.07-7.46,11.91-20.47,11.99-31.2.09-11.04-5.96-23.08-13.81-30.75ZM168.31,185.94c-4.93,5.75-14.54,13.02-21.69,15.57-3.19.87-6.38,1.47-9.58,2.35-3.73,1.02-7.57,2.44-11.34,3.21-2,.41-4.64.09-6.72.29l-.3.03c-2.86.35-5.67-.05-8.51-.43-3.29-.45-6.78-1.82-10.08-2.65-4.46-1.12-11.26-3-15.27-4.92-6.27-3.01-12.64-9.64-16.88-15.13-1.26-1.64-2.64-3.09-3.9-4.8-3.2-4.35-4.8-8.31-5.8-13.56-.19-1.01-.63-2.68-.47-3.67,1.43-1.87,1.87-6.83,3.07-9.32,1.71-3.69,3.82-7.34,6.04-10.75.7-1.08,1.96-2.18,2.75-3.24,10.85-14.64,29.62-17.33,47.07-18.71.26-.03.52-.05.77-.07,3.15-.24,10.79.33,13.83,1.18,8.91,2.49,19.46,7.72,27.21,12.72,3.15,2.03,8.88,7.46,10.68,10.44,2.05,3.41,3.66,7.02,5.22,10.67.92,2.16,1.38,5.05,3.21,6.53-.9,8.45-3.66,17.72-9.3,24.29Z"></path>
      </g>
    </svg>);

}

// ── Local guide (from `information` content) ──────────────────────
function GuideBlock({ stay, collapsible }) {
  const t = window.useT();
  const [active, setActive] = useStateS(null);
  const guide = stay.guide || [];
  if (!guide.length) return null;
  return (
    <CollapsibleSection icon={<SectionIcon stay={stay} name="explore" fallback={<CompassMark size={32} />} />} title={t('aroundCorner')} sub={stay.city ? t('ourPicksIn', { city: stay.city }) : ''} collapsible={collapsible}>
      <div style={{ display: 'flex', flexDirection: 'column' }}>
        {guide.map((g, i) => {
          const clickable = !!g.html;
          const last = i === guide.length - 1;
          return (
            <div key={i} onClick={() => clickable && setActive(g)} role={clickable ? 'button' : undefined} tabIndex={clickable ? 0 : undefined}
            onKeyDown={(e) => {if (clickable && (e.key === 'Enter' || e.key === ' ')) {e.preventDefault();setActive(g);}}}
            style={{ display: 'flex', alignItems: 'center', gap: 16, padding: '16px 4px', borderBottom: last ? 'none' : '1px solid var(--line)', cursor: clickable ? 'pointer' : 'default' }}>
              <span style={{ width: 28, height: 28, borderRadius: 999, border: '1px solid var(--line-strong)', color: 'var(--fg-muted)', fontFamily: 'var(--font-mono)', fontSize: 12.5, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>{i + 1}</span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontFamily: 'var(--font-mono)', fontSize: 14, color: 'var(--fg)', letterSpacing: '0.02em' }}>{g.name}</div>
                {g.meta && <div style={{ fontFamily: 'var(--font-accent)', fontStyle: 'italic', fontSize: 12.5, color: 'var(--fg-faint)', marginTop: 3 }}>{g.meta}</div>}
              </div>
              {clickable &&
              <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" style={{ color: 'var(--fg-muted)', flexShrink: 0 }}><path d="M5 2l6 6-6 6" /></svg>
              }
            </div>);

        })}
      </div>
      {active && <DetailModal title={active.name} eyebrow={t('aroundCorner')} html={active.html} onClose={() => setActive(null)} />}
    </CollapsibleSection>);

}

// ── Support / contact host ────────────────────────────────────────
const ContactGlyphs = {
  whatsapp: <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"><path d="M8 1.7a6.3 6.3 0 0 0-5.4 9.5L1.7 14.3l3.2-.9A6.3 6.3 0 1 0 8 1.7Z" /><path d="M5.8 5.4c.5 1.6 2 3.2 3.7 3.8.6.2 1-.2 1.3-.6" /></svg>,
  email: <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"><rect x="1.5" y="3" width="13" height="10" rx="1" /><path d="M2 4l6 5 6-5" /></svg>,
  call: <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"><path d="M3 2.5h2.5l1 3-1.6 1a8 8 0 0 0 3.6 3.6l1-1.6 3 1V15a1 1 0 0 1-1 1A11.5 11.5 0 0 1 2 4.5a1 1 0 0 1 1-1Z" /></svg>
};
const WHATSAPP = 'https://wa.me/4366012345678';
const HOST_TEL = 'tel:+4366012345678';

function SupportBlock({ stay, compact }) {
  const t = window.useT();
  const subject = encodeURIComponent(`${stay.building || 'master'}${stay.code ? ' · ' + stay.code : ''}`);
  const email = `mailto:hello@staymaster.com?subject=${subject}`;
  return (
    <div style={{ background: 'var(--bg-2)', padding: compact ? 22 : 28, border: '1px solid var(--line)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 14 }}>
        <span style={{ color: 'var(--illustration)' }}><SectionIcon stay={stay} name="support" fallback={<window.DoodleChat size={32} />} size={32} /></span>
        <div>
          <div style={{ fontFamily: 'var(--font-serif)', fontWeight: 300, fontSize: 20, color: 'var(--fg)' }}>{t('needHand')}</div>
          <window.Accent style={{ fontSize: 13 }}>{t('hostRepliesMinutes')}</window.Accent>
        </div>
      </div>
      <div style={{ fontFamily: 'var(--font-mono)', fontSize: 13.5, color: 'var(--fg-muted)', lineHeight: 1.6, marginBottom: 18 }}>
        {t('supportBody')}
      </div>
      <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
        <window.Btn variant="solid" onClick={() => window.open(WHATSAPP, '_blank')}>{ContactGlyphs.whatsapp} {t('whatsapp')}</window.Btn>
        <window.Btn variant="outline" onClick={() => {window.location.href = email;}}>{ContactGlyphs.email} {t('email')}</window.Btn>
        <window.Btn variant="outline" onClick={() => {window.location.href = HOST_TEL;}}>{ContactGlyphs.call} {t('call')}</window.Btn>
      </div>
    </div>);

}

// ── Checkout card ─────────────────────────────────────────────────
const CHECKOUT_URL = 'https://europe-west1-master-guide-56f7e.cloudfunctions.net/completeCheckout';

// Only shown on the departure day (property-local); the button notifies the
// check-out webhook via the completeCheckout function.
function CheckoutBlock({ stay }) {
  const t = window.useT();
  const { lang } = window.useLang();
  const [done, setDone] = useStateS(false);
  const [busy, setBusy] = useStateS(false);
  const [failed, setFailed] = useStateS(false);
  if (!stay.isCheckoutDay) return null;

  const submit = async () => {
    setBusy(true); setFailed(false);
    try {
      const r = await fetch(`${CHECKOUT_URL}?res=${encodeURIComponent(stay.reservation.resId || '')}`, { method: 'POST' });
      if (!r.ok) throw new Error('failed');
      setDone(true);
    } catch (e) {
      setFailed(true);
    } finally {
      setBusy(false);
    }
  };

  const steps = [t('coBelongings'), t('coLights'), stay.buildingCode ? t('coKeys', { code: stay.buildingCode }) : null, t('coDoor')].filter(Boolean);
  const sub = [stay.checkOut.date, stay.checkOut.time].filter(Boolean).join(' · ');
  return (
    <div>
      <SectionHead icon={<SectionIcon stay={stay} name="checkout" fallback={<window.DoodleCalendar size={32} />} />} title={t('checkOut')} sub={sub} />
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 20 }}>
        {steps.map((s, i) =>
        <div key={i} style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
            <span style={{ width: 26, height: 26, borderRadius: 999, border: '1px solid var(--line-strong)', color: 'var(--fg-muted)', fontFamily: 'var(--font-mono)', fontSize: 12, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>{i + 1}</span>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 13.5, color: 'var(--fg-muted)', letterSpacing: '0.02em' }}>{s}</span>
          </div>
        )}
      </div>
      {done ?
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, background: 'var(--bg-2)', border: '1px solid var(--line)', padding: '16px 18px' }}>
          <span style={{ width: 30, height: 30, borderRadius: 999, background: 'var(--brand)', color: 'var(--on-brand)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
            <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8.5l3.5 3.5L13 4" /></svg>
          </span>
          <div>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 13.5, color: 'var(--fg)', letterSpacing: '0.02em' }}>{t('checkedOut', { name: stay.reservation.guest.first || '' })}</div>
            <window.Accent style={{ fontSize: 12.5 }}>{t('hostAgain')}</window.Accent>
          </div>
        </div> :
      <div>
        <window.Btn variant="solid" full onClick={busy ? undefined : submit} style={{ justifyContent: lang === 'he' ? 'flex-end' : 'center', flexDirection: lang === 'he' ? 'row-reverse' : 'row', opacity: busy ? 0.6 : 1 }}>
          <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M10 2h3v3M13 2l-5.5 5.5M7 3H3.5A1.5 1.5 0 0 0 2 4.5v8A1.5 1.5 0 0 0 3.5 14h8a1.5 1.5 0 0 0 1.5-1.5V9" /></svg>
          {busy ? '…' : t('completeCheckout')}
        </window.Btn>
        {failed &&
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--color-red)', marginTop: 10, textAlign: 'center' }}>{t('checkoutFailed')}</div>
        }
      </div>
      }
    </div>);

}

// ── Luggage storage — a Need-assistance-style box shown only on the departure
// day, and only when a service is flagged `isLuggageStorage` in admin (its
// description is the CMS body here). No buttons. ─────────────────────
function LuggageBlock({ stay, compact }) {
  const t = window.useT();
  if (!stay.isCheckoutDay || !stay.luggage || !stay.luggage.html) return null;
  return (
    <div style={{ background: 'var(--bg-2)', padding: compact ? 22 : 28, border: '1px solid var(--line)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 14 }}>
        <span style={{ color: 'var(--illustration)' }}><SectionIcon stay={stay} name="luggage" fallback={<window.DoodleSuitcase size={32} />} size={32} /></span>
        <div>
          <div style={{ fontFamily: 'var(--font-serif)', fontWeight: 300, fontSize: 20, color: 'var(--fg)' }}>{t('luggageStorage')}</div>
          <window.Accent style={{ fontSize: 13 }}>{t('luggageStorageSub')}</window.Accent>
        </div>
      </div>
      <div className="answer-html" style={{ fontFamily: 'var(--font-mono)', fontSize: 13.5, color: 'var(--fg-muted)', lineHeight: 1.6 }} dangerouslySetInnerHTML={{ __html: stay.luggage.html }} />
    </div>);

}

// ── Breakfast — styled like the other sections (SectionHead: icon + serif
// title + "included in your stay" sub). On mobile (`collapsible`) the header
// toggles the body via a chevron. ─────────────────────────────────
function BreakfastBlock({ stay, collapsible }) {
  const t = window.useT();
  const b = stay.breakfast;
  if (!b) return null;
  const body = b.html ?
  <div className="answer-html breakfast-content" dangerouslySetInnerHTML={{ __html: b.html }} /> :
  <div style={{ fontFamily: 'var(--font-mono)', fontSize: 16, color: 'var(--fg)' }}>{t('breakfastIncluded')}</div>;
  return (
    <CollapsibleSection icon={<SectionIcon stay={stay} name="breakfast" fallback={<window.DoodleEgg size={32} />} />} title={b.label || t('breakfast')} sub={t('breakfastIncluded')} collapsible={collapsible}>
      {body}
    </CollapsibleSection>);

}

// Card wrapper used by layouts.
function Panel({ children, style, pad = 28 }) {
  return <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', padding: pad, ...style }}>{children}</div>;
}

Object.assign(window, { WeatherChip, StayHero, AccessBlock, AddressBlock, SectionHead, CollapsibleSection, EssentialsBlock, EssentialCard, DetailModal, GuideBlock, SupportBlock, CheckoutBlock, BreakfastBlock, LuggageBlock, Panel, Fact });
