/* ============================================================
 * Module page content, one entry per slug
 * Each module page renders <ModulePage module={...} content={...} />
 * ============================================================ */

const moduleContent = {
  inbox: {
    eyebrow: "Inboxes",
    h1: 'Every guest message, <span class="italic-accent">on every channel</span>, in one shared inbox.',
    lead: "WhatsApp, Instagram, Facebook, OTAs, email, web chat, voice, every conversation lands in one shared thread, with one shared history. AI handles the easy ones, your team handles the rest.",
    stats: [
      { value: "62%", label: "of guest messages on WhatsApp" },
      { value: "<3s", label: "avg AI response time" },
      { value: "8", label: "channels, one thread" },
    ],
    heroVisual: <InboxHeroVisual />,
    featuresTitle: 'Built for the <span class="italic-accent">multi-channel</span> reality of hospitality.',
    features: [
      {
        title: "One thread per guest",
        body: "Anna messages on Instagram in March, calls in April, replies to email in May, it's all one thread, with one memory.",
        bullets: ["Guest identity stitched across channels", "Native rich media: photos, voice notes, attachments", "Read receipts + typing indicators where supported"],
      },
      {
        title: "AI-first triage",
        body: "Most guest messages are simple, pet policy, check-in time, spa hours. Our AI handles those instantly. Your team only sees the ones that matter.",
        bullets: ["Auto-reply with full property context", "Soft escalation to humans, never lost", "Confidence threshold per channel"],
      },
      {
        title: "Shared by design",
        body: "Front desk, reservations, marketing, everyone sees the same view. No more screenshots between WhatsApp and the PMS.",
        bullets: ["Internal notes & @mentions", "Assignments & SLAs", "Multi-property routing"],
      },
    ],
    howTitle: "From <em>scattered</em> to <span class=\"italic-accent\">one stream</span>, in 3 steps.",
    steps: [
      {
        title: "Connect your channels",
        body: "Hook up WhatsApp Business, Instagram, Facebook, your OTA inboxes, your email, web chat widget, and voice. Most go live in under an hour.",
        detail: "WhatsApp Business API · Meta Cloud API · Booking.com Messaging · Gmail / Outlook / IMAP · Twilio Voice · Custom webhook for any other source.",
      },
      {
        title: "Train your AI on your hotel",
        body: "Drop in your fact sheets, room types, policies, FAQ, restaurant menus, spa hours. The AI grounds every reply in your actual property, never invents.",
        detail: "Knowledge base auto-syncs from your PMS, website, and uploaded docs. Test with sample questions before going live.",
      },
      {
        title: "Go live with confidence",
        body: "Start in human-review mode, AI drafts, your team approves with one click. As trust grows, switch to full auto on the channels you choose.",
        detail: "Per-channel auto / draft / off · Per-question-type rules · Real-time quality scoring on every reply.",
      },
    ],
    useCasesTitle: "What teams use the Inbox for, every day.",
    useCases: [
      {
        tag: "Reservations",
        title: "Pre-stay questions, instantly",
        body: "\"Can I add a crib?\" \"Are pets welcome?\" \"What time is check-in?\", answered in their language, in seconds, day or night.",
        outcome: "−74% time-to-first-reply",
      },
      {
        tag: "Front Desk",
        title: "In-stay service, no app needed",
        body: "Towels, late checkout, spa booking, restaurant reservation, guests message, the right team picks it up, the AI fills the gaps.",
        outcome: "+38 NPS in-stay",
      },
      {
        tag: "Marketing",
        title: "Win the OTA inbox",
        body: "Reply first in the OTA inbox. Move OTA guests onto direct channels for the next stay, automatically and politely.",
        outcome: "+22% direct rebookings",
      },
    ],
    integrationsTitle: 'Plugs into the channels guests <span class="italic-accent">already use</span>.',
    integrationsBody: "We bring the messages in. We send the replies out. Your channels stay theirs, we just give your team one place to live.",
    integrations: [
      { label: "Messaging", items: ["WhatsApp Business", "Instagram DM", "Facebook Messenger", "Apple Business Chat", "Telegram"] },
      { label: "OTA & Booking", items: ["Booking.com Messaging", "Expedia Conversations", "Airbnb Messages"] },
      { label: "Email & Voice", items: ["Gmail", "Outlook 365", "Twilio Voice", "Aircall", "Vonage"] },
      { label: "Web", items: ["GuestMaker Web Widget", "Custom HTML embed"] },
    ],
    testimonial: {
      quote: "Our front desk team went from <strong>tab-switching across 6 systems</strong> to one shared inbox. We respond in minutes, even at 2am.",
      name: "Marta Vidal",
      role: "Director of Guest Experience · Lumina Hotels",
    },
    faq: [
      { q: "Does the AI ever reply on its own?", a: "Only on the channels and question types you choose. You can run the whole inbox in human-review mode for the first weeks, then graduate specific question types to full auto as confidence grows." },
      { q: "What happens to ongoing conversations during onboarding?", a: "Existing threads on each channel keep working, the inbox layers on top. We import the last 90 days of message history so the AI has context from day one." },
      { q: "How is guest identity stitched across channels?", a: "By phone number, email, booking reference, and OTA confirmation code. When a match is found, we merge profiles automatically; you can also merge or split manually." },
      { q: "Does it work in multiple languages?", a: "Yes, over 60 languages out of the box. Replies are in the language the guest writes in, with the tone and policy of your hotel." },
      { q: "Can my team still use WhatsApp Business directly?", a: "Yes. We sit alongside the official WhatsApp Business app. Most teams stop using it within a few weeks, but you can keep both running." },
      { q: "How does pricing work?", a: "Flat per-property monthly fee, no per-message charges. Channel costs (e.g. WhatsApp conversation fees) are pass-through at cost." },
    ],
    ctaTitle: 'See your guest conversations <span class="italic-accent">in one place</span>.',
    ctaBody: "30-minute demo with real conversations from a hotel like yours, your data sources, and the team that built GuestMaker.",
  },

  voice: {
    eyebrow: "Voice Agent",
    h1: 'Always-on AI phone. <span class="italic-accent">No menus. No hold music.</span>',
    lead: "A voice agent that picks up on the first ring, books the room, answers the FAQ, and knows when to hand off to a human. Same brain as the inbox, same memory across every guest.",
    stats: [
      { value: "<800ms", label: "first response" },
      { value: "60+", label: "languages, voice-matched" },
      { value: "24/7", label: "no IVR, no queues" },
    ],
    heroVisual: <VoiceHeroVisual />,
    featuresTitle: 'A receptionist that <span class="italic-accent">never sleeps</span>, never forgets.',
    features: [
      {
        title: "Natural, not robotic",
        body: "Sub-second turn-taking, real interruption handling, voice cloning per property. Guests don't realise they're talking to AI until you tell them.",
        bullets: ["Streaming TTS + barge-in", "Branded voice per hotel", "Tone-matched to your property"],
      },
      {
        title: "Books, modifies, cancels, in voice",
        body: "Connected directly to your PMS and booking engine. Availability lookup, payment links by SMS, ID and card capture, confirmation in the guest's language.",
        bullets: ["Live PMS availability", "SMS-linked payment", "Multi-language confirmations"],
      },
      {
        title: "Handoff that actually works",
        body: "When confidence drops or a guest asks for a human, the call transfers to the right team with full context already on screen. No \"can you repeat that?\".",
        bullets: ["Warm transfer with summary", "Per-property routing rules", "Recording + transcript on the CRM profile"],
      },
    ],
    howTitle: "From <em>missed calls</em> to <span class=\"italic-accent\">answered every time</span>, in 3 steps.",
    steps: [
      {
        title: "Get a number, or port yours",
        body: "Pick a local or toll-free number per property, or forward your existing line to the AI. Goes live in 24 hours.",
        detail: "Twilio · Aircall · Vonage · SIP forwarding · Number porting in 30+ countries.",
      },
      {
        title: "Train on your property",
        body: "Same knowledge base as the inbox, room types, rates, restaurant hours, spa policy. The AI knows your hotel before the first call.",
        detail: "Auto-sync from PMS · Custom answers per question type · Voice-test before go-live.",
      },
      {
        title: "Go live with safety nets",
        body: "Start with after-hours only. Promote to full-time once confidence is there. Every call recorded, transcribed, and scored.",
        detail: "After-hours / overflow / 24-7 modes · Real-time call monitoring · One-click handoff to a human.",
      },
    ],
    useCasesTitle: "What hotel groups use the Voice Agent for.",
    useCases: [
      {
        tag: "Front Desk",
        title: "Never miss a booking call",
        body: "Most missed calls are reservation inquiries. The AI picks up every one, qualifies the guest, and books, even at 3am on a Sunday.",
        outcome: "+18% direct booking conversion",
      },
      {
        tag: "Reservations",
        title: "Routine FAQs, instantly",
        body: "\"What time is breakfast?\" \"Is parking free?\" \"Can I extend?\", answered in seconds, in their language, with full property context.",
        outcome: "−65% routine call volume",
      },
      {
        tag: "Group Operations",
        title: "Overflow without hiring",
        body: "Peak season, busy mornings, lunch hour, the AI absorbs overflow so your team only takes the calls that need a human.",
        outcome: "0 abandoned calls",
      },
    ],
    integrationsTitle: 'Plugs into the <span class="italic-accent">phone stack</span> you already have.',
    integrationsBody: "Use your existing numbers, your existing PBX, your existing routing. The Voice Agent slots in as one more extension, but the smartest one.",
    integrations: [
      { label: "Telephony", items: ["Twilio Voice", "Aircall", "Vonage", "Plivo", "SIP / SIP Trunk"] },
      { label: "PMS / Booking", items: ["Mews", "Cloudbeds", "Apaleo", "Opera", "SiteMinder", "Booking Engine API"] },
      { label: "Numbers", items: ["Local numbers in 30+ countries", "Toll-free", "Number porting", "Per-property routing"] },
      { label: "Handoff", items: ["Front desk transfer", "Mobile escalation", "Slack / Teams alerts", "After-hours rules"] },
    ],
    testimonial: {
      quote: "We used to lose <strong>~40 calls a week to voicemail</strong>. Now the AI picks up every one, books most of them, and only sends the tricky ones to my team.",
      name: "David Lefèvre",
      role: "Reservations Manager · Côte d'Azur Resorts",
    },
    faq: [
      { q: "Will guests know they're talking to AI?", a: "If a guest asks, the agent identifies as a virtual assistant, and they can ask for a human at any moment. Voice quality is good enough that most callers don't mind, but transparency is a hard rule, not a setting." },
      { q: "What happens if the AI can't answer?", a: "The agent tries to clarify once, then either transfers to the right team with a written summary, or opens a ticket and follows up by email or SMS once a human has answered. No call is abandoned and no question goes silent." },
      { q: "Can it take payments?", a: "Card details are never read out loud. The agent sends a secure payment link by SMS or email mid-call and confirms in voice the moment the payment lands. Safer for the guest, simpler for compliance." },
      { q: "What languages and voices are supported?", a: "27+ languages, with the voice auto-detected from the caller's phone country code and confirmed in the first sentence. You pick a brand voice per property from a 700+ library, and the agent switches mid-call when a guest changes language." },
      { q: "How is call data handled?", a: "Recordings, transcripts and memories are encrypted at rest, scoped per tenant, retained on your policy (default 90 days), and never used to train shared models. Each call has a one-click 'forget this guest' that purges memory across the platform." },
      { q: "Can I keep my existing phone numbers?", a: "Yes. Either forward your existing numbers to the agent, or port them in. Porting takes one to four weeks depending on the country and carrier; forwarding is live the same day." },
      { q: "How does it handle group bookings, MICE and complaints?", a: "Each call is classified into one of fourteen escalation themes in real time. Group bookings, MICE enquiries, weddings and complaints route to the right team with a translated summary and the full transcript, so the human picks up exactly where the agent left off." },
      { q: "Does it know my hotel's policies, F&B and spa details?", a: "Yes. The agent reads from the same Knowledge Base that powers the inbox and web chat, scoped per property. Cancellation rules, breakfast hours, vegan menus, parking rates, pet policy, spa packages: all answered with the source cited." },
    ],
    hideDefaults: true,
    useBigCta: true,
    ctaTitle: 'Hear the voice agent <span class="italic-accent">running on your hotels.</span>',
    ctaBody: "We'll spin up the agent on a real property: your numbers, your rates, your knowledge base, your tone of voice. Bring a phone; we'll call you within thirty minutes.",
  },
};

/* ===== Hero Visual: Inbox ===== */
function InboxHeroVisual() {
  return (
    <div className="ihv">
      <div className="ihv-tabs">
        {[
          { ch: "whatsapp", count: 4, active: true },
          { ch: "instagram", count: 2 },
          { ch: "email", count: 7 },
          { ch: "voice", count: 1 },
          { ch: "booking", count: 3 },
        ].map((t, i) => {
          const Ic = (window.channelIcon || {})[t.ch];
          return (
            <div key={i} className={`ihv-tab ${t.active ? "active" : ""}`} style={{ color: (window.channelHue || {})[t.ch] }}>
              {Ic && <Ic size={13} stroke={2} />}
              <span className="ihv-tab-count">{t.count}</span>
            </div>
          );
        })}
        <div className="ihv-tab-spacer" />
        <div className="ihv-tab-search">
          <IconSearch size={11} stroke={2} /> Search
        </div>
      </div>

      <div className="ihv-thread">
        {[
          { who: "Anna Müller", ch: "instagram", text: "Loved your sunset photos! Do you have spa packages?", time: "Mon · 14:02", side: "in" },
          { who: "Lumina · AI", ch: "instagram", text: "Hi Anna! 3 packages available. Sending the menu now ✨", time: "Mon · 14:02", side: "out", ai: true },
          { who: "Anna Müller", ch: "whatsapp", text: "Quick q, pets allowed? Bringing my dachshund 🐕", time: "Sat · 10:14", side: "in" },
          { who: "Lumina · AI", ch: "whatsapp", text: "Yes! Pets up to 8kg are welcome at €15/night. Anything else?", time: "Sat · 10:14", side: "out", ai: true },
        ].map((m, i) => {
          const Ic = (window.channelIcon || {})[m.ch];
          const hue = (window.channelHue || {})[m.ch];
          return (
            <div key={i} className={`ihv-msg ${m.side}`}>
              <div className="ihv-msg-meta">
                <span className="ihv-msg-ch" style={{ color: hue }}>{Ic && <Ic size={9} stroke={2} />}</span>
                <span className="ihv-msg-who">{m.who}</span>
                {m.ai && <span className="ihv-ai">AI</span>}
                <span className="ihv-msg-time">{m.time}</span>
              </div>
              <div className="ihv-msg-bubble">{m.text}</div>
            </div>
          );
        })}
      </div>

      <div className="ihv-thread-foot">
        <span className="ihv-thread-foot-dot" />
        Same guest. Same memory. Across every channel.
      </div>

      <style>{`
        .ihv { display: flex; flex-direction: column; gap: 12px; min-height: 420px; }
        .ihv-tabs {
          display: flex; gap: 6px; align-items: center;
          padding-bottom: 12px;
          border-bottom: 1px solid var(--hairline);
        }
        .ihv-tab {
          display: inline-flex; gap: 5px; align-items: center;
          padding: 5px 9px;
          font-family: var(--f-mono); font-size: 11px;
          background: var(--bg-elev-2);
          border: 1px solid var(--hairline);
          border-radius: 999px;
          color: var(--ink-3);
        }
        .ihv-tab.active {
          background: color-mix(in srgb, currentColor 10%, var(--bg));
          border-color: color-mix(in srgb, currentColor 35%, transparent);
        }
        .ihv-tab-count { color: var(--ink-3); font-size: 10.5px; }
        .ihv-tab-spacer { flex: 1; }
        .ihv-tab-search {
          display: inline-flex; gap: 5px; align-items: center;
          padding: 5px 10px;
          font-family: var(--f-mono); font-size: 11px;
          color: var(--ink-3);
          background: var(--bg-elev-2);
          border: 1px solid var(--hairline);
          border-radius: 6px;
        }
        .ihv-thread { display: flex; flex-direction: column; gap: 10px; }
        .ihv-msg { display: flex; flex-direction: column; gap: 4px; max-width: 86%; }
        .ihv-msg.out { align-self: flex-end; align-items: flex-end; }
        .ihv-msg-meta {
          display: inline-flex; align-items: center; gap: 8px;
          font-family: var(--f-mono); font-size: 10.5px;
          color: var(--ink-3);
        }
        .ihv-msg-who { color: var(--ink-2); font-weight: 500; }
        .ihv-msg-time { font-size: 10px; opacity: 0.7; }
        .ihv-ai {
          padding: 1px 5px; font-size: 9px; font-weight: 600; letter-spacing: 0.06em;
          background: var(--accent); color: white; border-radius: 3px;
        }
        .ihv-msg-bubble {
          padding: 10px 14px;
          background: var(--bg-elev-2);
          border: 1px solid var(--hairline);
          border-radius: 12px;
          font-size: 13.5px; line-height: 1.45;
          color: var(--ink);
        }
        .ihv-msg.out .ihv-msg-bubble {
          background: color-mix(in srgb, var(--accent) 10%, var(--bg-elev-2));
          border-color: color-mix(in srgb, var(--accent) 25%, transparent);
        }
        .ihv-thread-foot {
          margin-top: auto;
          padding-top: 12px;
          border-top: 1px dashed var(--hairline);
          display: flex; gap: 8px; align-items: center;
          font-family: var(--f-mono); font-size: 11px;
          color: var(--ink-3);
          text-transform: uppercase; letter-spacing: 0.06em;
        }
        .ihv-thread-foot-dot {
          width: 5px; height: 5px; border-radius: 50%;
          background: var(--accent);
          box-shadow: 0 0 8px var(--accent);
          animation: pulse 2s infinite;
        }
      `}</style>
    </div>
  );
}

/* ===== Hero Visual: Voice ===== */
function VoiceHeroVisual() {
  return (
    <div className="vhv">
      {/* Top: incoming call card */}
      <div className="vhv-call">
        <div className="vhv-call-head">
          <span className="vhv-pulse"><span /></span>
          <span className="vhv-call-label">Incoming · Lumina Mallorca</span>
          <span className="vhv-call-time">00:14</span>
        </div>
        <div className="vhv-call-body">
          <div className="vhv-call-num">+34 971 ··· ··12</div>
          <div className="vhv-call-meta">Returning guest · Anna M. · 3 stays</div>
        </div>
        {/* Voice waveform */}
        <div className="vhv-wave">
          {Array.from({ length: 28 }).map((_, i) => (
            <span key={i} style={{ animationDelay: `${(i * 0.07) % 1.4}s`, height: `${20 + ((i * 7) % 60)}%` }} />
          ))}
        </div>
      </div>

      {/* Live transcript */}
      <div className="vhv-trans">
        <div className="vhv-trans-head">
          <span className="vhv-dot" /> Live transcript
          <span className="vhv-trans-meta">EN · auto-translated to ES</span>
        </div>
        <div className="vhv-trans-body">
          <div className="vhv-line vhv-line-guest">
            <span className="vhv-who">Guest</span>
            <span>Hi, can I add a crib for my room next Friday?</span>
          </div>
          <div className="vhv-line vhv-line-ai">
            <span className="vhv-who vhv-who-ai">AI</span>
            <span>Of course Anna, I see your booking from <em>Mar 14–17</em>. A crib is included free in our family rooms, I'll add one. Anything else?</span>
          </div>
          <div className="vhv-line vhv-line-guest typing">
            <span className="vhv-who">Guest</span>
            <span className="vhv-typing"><i /><i /><i /></span>
          </div>
        </div>
      </div>

      {/* Action bar */}
      <div className="vhv-actions">
        <div className="vhv-action">
          <span className="vhv-action-tag">PMS</span> Booking #L-3491 updated
        </div>
        <div className="vhv-action">
          <span className="vhv-action-tag">SMS</span> Confirmation sent
        </div>
        <div className="vhv-action vhv-action-warn">
          <span className="vhv-action-tag warn">Handoff</span> 1 click to front desk
        </div>
      </div>

      <style>{`
        .vhv { display: flex; flex-direction: column; gap: 14px; }
        .vhv-call {
          padding: 22px 24px;
          background: linear-gradient(135deg, color-mix(in srgb, var(--c-voice) 14%, var(--bg-elev-2)), var(--bg-elev-2));
          border: 1px solid color-mix(in srgb, var(--c-voice) 30%, transparent);
          border-radius: 14px;
          position: relative; overflow: hidden;
        }
        .vhv-call-head {
          display: flex; align-items: center; gap: 10px;
          font-family: var(--f-mono); font-size: 11px;
          color: var(--ink-2); text-transform: uppercase; letter-spacing: 0.08em;
        }
        .vhv-call-time { margin-left: auto; color: var(--c-voice); font-weight: 600; }
        .vhv-pulse { position: relative; width: 10px; height: 10px; }
        .vhv-pulse span {
          position: absolute; inset: 0; border-radius: 50%; background: var(--c-voice);
          animation: vhvPulse 1.4s ease-out infinite;
        }
        .vhv-pulse::after {
          content: ""; position: absolute; inset: 2px; border-radius: 50%; background: var(--c-voice);
        }
        @keyframes vhvPulse { 0% { transform: scale(0.6); opacity: 0.7; } 100% { transform: scale(2.4); opacity: 0; } }
        .vhv-call-body { margin-top: 18px; }
        .vhv-call-num { font-family: var(--f-display); font-size: 28px; color: var(--ink); letter-spacing: -0.01em; }
        .vhv-call-meta { margin-top: 4px; font-size: 13px; color: var(--ink-3); }
        .vhv-wave {
          margin-top: 18px;
          display: flex; align-items: center; gap: 3px;
          height: 36px;
        }
        .vhv-wave span {
          flex: 1; min-height: 14%;
          background: linear-gradient(180deg, var(--c-voice), color-mix(in srgb, var(--c-voice) 30%, transparent));
          border-radius: 2px;
          animation: vhvWave 1.4s ease-in-out infinite;
        }
        @keyframes vhvWave { 0%, 100% { transform: scaleY(0.5); } 50% { transform: scaleY(1); } }

        .vhv-trans {
          padding: 16px 18px; background: var(--bg-elev-1); border: 1px solid var(--hairline); border-radius: 12px;
        }
        .vhv-trans-head {
          display: flex; align-items: center; gap: 8px;
          font-family: var(--f-mono); font-size: 11px;
          color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.08em;
          padding-bottom: 12px; border-bottom: 1px solid var(--hairline); margin-bottom: 12px;
        }
        .vhv-trans-meta { margin-left: auto; color: var(--ink-4); text-transform: none; letter-spacing: 0.04em; }
        .vhv-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--c-voice); animation: vhvBlink 1.5s ease-in-out infinite; }
        @keyframes vhvBlink { 50% { opacity: 0.3; } }
        .vhv-trans-body { display: flex; flex-direction: column; gap: 10px; }
        .vhv-line { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; line-height: 1.5; }
        .vhv-line.typing { opacity: 0.7; }
        .vhv-who {
          flex-shrink: 0; width: 44px;
          font-family: var(--f-mono); font-size: 10px; color: var(--ink-3);
          text-transform: uppercase; letter-spacing: 0.08em;
          padding-top: 3px;
        }
        .vhv-who-ai { color: var(--c-voice); font-weight: 600; }
        .vhv-typing { display: inline-flex; gap: 4px; padding: 8px 0; }
        .vhv-typing i {
          width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3);
          animation: vhvDot 1.2s ease-in-out infinite;
        }
        .vhv-typing i:nth-child(2) { animation-delay: 0.15s; }
        .vhv-typing i:nth-child(3) { animation-delay: 0.3s; }
        @keyframes vhvDot { 50% { transform: translateY(-3px); opacity: 1; } 0%, 100% { opacity: 0.4; } }

        .vhv-actions { display: flex; flex-direction: column; gap: 6px; }
        .vhv-action {
          padding: 10px 14px;
          background: var(--bg-elev-1); border: 1px solid var(--hairline); border-radius: 10px;
          font-size: 13px; color: var(--ink-2);
          display: flex; align-items: center; gap: 10px;
        }
        .vhv-action-tag {
          font-family: var(--f-mono); font-size: 10px;
          padding: 3px 7px; border-radius: 4px;
          background: color-mix(in srgb, var(--c-voice) 14%, transparent);
          color: var(--c-voice); letter-spacing: 0.06em;
        }
        .vhv-action-tag.warn {
          background: color-mix(in srgb, var(--accent) 14%, transparent);
          color: var(--accent);
        }
      `}</style>
    </div>
  );
}

/* ===== Generic stub content for the other modules, same shape, lighter copy ===== */
function makeStubContent(mod) {
  return {
    eyebrow: mod.label,
    h1: `${mod.label}.<br/><span class="italic-accent">${mod.sub}</span>`,
    lead: `${mod.sub} Built into the GuestMaker platform, connected to your inbox, your CRM, and your hotel data.`,
    stats: [
      { value: "8", label: "channels supported" },
      { value: "60+", label: "languages" },
      { value: "30 min", label: "live demo" },
    ],
    heroVisual: <GenericVisual mod={mod} />,
    featuresTitle: `What ${mod.label} <span class="italic-accent">does for you</span>.`,
    features: [
      { title: "Hotel-grade reasoning", body: "Trained on your property's policies, room types, and tone. Grounded in real data, never invents.", bullets: ["Property-aware", "Multi-language", "Brand-consistent"] },
      { title: "Plugs into your stack", body: "Connects to your PMS, channel manager, booking engine, and CRM out of the box. New systems via API in days.", bullets: ["No rip-and-replace", "Bi-directional sync", "Open API"] },
      { title: "Built for groups", body: "One platform, every property. Per-property settings, group-wide reporting, role-based access for HQ and on-property teams.", bullets: ["Multi-property", "Role-based access", "Group analytics"] },
    ],
    howTitle: `From <em>setup</em> to <span class="italic-accent">live</span>, in 3 steps.`,
    steps: [
      { title: "Connect your data", body: "We pull from your PMS, your booking engine, your CRM, your knowledge base. Most properties go live in under a week." },
      { title: "Tune for your brand", body: "Tone of voice, policies, escalation rules, set per property, per channel, per question type." },
      { title: "Go live with safety nets", body: "Start in review mode. Promote to auto when you're ready. Every reply is logged, scored, and improvable." },
    ],
    useCasesTitle: `Where ${mod.label} pays off.`,
    useCases: [
      { tag: "Pre-stay", title: "More direct bookings", body: "Cut OTA dependency by capturing the conversation before the booking. Move guests to direct on the second stay.", outcome: "+22% direct" },
      { tag: "In-stay", title: "Fewer front-desk interruptions", body: "Routine requests handled instantly so your team focuses on the high-value moments that build loyalty.", outcome: "−40% repetitive tasks" },
      { tag: "Post-stay", title: "Better reactivation", body: "Right-time, right-channel follow-ups based on what guests actually did, said, and spent during their stay.", outcome: "+1.4× repeat bookings" },
    ],
    integrations: [
      { label: "PMS", items: ["Mews", "Cloudbeds", "Apaleo", "Opera", "Protel", "Hotelogix"] },
      { label: "Channels", items: ["WhatsApp", "Instagram", "OTAs", "Email", "Voice", "Web"] },
      { label: "CRM & Marketing", items: ["Salesforce", "HubSpot", "Mailchimp", "Klaviyo"] },
    ],
    testimonial: {
      quote: `"<strong>${mod.label}</strong> finally gave us a single source of truth for guest experience, across 9 properties.",`.replace(/,$/, ""),
      name: "Carlos Reyes",
      role: "VP Operations · Marina Group",
    },
    faq: [
      { q: `How long does ${mod.label} take to go live?`, a: "Most properties are live in 2–4 weeks. Multi-property groups roll out one property at a time, typically completing in 6–10 weeks." },
      { q: "Do I need to replace my existing systems?", a: "No. GuestMaker sits on top of your PMS, booking engine, and channel manager. We integrate, we don't replace." },
      { q: "What languages are supported?", a: "60+ languages out of the box, including all major European, Latin American, Asian, and Arabic dialects. Replies match the language and register of the guest." },
      { q: "Is my data safe?", a: "GDPR-compliant by design, hosted in EU, SOC 2 Type II in progress, ISO 27001 underway. Your data is never used to train shared models." },
      { q: "Who's behind GuestMaker?", a: "Built by Hotelinking, 10+ years building hospitality tech for groups across EMEA and LATAM. We're hoteliers as much as we're engineers." },
    ],
  };
}

function GenericVisual({ mod }) {
  const Ic = (window.moduleIconMap || {})[mod.icon];
  return (
    <div className="gv">
      <div className="gv-hero" style={{ background: `radial-gradient(circle at 30% 30%, color-mix(in srgb, ${mod.hue} 22%, transparent), transparent 70%)` }}>
        <div className="gv-icon" style={{ color: mod.hue, background: `color-mix(in srgb, ${mod.hue} 14%, var(--bg-elev-2))`, borderColor: `color-mix(in srgb, ${mod.hue} 30%, transparent)` }}>
          {Ic && <Ic size={36} color={mod.hue} />}
        </div>
        <div className="gv-label">{mod.label}</div>
        <div className="gv-sub">{mod.sub}</div>
      </div>
      <div className="gv-grid">
        {Array.from({ length: 6 }).map((_, i) => (
          <div key={i} className="gv-cell">
            <div className="gv-cell-bar" style={{ width: `${30 + (i * 11) % 60}%`, background: `linear-gradient(90deg, ${mod.hue}, color-mix(in srgb, ${mod.hue} 30%, transparent))` }} />
            <div className="gv-cell-line" style={{ width: `${50 + (i * 17) % 30}%` }} />
          </div>
        ))}
      </div>
      <style>{`
        .gv { display: flex; flex-direction: column; gap: 12px; min-height: 420px; }
        .gv-hero {
          padding: 40px 28px;
          border-radius: 14px;
          background-color: var(--bg-elev-2);
          border: 1px solid var(--hairline);
          display: flex; flex-direction: column; align-items: flex-start; gap: 14px;
        }
        .gv-icon {
          width: 72px; height: 72px;
          display: grid; place-items: center;
          border-radius: 18px;
          border: 1px solid;
        }
        .gv-label {
          font-family: var(--f-display); font-size: 28px; line-height: 1.1;
          color: var(--ink); font-weight: 400;
          letter-spacing: -0.01em;
        }
        .gv-sub {
          font-size: 14px; color: var(--ink-2);
          line-height: 1.5;
          max-width: 320px;
        }
        .gv-grid {
          display: grid;
          grid-template-columns: 1fr 1fr;
          gap: 8px;
        }
        .gv-cell {
          padding: 14px 16px;
          background: var(--bg-elev-2);
          border: 1px solid var(--hairline);
          border-radius: 10px;
          display: flex; flex-direction: column; gap: 8px;
        }
        .gv-cell-bar { height: 6px; border-radius: 3px; }
        .gv-cell-line { height: 4px; border-radius: 2px; background: var(--hairline-strong); opacity: 0.5; }
      `}</style>
    </div>
  );
}

window.moduleContent = moduleContent;
window.makeStubContent = makeStubContent;
