// Guestmaker · Hotel CRM hero — Loyalty-Engine-class composition
// 6 source cards orbiting one dramatic dark HUB in the middle, with continuous
// animateMotion packets riding curved rails through wide diagonal negative space.
const { useState, useEffect, useRef, useMemo } = React;

// ────────────────────────── tokens ──────────────────────────
const FUCHSIA      = "#ED4D86";
const FUCHSIA_DEEP = "#C93A6B";
const FUCHSIA_DARK = "#8E2148";
const FUCHSIA_SOFT = "#F8B7CF";
const FUCHSIA_PALE = "#FCE4EE";
const PLUM         = "#211A22";

const INK    = "#1a1a1a";
const DIM    = "#6e6363";
const FAINT  = "#a99fa3";
const RULE   = "#e3ddcf";
const PAPER  = "#fbfaf6";
const CREAM  = "#f6f4ef";
const SURFACE = "#ffffff";

const GREEN  = "#3b8d5a";
const AMBER  = "#b06a2a";

const MONO   = "'JetBrains Mono', ui-monospace, monospace";
const SERIF  = "var(--f-display)";

// ────────────────────────── icons ──────────────────────────
function Icon({ name, size = 14, color, strokeWidth = 1.8, style }) {
  const stroke = color || "currentColor";
  const props = {
    width: size, height: size, viewBox: "0 0 24 24", fill: "none",
    stroke, strokeWidth, strokeLinecap: "round", strokeLinejoin: "round",
    style: { display: "block", flexShrink: 0, ...style },
  };
  switch (name) {
    case "chat":          return <svg {...props}><path d="M21 12a8.5 8.5 0 0 1-12.6 7.4L3 21l1.6-5.4A8.5 8.5 0 1 1 21 12z"/></svg>;
    case "phone":         return <svg {...props}><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6 19.8 19.8 0 0 1-3.1-8.7A2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .3 1.9.6 2.8a2 2 0 0 1-.5 2.1L8 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.5 2.8.6a2 2 0 0 1 1.8 2z"/></svg>;
    case "link":          return <svg {...props}><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7"/><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/></svg>;
    case "share":         return <svg {...props}><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.6" y1="13.5" x2="15.4" y2="17.5"/><line x1="15.4" y1="6.5" x2="8.6" y2="10.5"/></svg>;
    case "keycard":       return <svg {...props}><rect x="2" y="5" width="20" height="14" rx="2"/><circle cx="8" cy="12" r="2.5"/><path d="M14 10h5M14 14h3"/></svg>;
    case "receipt":       return <svg {...props}><path d="M5 2v20l3-2 3 2 3-2 3 2 3-2V2H5z"/><path d="M9 8h6M9 12h6M9 16h4"/></svg>;
    case "search":        return <svg {...props}><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>;
    case "calendar-check":return <svg {...props}><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M16 3v4M8 3v4M3 11h18M9 16l2 2 4-4"/></svg>;
    case "bed":           return <svg {...props}><path d="M2 20v-7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v7"/><path d="M2 17h20M6 11V7a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v4"/><circle cx="8" cy="9" r="1.2"/></svg>;
    case "gift":          return <svg {...props}><rect x="3" y="8" width="18" height="4"/><path d="M5 12v10h14V12"/><path d="M12 22V8M12 8H7.5a2.5 2.5 0 0 1 0-5C11 3 12 8 12 8zM12 8h4.5a2.5 2.5 0 0 0 0-5C13 3 12 8 12 8z"/></svg>;
    case "refresh":       return <svg {...props}><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M20.5 15a9 9 0 0 1-14.9 3.4L1 14"/></svg>;
    case "mail":          return <svg {...props}><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>;
    case "whatsapp":      return <svg {...props}><path d="M3 21l1.7-5A8.5 8.5 0 1 1 8 19.3L3 21z"/><path d="M9 9.5c0 3 2.5 5.5 5.5 5.5l1.5-1.5-2-1-1 1c-1 0-2-1-2-2l1-1-1-2-1.5 1.5z"/></svg>;
    case "users":         return <svg {...props}><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8"/></svg>;
    case "merge":         return <svg {...props}><path d="M6 21V8a5 5 0 0 1 5-5h2a5 5 0 0 1 5 5v13"/><path d="M2 12l4-4 4 4M16 12l4-4 4 4"/></svg>;
    case "spark":         return <svg {...props}><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1"/><circle cx="12" cy="12" r="3"/></svg>;
    case "brain":         return <svg {...props}><path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2z"/></svg>;
    case "play":          return <svg {...props} fill={stroke}><polygon points="6,4 20,12 6,20"/></svg>;
    case "pause":         return <svg {...props} fill={stroke}><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>;
    case "check":         return <svg {...props}><polyline points="4,12 10,18 20,6"/></svg>;
    case "alert":         return <svg {...props}><path d="M12 2 2 22h20L12 2zM12 9v5M12 18v.5"/></svg>;
    case "arrow-down":    return <svg {...props}><path d="M12 5v14M5 12l7 7 7-7"/></svg>;
    default: return null;
  }
}

// ────────────────────────── sources (6, positioned for the perimeter grid) ──────────────────────────
const SOURCES = [
  { id: "webchat", pos: "TL", title: "Webchat & WhatsApp",     sub: "Guestmaker prospects · conversations",      icon: "chat",    eyebrow: "01 · CHAT" },
  { id: "voice",   pos: "TR", title: "Voice Agent",            sub: "Calls · transcripts · prospect leads",       icon: "phone",   eyebrow: "02 · VOICE" },
  { id: "booking", pos: "ML", title: "Booking Engine",         sub: "Direct · phone + email · principal contact",   icon: "link",    eyebrow: "03 · DIRECT" },
  { id: "pms_cm",  pos: "MR", title: "Channel Manager",         sub: "OTA · phone + temp email · TTOO · no contact", icon: "share",   eyebrow: "04 · OTA + TTOO" },
  { id: "pms_in",  pos: "BL", title: "PMS · In-stay sync",     sub: "Companions · captive portal · reception",    icon: "keycard", eyebrow: "05 · IN-STAY" },
  { id: "pms_out", pos: "BR", title: "PMS · Post-stay sync",   sub: "Folio extras · post-checkout enrichment",    icon: "receipt", eyebrow: "06 · POST" },
];
const SRC_BY_ID = Object.fromEntries(SOURCES.map(s => [s.id, s]));

// rolling tickers — flavor for each source card
const SRC_TICKERS = {
  webchat: [
    { txt: "Chat · vegan menu",      val: "PROSPECT", kind: "new"   },
    { txt: "WhatsApp · ETA",         val: "EXISTING", kind: "merge" },
    { txt: "Chat · wedding inquiry", val: "PROSPECT", kind: "new"   },
    { txt: "WhatsApp · upgrade",     val: "EXISTING", kind: "merge" },
  ],
  voice: [
    { txt: "Inbound · rebooking",    val: "REC",      kind: "merge" },
    { txt: "Outbound · win-back",    val: "REC",      kind: "merge" },
    { txt: "Inbound · concierge",    val: "PROSPECT", kind: "new"   },
    { txt: "Outbound · group ask",   val: "REC",      kind: "merge" },
  ],
  booking: [
    { txt: "BK-92103 · 4 nts",       val: "+BOOK",    kind: "new"   },
    { txt: "BK-92107 · 2 nts",       val: "+BOOK",    kind: "new"   },
    { txt: "BK-92115 · 7 nts",       val: "+BOOK",    kind: "merge" },
    { txt: "BK-92120 · couple",      val: "+BOOK",    kind: "new"   },
  ],
  pms_cm: [
    { txt: "Booking.com · phone",    val: "OTA",      kind: "new"   },
    { txt: "Expedia · phone",         val: "OTA",      kind: "merge" },
    { txt: "TUI · 14 rooms",           val: "TTOO",     kind: "new"   },
    { txt: "Agoda · phone",            val: "OTA",      kind: "new"   },
    { txt: "Hotelbeds · rooming list", val: "TTOO",     kind: "new"   },
    { txt: "Hotels.com · phone",       val: "OTA",      kind: "merge" },
    { txt: "FTI · group block",        val: "TTOO",     kind: "new"   },
  ],
  pms_in: [
    { txt: "Captive · +1 wife",      val: "+COMP",    kind: "merge" },
    { txt: "Reception · +2 kids",    val: "+COMP",    kind: "merge" },
    { txt: "Captive · email",        val: "+EMAIL",   kind: "merge" },
    { txt: "Reception · passport",   val: "+ID",      kind: "merge" },
  ],
  pms_out: [
    { txt: "Spa · ritual",           val: "+€340",    kind: "merge" },
    { txt: "F&B · tasting",          val: "+€180",    kind: "merge" },
    { txt: "Minibar · day 3",        val: "+€38",     kind: "merge" },
    { txt: "Excursion · catamaran",  val: "+€220",    kind: "merge" },
  ],
};
const KIND_COLOR = { new: GREEN, merge: FUCHSIA };

// ────────────────────────── guest records (records strip below) ──────────────────────────
const GUESTS = [
  { id: "G1", cid: "C-04821", name: "Marek Klein",     locale: "Berlin · DE",     init: { email: false, phone: false, wa: true,  bookings: 0,  touch: 1 }, journey: "PROSPECT" },
  { id: "G2", cid: "C-04822", name: "Andrei Petrov",   locale: "Sofia · BG",      init: { email: true,  phone: true,  wa: false, bookings: 1,  touch: 2 }, journey: "BOOKED" },
  { id: "G3", cid: "C-04823", name: "Sofia Marchetti", locale: "Verona · IT",     init: { email: "ota", phone: true,  wa: false, bookings: 1,  touch: 1 }, journey: "BOOKED" },
  { id: "G4", cid: "C-04824", name: "TUI · package",   locale: "TTOO · 14 rooms", init: { email: false, phone: false, wa: false, bookings: 14, touch: 0 }, journey: "BOOKED", isPackage: true },
  { id: "G5", cid: "C-04825", name: "Lien Nakamura",   locale: "Kyoto · JP",      init: { email: "ota", phone: true,  wa: false, bookings: 1,  touch: 1 }, journey: "BOOKED" },
];
const GUEST_BY_ID = Object.fromEntries(GUESTS.map(g => [g.id, g]));

// journey phases (visual progression in fuchsia tones)
const PHASES = ["PROSPECT", "BOOKED", "IN-HOUSE", "POST-STAY", "RETURNING"];
const PHASE_COLOR = {
  PROSPECT:    FUCHSIA_SOFT,
  BOOKED:      FUCHSIA,
  "IN-HOUSE":  FUCHSIA_DEEP,
  "POST-STAY": FUCHSIA_DARK,
  RETURNING:   PLUM,
};
const PHASE_ICON = {
  PROSPECT:    "search",
  BOOKED:      "calendar-check",
  "IN-HOUSE":  "bed",
  "POST-STAY": "gift",
  RETURNING:   "refresh",
};

// ────────────────────────── master packet timeline ──────────────────────────
const PACKETS = [
  { t: 0.6,  src: "webchat", target: "G1", kind: "new",   phase: "PROSPECT",  label: "Anonymous chat · vegan ask",   detail: "session · pre-booking",     delta: { wa: true, touch: 1 } },
  { t: 1.8,  src: "booking", target: "G2", kind: "new",   phase: "BOOKED",    label: "Direct booking · 4 nts",       detail: "BK-92103 · email + mobile", delta: { email: true, phone: true, bookings: 1 } },
  { t: 3.0,  src: "pms_cm",  target: "G3", kind: "new",   phase: "BOOKED",    label: "Booking.com · OTA · 7 nts",    detail: "temp email · valid mobile", delta: { email: "ota", phone: true, bookings: 1 } },
  { t: 4.2,  src: "voice",   target: "G1", kind: "merge", matchOn: "phone",   phase: "PROSPECT",  label: "Inbound call · 02:14",         detail: "phone match · transcript",  delta: { phone: true, touch: 1 } },
  { t: 5.4,  src: "pms_cm",  target: "G4", kind: "new",   phase: "BOOKED",    label: "TUI rooming list · 14 rooms",  detail: "TTOO · no contact",         delta: {} },
  { t: 6.8,  src: "booking", target: "G1", kind: "merge", matchOn: "phone",   phase: "BOOKED",    label: "Direct booking · 3 nts",       detail: "PROSPECT → BOOKED",         delta: { email: true, bookings: 1 } },
  { t: 8.2,  src: "pms_in",  target: "G2", kind: "merge", matchOn: "stay",    phase: "IN-HOUSE",  label: "Captive portal · +2 adults",   detail: "companion data attached",   delta: { touch: 1 } },
  { t: 9.4,  src: "pms_in",  target: "G3", kind: "merge", matchOn: "stay",    phase: "IN-HOUSE",  label: "Reception · +1 child",         detail: "passport · DOB · loyalty",  delta: { touch: 1 } },
  { t: 10.6, src: "pms_in",  target: "G4", kind: "merge", matchOn: "stay",    phase: "IN-HOUSE",  label: "Captive portal · 12 emails",   detail: "TTOO unlocked",             delta: { email: true, phone: true, touch: 4 } },
  { t: 12.0, src: "pms_out", target: "G2", kind: "merge", matchOn: "stay",    phase: "POST-STAY", label: "Spa · Aphrodite ritual",       detail: "+ €340 folio extra",        delta: { touch: 1 } },
  { t: 13.2, src: "pms_out", target: "G2", kind: "merge", matchOn: "stay",    phase: "POST-STAY", label: "F&B · Verde tasting",          detail: "+ €180 folio extra",        delta: { touch: 1 } },
  { t: 14.4, src: "pms_out", target: "G3", kind: "merge", matchOn: "stay",    phase: "POST-STAY", label: "Excursion · catamaran",        detail: "+ €220 folio extra",        delta: { touch: 1 } },
  { t: 15.8, src: "webchat", target: "G2", kind: "merge", matchOn: "email",   phase: "RETURNING", label: "WhatsApp · rebooking",         detail: "wa-id linked · email match",delta: { wa: true, touch: 1 } },
  { t: 17.0, src: "booking", target: "G2", kind: "merge", matchOn: "email",   phase: "RETURNING", label: "Direct booking · 5 nts",       detail: "BK-92118 · returning",      delta: { bookings: 1 } },
  { t: 18.2, src: "pms_cm",  target: "G5", kind: "new",   phase: "BOOKED",    label: "Expedia · OTA · 3 nts",        detail: "BK-92120 · temp · mobile",  delta: { email: "ota", phone: true, bookings: 1 } },
  { t: 19.4, src: "pms_in",  target: "G5", kind: "merge", matchOn: "stay",    phase: "IN-HOUSE",  label: "Captive · email enriched",     detail: "personal email replaces OTA", delta: { email: true, touch: 1 } },
  { t: 20.6, src: "voice",   target: "G3", kind: "merge", matchOn: "phone",   phase: "RETURNING", label: "Outbound · extension",         detail: "phone match · recorded",    delta: { touch: 1 } },
  { t: 21.8, src: "pms_out", target: "G1", kind: "merge", matchOn: "stay",    phase: "POST-STAY", label: "Minibar · €38",                detail: "folio reconciled",          delta: { touch: 1 } },
];
const TIMELINE_END = 23.4;

// ────────────────────────── connector geometry ──────────────────────────
// Diagram inner area: 1000 × 490
//   Col widths: 220 (sources L)  · gap 80 · 400 (hub) · gap 80 · 220 (sources R)
//   Row heights: 130 · gap 50 · 130 · gap 50 · 130
//   Hub spans all 3 rows in the middle column.
// Source center y values: 65, 245, 425
// Hub left edge:  x=300    Hub right edge: x=700
// Anchors: source-card right (for L col) at x=220; source-card left (for R col) at x=780
const VB_W = 1000;
const VB_H = 490;
const CONNECTOR_PATHS = {
  // each path starts at the source card's inner edge, sweeps with a cubic
  // bezier, and terminates at the hub edge
  webchat: "M 220 65  C 250 75,  280 130, 300 180",  // TL → hub upper-left
  voice:   "M 780 65  C 750 75,  720 130, 700 180",  // TR → hub upper-right
  booking: "M 220 245 C 250 245, 280 245, 300 245",  // ML → hub mid-left
  pms_cm:  "M 780 245 C 750 245, 720 245, 700 245",  // MR → hub mid-right
  pms_in:  "M 220 425 C 250 415, 280 360, 300 310",  // BL → hub lower-left
  pms_out: "M 780 425 C 750 415, 720 360, 700 310",  // BR → hub lower-right
};
const HUB_CENTER = { x: 500, y: 245 };

// ────────────────────────── playback hook ──────────────────────────
function usePlayback(speed = 1) {
  const [time, setTime] = useState(0);
  const [playing, setPlaying] = useState(true);
  const lastRef = useRef(performance.now());
  const speedRef = useRef(speed);
  useEffect(() => { speedRef.current = speed; }, [speed]);

  useEffect(() => {
    let raf;
    const loop = (now) => {
      const dt = (now - lastRef.current) / 1000;
      lastRef.current = now;
      if (playing) {
        setTime(t => {
          const nt = t + dt * speedRef.current;
          return nt >= TIMELINE_END ? 0 : nt;
        });
      }
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(n => { lastRef.current = n; loop(n); });
    return () => cancelAnimationFrame(raf);
  }, [playing]);

  return { time, setTime, playing, setPlaying };
}

// ────────────────────────── atoms ──────────────────────────
function Eyebrow({ children, color = FUCHSIA, style }) {
  return <div style={{
    fontFamily: MONO, fontSize: 10.5, color, fontWeight: 600,
    letterSpacing: "0.18em", textTransform: "uppercase", ...style,
  }}>{children}</div>;
}
function Mono({ children, size = 10.5, color = DIM, style }) {
  return <span style={{
    fontFamily: MONO, fontSize: size, color, letterSpacing: "0.04em", ...style,
  }}>{children}</span>;
}
function Dot({ color, size = 6, pulse = false, style }) {
  return <span style={{
    display: "inline-block", width: size, height: size, borderRadius: "50%",
    background: color, animation: pulse ? "gm-dot-pulse 1.4s ease-in-out infinite" : "none",
    flexShrink: 0, ...style,
  }} />;
}
function fmt(t) {
  const m = Math.floor(t / 60); const s = Math.floor(t % 60);
  return `${m}:${String(s).padStart(2, "0")}`;
}
function fmtNum(n) { return n.toLocaleString("en-US"); }

// ────────────────────────── source card ──────────────────────────
function SourceTicker({ events, isFiring }) {
  const [idx, setIdx] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setIdx(i => (i + 1) % events.length), 2200);
    return () => clearInterval(id);
  }, [events.length]);
  const visible = [0, 1, 2].map(i => events[(idx - i + events.length * 5) % events.length]);
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4, fontFamily: MONO, fontSize: 10 }}>
      {visible.slice().reverse().map((e, i) => (
        <div key={`${idx}-${i}`} style={{
          display: "flex", justifyContent: "space-between", alignItems: "center",
          padding: "4px 8px", borderRadius: 4,
          background: i === 2 ? (isFiring ? FUCHSIA + "14" : "#fff") : "transparent",
          border: i === 2 ? `1px solid ${isFiring ? FUCHSIA + "55" : RULE}` : "1px solid transparent",
          opacity: i === 0 ? 0.32 : i === 1 ? 0.62 : 1,
          animation: i === 2 ? "gm-row-in 0.5s ease" : "none",
          transition: "background 0.3s ease, border 0.3s ease",
        }}>
          <span style={{ color: INK, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", maxWidth: 130 }}>{e.txt}</span>
          <span style={{ color: KIND_COLOR[e.kind], fontWeight: 700, letterSpacing: "0.06em", flexShrink: 0 }}>{e.val}</span>
        </div>
      ))}
    </div>
  );
}

function SourceCard({ s, isFiring }) {
  return (
    <div style={{
      position: "relative",
      background: PAPER,
      border: `1px solid ${isFiring ? FUCHSIA + "AA" : RULE}`,
      borderRadius: 10, padding: "12px 13px",
      width: 220, height: 130,
      boxShadow: isFiring
        ? `0 14px 28px -16px ${FUCHSIA}AA, 0 0 0 3px ${FUCHSIA}1F`
        : "0 1px 0 rgba(0,0,0,0.02), 0 6px 16px -10px rgba(0,0,0,0.08)",
      transition: "all 0.4s ease",
      display: "flex", flexDirection: "column",
    }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 6 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
          <div style={{
            width: 24, height: 24, borderRadius: 6,
            background: isFiring ? FUCHSIA : FUCHSIA + "14",
            color: isFiring ? "#fff" : FUCHSIA,
            border: `1px solid ${isFiring ? FUCHSIA : FUCHSIA + "33"}`,
            display: "flex", alignItems: "center", justifyContent: "center",
            transition: "all 0.3s ease",
          }}>
            <Icon name={s.icon} size={13} strokeWidth={2} />
          </div>
          <Mono size={9} color={DIM} style={{ fontWeight: 600, letterSpacing: "0.16em" }}>{s.eyebrow}</Mono>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
          <Dot color={FUCHSIA} size={5} pulse />
          <Mono size={8.5} color={FUCHSIA} style={{ letterSpacing: "0.16em", fontWeight: 700 }}>LIVE</Mono>
        </div>
      </div>
      <div style={{ fontSize: 13.5, fontWeight: 600, color: INK, letterSpacing: "-0.005em", marginBottom: 1 }}>
        {s.title}
      </div>
      <div style={{ fontSize: 10.5, color: DIM, marginBottom: 6, lineHeight: 1.3, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
        {s.sub}
      </div>
      <div style={{ flex: 1 }} />
      <SourceTicker events={SRC_TICKERS[s.id]} isFiring={isFiring} />
    </div>
  );
}

// ────────────────────────── HUB (the centerpiece) ──────────────────────────
function useLiveBars() {
  const [vals, setVals] = useState([88, 94, 72, 81]);
  useEffect(() => {
    const id = setInterval(() => {
      setVals(prev => prev.map(v => {
        const delta = (Math.random() - 0.5) * 18;
        return Math.round(Math.max(48, Math.min(99, v + delta)));
      }));
    }, 720);
    return () => clearInterval(id);
  }, []);
  return vals;
}

function HubBars() {
  const vals = useLiveBars();
  const rows = [
    { lbl: "IDENTITY RESOLVER", scan: 2.4 },
    { lbl: "DEDUP ENGINE",      scan: 1.9 },
    { lbl: "JOURNEY STATE",     scan: 3.0 },
    { lbl: "WEBHOOKS OUT",      scan: 2.2 },
  ];
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
      {rows.map((r, i) => (
        <div key={i} style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <Mono size={9} color="#9b8f95" style={{ letterSpacing: "0.12em", width: 116, fontWeight: 600 }}>
            {r.lbl}
          </Mono>
          <div style={{
            flex: 1, height: 4, background: "#2a2a2a", borderRadius: 2,
            overflow: "hidden", position: "relative",
          }}>
            {/* fill bar · width animates smoothly */}
            <div style={{
              position: "absolute", top: 0, left: 0, bottom: 0,
              width: `${vals[i]}%`,
              background: `linear-gradient(90deg, ${FUCHSIA_SOFT}, ${FUCHSIA})`,
              transition: "width 0.7s cubic-bezier(0.4, 0, 0.2, 1)",
            }} />
            {/* moving sheen sweep */}
            <div style={{
              position: "absolute", top: 0, bottom: 0,
              width: "40%", pointerEvents: "none",
              background: `linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.45) 50%, transparent 100%)`,
              animation: `gm-bar-scan ${r.scan}s linear infinite`,
              animationDelay: `${i * 0.3}s`,
              mixBlendMode: "overlay",
            }} />
          </div>
          <Mono size={9} color={FUCHSIA_SOFT} style={{
            width: 26, textAlign: "right", fontWeight: 700,
            fontVariantNumeric: "tabular-nums",
            transition: "color 0.4s ease",
          }}>
            {vals[i]}
          </Mono>
        </div>
      ))}
    </div>
  );
}

function HubDecision({ time }) {
  // Show the most recent packet's decision in a cinematic plate.
  const recent = [...PACKETS].reverse().find(p => time >= p.t);
  if (!recent) {
    return (
      <div style={{
        background: "#161616", border: "1px solid #2a2a2a", borderRadius: 8,
        padding: "11px 13px", height: 76,
        display: "flex", alignItems: "center", justifyContent: "center",
        fontFamily: SERIF, fontSize: 13, fontStyle: "italic", color: "#7a747a",
      }}>
        waiting on signal…
      </div>
    );
  }
  const since = time - recent.t;
  const isHot = since < 1.4;
  const guest = GUEST_BY_ID[recent.target];
  const src = SRC_BY_ID[recent.src];
  const isNew = recent.kind === "new";
  const accent = isNew ? GREEN : FUCHSIA;
  return (
    <div key={recent.t} style={{
      background: "#161616",
      border: `1px solid ${isHot ? accent + "AA" : "#2a2a2a"}`,
      borderRadius: 8, padding: "11px 13px", height: 76,
      boxShadow: isHot ? `0 0 0 3px ${accent}1F, 0 12px 24px -12px ${accent}55` : "none",
      transition: "all 0.4s ease",
      animation: isHot ? "gm-fadein 0.4s ease" : "none",
      position: "relative", overflow: "hidden",
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 5 }}>
        <Icon name={isNew ? "spark" : "merge"} size={12} color={accent} strokeWidth={2.2} />
        <Mono size={9.5} color={accent} style={{ letterSpacing: "0.16em", fontWeight: 700 }}>
          {isNew ? "NEW CONTACT" : `MERGED · MATCHED ON ${recent.matchOn?.toUpperCase()}`}
        </Mono>
        <span style={{ flex: 1 }} />
        <Mono size={9} color="#7a747a" style={{ letterSpacing: "0.08em" }}>{guest.cid}</Mono>
      </div>
      <div style={{
        fontFamily: SERIF, fontSize: 14.5, color: "#f6f4ef", fontWeight: 500,
        letterSpacing: "-0.005em", marginBottom: 4,
        whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
      }}>
        {guest.name} <span style={{ color: "#8a808a", fontStyle: "italic" }}>· {guest.locale}</span>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
        <Icon name={src.icon} size={10} color={FUCHSIA_SOFT} strokeWidth={2} />
        <Mono size={10} color="#cdbcc4" style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", flex: 1 }}>
          {recent.label}
        </Mono>
      </div>
    </div>
  );
}

function HubCard({ time }) {
  const ingested = PACKETS.filter(p => time >= p.t).length;
  const merges = PACKETS.filter(p => time >= p.t && p.kind === "merge").length;
  const news = PACKETS.filter(p => time >= p.t && p.kind === "new").length;
  const dedupPct = ingested === 0 ? 0 : Math.round((merges / ingested) * 100);

  return (
    <div style={{
      width: 400, height: 490,
      background: "#1a1a1a",
      borderRadius: 14,
      padding: "20px 22px",
      color: "#f6f4ef",
      position: "relative",
      boxShadow: `0 40px 80px -36px ${FUCHSIA}BB, 0 0 0 1px ${FUCHSIA}25`,
      display: "flex", flexDirection: "column",
    }}>
      {/* glow ring */}
      <div style={{
        position: "absolute", inset: -3, borderRadius: 17,
        background: `linear-gradient(135deg, ${FUCHSIA}, transparent 45%, ${FUCHSIA_DEEP})`,
        opacity: 0.25, zIndex: -1, pointerEvents: "none",
        filter: "blur(14px)",
      }} />

      {/* top row */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
          <div style={{
            width: 28, height: 28, borderRadius: 7,
            background: FUCHSIA + "22",
            border: `1px solid ${FUCHSIA}55`,
            display: "flex", alignItems: "center", justifyContent: "center",
          }}>
            <Icon name="brain" size={16} color={FUCHSIA_SOFT} strokeWidth={1.9} />
          </div>
          <Eyebrow color={FUCHSIA_SOFT}>HOTEL CRM · CORE</Eyebrow>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 5 }}>
          <Dot color={FUCHSIA} size={6} pulse />
          <Mono size={9} color={FUCHSIA_SOFT} style={{ letterSpacing: "0.18em", fontWeight: 700 }}>PROCESSING</Mono>
        </div>
      </div>

      {/* hero title */}
      <div style={{
        fontFamily: SERIF, fontSize: 30, fontWeight: 500,
        letterSpacing: "-0.025em", lineHeight: 1.04,
        marginBottom: 4,
      }}>
        Unified
      </div>
      <div style={{
        fontFamily: SERIF, fontSize: 30, fontWeight: 500, fontStyle: "italic",
        letterSpacing: "-0.025em", lineHeight: 1.04,
        color: FUCHSIA_SOFT,
        marginBottom: 14,
      }}>
        guest record
      </div>

      <div style={{ fontSize: 12, color: "#a89aa0", lineHeight: 1.5, marginBottom: 18 }}>
        Every signal from every channel resolves to a single profile.
        Identity, history, journey state.
      </div>

      {/* stats grid */}
      <div style={{
        display: "grid", gridTemplateColumns: "1fr 1fr 1fr",
        gap: 1, background: "#2a2a2a",
        borderRadius: 6, overflow: "hidden",
        marginBottom: 14,
      }}>
        {[
          { lbl: "CONTACTS",      val: fmtNum(4287 + news), accent: false },
          { lbl: "DEDUP RATE",    val: `${dedupPct}%`,      accent: true },
          { lbl: "EVENTS · TODAY",val: fmtNum(18420 + ingested), accent: false },
        ].map((s, i) => (
          <div key={i} style={{ background: "#161616", padding: "9px 11px" }}>
            <Mono size={8.5} color="#7a747a" style={{ letterSpacing: "0.16em", fontWeight: 600 }}>{s.lbl}</Mono>
            <div style={{
              fontFamily: MONO, fontSize: 17, fontWeight: 700,
              color: s.accent ? FUCHSIA_SOFT : "#f6f4ef",
              fontVariantNumeric: "tabular-nums", letterSpacing: "-0.005em",
              marginTop: 2,
            }}>{s.val}</div>
          </div>
        ))}
      </div>

      {/* latest decision plate */}
      <HubDecision time={time} />

      <div style={{ flex: 1 }} />

      {/* activity bars */}
      <HubBars />
    </div>
  );
}

// ────────────────────────── connectors (continuous animateMotion) ──────────────────────────
function Connectors({ paused }) {
  const cycle = 3.0; // s
  return (
    <svg viewBox={`0 0 ${VB_W} ${VB_H}`}
      preserveAspectRatio="none"
      style={{
        position: "absolute", inset: 0, width: "100%", height: "100%",
        pointerEvents: "none", overflow: "visible",
      }}
    >
      <defs>
        <filter id="gm-glow" x="-100%" y="-100%" width="300%" height="300%">
          <feGaussianBlur stdDeviation="3" result="b"/>
          <feMerge>
            <feMergeNode in="b"/>
            <feMergeNode in="SourceGraphic"/>
          </feMerge>
        </filter>
        <radialGradient id="gm-packet-grad" cx="50%" cy="50%" r="50%">
          <stop offset="0%"  stopColor={FUCHSIA} stopOpacity="1"/>
          <stop offset="55%" stopColor={FUCHSIA} stopOpacity="0.45"/>
          <stop offset="100%" stopColor={FUCHSIA} stopOpacity="0"/>
        </radialGradient>
      </defs>

      {/* slow rotating outer ring around hub center */}
      <g style={{ transformOrigin: `${HUB_CENTER.x}px ${HUB_CENTER.y}px` }}>
        <circle cx={HUB_CENTER.x} cy={HUB_CENTER.y} r="195"
          fill="none" stroke={FUCHSIA} strokeOpacity="0.10"
          strokeWidth="1" strokeDasharray="2 7">
          {!paused && <animateTransform attributeName="transform" type="rotate"
            from={`0 ${HUB_CENTER.x} ${HUB_CENTER.y}`}
            to={`360 ${HUB_CENTER.x} ${HUB_CENTER.y}`}
            dur="60s" repeatCount="indefinite" />}
        </circle>
        <circle cx={HUB_CENTER.x} cy={HUB_CENTER.y} r="225"
          fill="none" stroke={FUCHSIA} strokeOpacity="0.06"
          strokeWidth="1" strokeDasharray="2 11">
          {!paused && <animateTransform attributeName="transform" type="rotate"
            from={`360 ${HUB_CENTER.x} ${HUB_CENTER.y}`}
            to={`0 ${HUB_CENTER.x} ${HUB_CENTER.y}`}
            dur="80s" repeatCount="indefinite" />}
        </circle>
      </g>

      {/* connectors + packets */}
      {SOURCES.map((s, i) => {
        const d = CONNECTOR_PATHS[s.id];
        const begin = `${(i * cycle / SOURCES.length).toFixed(2)}s`;
        const begin2 = `${(i * cycle / SOURCES.length + cycle / 2).toFixed(2)}s`;
        return (
          <g key={s.id}>
            {/* rail */}
            <path d={d} fill="none" stroke={FUCHSIA}
              strokeWidth="1.4" strokeOpacity="0.35"
              strokeDasharray="3 5" />
            {/* glow halo packet */}
            <circle r="14" fill="url(#gm-packet-grad)" opacity="0.85">
              {!paused && <animateMotion dur={`${cycle}s`} repeatCount="indefinite" begin={begin} path={d} />}
            </circle>
            {/* primary packet head */}
            <circle r="4.5" fill={FUCHSIA} filter="url(#gm-glow)">
              {!paused && <animateMotion dur={`${cycle}s`} repeatCount="indefinite" begin={begin} path={d} />}
            </circle>
            <circle r="1.8" fill="#fff">
              {!paused && <animateMotion dur={`${cycle}s`} repeatCount="indefinite" begin={begin} path={d} />}
            </circle>
            {/* second smaller packet, offset half-cycle */}
            <circle r="9" fill="url(#gm-packet-grad)" opacity="0.55">
              {!paused && <animateMotion dur={`${cycle}s`} repeatCount="indefinite" begin={begin2} path={d} />}
            </circle>
            <circle r="3" fill={FUCHSIA} opacity="0.8">
              {!paused && <animateMotion dur={`${cycle}s`} repeatCount="indefinite" begin={begin2} path={d} />}
            </circle>
          </g>
        );
      })}
    </svg>
  );
}

// ────────────────────────── diagram (the big card) ──────────────────────────
function Diagram({ time, playing, recentBySource }) {
  const grid = {
    display: "grid",
    gridTemplateColumns: "220px 400px 220px",
    gridTemplateRows: "130px 130px 130px",
    columnGap: 80, rowGap: 50,
    position: "relative",
    width: 1000,
    margin: "0 auto",
  };
  const positions = {
    TL: { gridColumn: 1, gridRow: 1 },
    TR: { gridColumn: 3, gridRow: 1 },
    ML: { gridColumn: 1, gridRow: 2 },
    MR: { gridColumn: 3, gridRow: 2 },
    BL: { gridColumn: 1, gridRow: 3 },
    BR: { gridColumn: 3, gridRow: 3 },
  };

  return (
    <div style={{
      maxWidth: "var(--rail)", margin: "0 auto",
      background: PAPER,
      border: `1px solid ${RULE}`,
      borderRadius: 14,
      padding: "36px 50px 40px",
      position: "relative",
      boxShadow: "0 30px 60px -40px rgba(20,14,10,0.18)",
    }}>
      {/* card header */}
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        marginBottom: 28,
      }}>
        <Eyebrow color={DIM}>CENTRAL ENGINE · LIVE DATA FLOW</Eyebrow>
        <Mono size={10} color={FAINT} style={{ letterSpacing: "0.14em" }}>
          REAL-TIME · BIDIRECTIONAL · DEDUPED
        </Mono>
      </div>

      {/* the grid */}
      <div style={grid}>
        {/* SVG overlay covering the grid */}
        <div style={{
          position: "absolute", inset: 0, pointerEvents: "none", zIndex: 1,
        }}>
          <Connectors paused={!playing} />
        </div>

        {/* sources */}
        {SOURCES.map(s => (
          <div key={s.id} style={{ ...positions[s.pos], zIndex: 2 }}>
            <SourceCard s={s} isFiring={!!recentBySource[s.id]} />
          </div>
        ))}

        {/* hub */}
        <div style={{ gridColumn: 2, gridRow: "1 / 4", zIndex: 3, alignSelf: "stretch" }}>
          <HubCard time={time} />
        </div>
      </div>
    </div>
  );
}

// ────────────────────────── records strip ──────────────────────────
function computeGuestState(g, time) {
  const st = { ...g.init, journey: g.journey, _lastTouchT: -10, _lastMerge: null, _lastNew: null, _bornT: null, _lastSrc: null };
  for (const p of PACKETS) {
    if (p.target !== g.id || time < p.t) continue;
    if (p.kind === "new") { st._bornT = p.t; st._lastNew = { at: p.t }; }
    else if (p.matchOn) { st._lastMerge = { on: p.matchOn, at: p.t }; }
    st.journey = p.phase;
    const d = p.delta || {};
    Object.entries(d).forEach(([k, v]) => {
      if (k === "bookings" || k === "touch") st[k] = (st[k] || 0) + v;
      else if (k === "email") {
        if (v === true) st.email = true;
        else if (v === "ota" && !st.email) st.email = "ota";
      } else if (k === "phone" || k === "wa") st[k] = v || st[k];
    });
    st._lastTouchT = p.t;
    st._lastSrc = p.src;
  }
  return st;
}

function IdChip({ kind, value }) {
  const filled = value === true || value === "ota";
  const isOta = value === "ota";
  const iconName = kind === "email" ? "mail" : kind === "phone" ? "phone" : "whatsapp";
  const c = filled ? (isOta ? AMBER : GREEN) : "#c0b6ab";
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 4,
      padding: "3px 6px",
      borderRadius: 4,
      background: filled ? (isOta ? AMBER + "12" : GREEN + "12") : "transparent",
      border: `1px solid ${filled ? (isOta ? AMBER + "55" : GREEN + "55") : "#d8d2c2"}`,
      fontFamily: MONO, fontSize: 9, fontWeight: 600,
      letterSpacing: "0.04em", color: c,
    }}>
      <Icon name={iconName} size={9} strokeWidth={2.2} />
      {filled ? (isOta ? "OTA" : "✓") : "×"}
    </span>
  );
}

function JourneyBadge({ phase }) {
  const c = PHASE_COLOR[phase];
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 4,
      padding: "3px 8px 3px 6px", borderRadius: 999,
      background: c + "18", border: `1px solid ${c}55`, color: c,
      fontFamily: MONO, fontSize: 8.5, fontWeight: 700,
      letterSpacing: "0.14em", whiteSpace: "nowrap",
    }}>
      <Icon name={PHASE_ICON[phase]} size={9} strokeWidth={2.2} />
      {phase}
    </span>
  );
}

function RecordCard({ g, time }) {
  const st = computeGuestState(g, time);
  const born = st._bornT != null;
  const recentMerge = st._lastMerge && (time - st._lastMerge.at) < 1.5 ? st._lastMerge : null;
  const recentNew   = st._lastNew   && (time - st._lastNew.at)   < 1.5 ? st._lastNew   : null;
  const recentBump  = (time - st._lastTouchT) < 1.0;

  if (!born) {
    return (
      <div style={{
        flex: 1, minWidth: 0,
        background: "rgba(255,255,255,0.55)",
        border: `1px dashed ${RULE}`,
        borderRadius: 10, padding: "12px 13px",
        height: 134,
        display: "flex", alignItems: "center", justifyContent: "center",
      }}>
        <Mono size={9} color={FAINT} style={{ letterSpacing: "0.14em" }}>AWAITING SIGNAL</Mono>
      </div>
    );
  }

  const initials = g.isPackage ? "T" : g.name.split(" ").map(s => s[0]).slice(0, 2).join("").toUpperCase();

  return (
    <div style={{
      flex: 1, minWidth: 0,
      position: "relative",
      background: SURFACE,
      border: `1px solid ${recentBump ? FUCHSIA + "55" : RULE}`,
      borderRadius: 10, padding: "11px 13px 12px",
      height: 134,
      boxShadow: recentBump
        ? `0 16px 32px -18px ${FUCHSIA}AA`
        : "0 1px 0 rgba(0,0,0,0.02), 0 6px 16px -12px rgba(0,0,0,0.1)",
      transition: "all 0.45s ease",
      animation: born && (time - st._bornT) < 0.7 ? "gm-fadein 0.5s ease" : "none",
      overflow: "hidden",
      display: "flex", flexDirection: "column",
    }}>
      {recentMerge && (
        <span style={{
          position: "absolute", left: -1, top: -1, right: -1, bottom: -1,
          borderRadius: 10, pointerEvents: "none",
          animation: "gm-merge-flash 1.4s ease",
        }} />
      )}

      <div style={{ display: "flex", alignItems: "flex-start", gap: 9, marginBottom: 6 }}>
        <div style={{
          width: 28, height: 28, borderRadius: 7, flexShrink: 0,
          background: g.isPackage
            ? `linear-gradient(135deg, ${PLUM}, ${FUCHSIA_DARK})`
            : `linear-gradient(135deg, ${FUCHSIA}, ${FUCHSIA_DEEP})`,
          display: "flex", alignItems: "center", justifyContent: "center", color: "#fff",
          boxShadow: "inset 0 -2px 4px rgba(0,0,0,0.18)",
        }}>
          {g.isPackage
            ? <Icon name="users" size={14} strokeWidth={2.1} color="#fff" />
            : <span style={{ fontFamily: SERIF, fontSize: 12, fontStyle: "italic", fontWeight: 600 }}>{initials}</span>}
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontFamily: SERIF, fontSize: 13.5, color: INK, fontWeight: 600,
            letterSpacing: "-0.005em",
            whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
          }}>{g.name}</div>
          <Mono size={9} color={DIM} style={{
            display: "block", marginTop: 1, whiteSpace: "nowrap",
            overflow: "hidden", textOverflow: "ellipsis",
          }}>{g.locale}</Mono>
        </div>
      </div>

      <div style={{ marginBottom: 6 }}>
        <JourneyBadge phase={st.journey} />
      </div>

      <div style={{ display: "flex", alignItems: "center", gap: 5, marginBottom: 7 }}>
        <IdChip kind="email" value={st.email} />
        <IdChip kind="phone" value={st.phone} />
        <IdChip kind="wa" value={st.wa} />
      </div>

      <div style={{ flex: 1 }} />

      <div style={{
        fontFamily: MONO, fontSize: 9, letterSpacing: "0.04em",
        minHeight: 14, display: "flex", alignItems: "center",
      }}>
        {recentMerge ? (
          <span style={{ display: "inline-flex", alignItems: "center", gap: 5, color: FUCHSIA, fontWeight: 700, letterSpacing: "0.1em" }}>
            <Icon name="merge" size={10} strokeWidth={2.2} />
            MERGED · {recentMerge.on.toUpperCase()}
          </span>
        ) : recentNew ? (
          <span style={{ display: "inline-flex", alignItems: "center", gap: 5, color: GREEN, fontWeight: 700, letterSpacing: "0.1em" }}>
            <Icon name="spark" size={10} strokeWidth={2.2} />
            NEW CONTACT
          </span>
        ) : (
          <span style={{ color: DIM }}>
            <span style={{ color: INK, fontWeight: 700 }}>{st.bookings || 0}</span> BK
            <span style={{ color: FAINT, margin: "0 6px" }}>·</span>
            <span style={{ color: INK, fontWeight: 700 }}>{st.touch || 0}</span> TP
          </span>
        )}
      </div>
    </div>
  );
}

function RecordsStrip({ time }) {
  return (
    <div style={{
      maxWidth: "var(--rail)", margin: "26px auto 0",
    }}>
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        padding: "0 8px", marginBottom: 12,
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9 }}>
          <Icon name="users" size={13} color={FUCHSIA} strokeWidth={2.2} />
          <Eyebrow color={FUCHSIA}>Live unified records</Eyebrow>
        </div>
        <Mono size={10} color={FAINT} style={{ letterSpacing: "0.14em" }}>
          5 OF 4,287 CONTACTS · STREAMING
        </Mono>
      </div>
      <div style={{ display: "flex", gap: 12 }}>
        {GUESTS.map(g => <RecordCard key={g.id} g={g} time={time} />)}
      </div>
    </div>
  );
}

// ────────────────────────── capabilities grid (engine-style) ──────────────────────────
const CAPS = [
  { idx: "01", title: "Auto-dedup, every channel",   body: "Phone, email, WhatsApp ID, OTA temp address. Identity stitched on the way in, never as a nightly batch." },
  { idx: "02", title: "OTA temp → real email",       body: "When the captive portal collects a personal email, it transparently replaces the booking.com / expedia temp address." },
  { idx: "03", title: "TTOO bookings unlocked",      body: "Tour-operator rooming lists arrive with zero contact data. Reception or the portal enriches each pax in-stay." },
  { idx: "04", title: "Companions captured",         body: "Every adult and child registered at check-in becomes its own contact under the booking, with consent and DOB." },
  { idx: "05", title: "Folio extras enrich",         body: "Spa, F&B, minibar, excursion. Every post-checkout extra hangs off the guest, ready for targeted campaigns." },
  { idx: "06", title: "Webhooks out · real-time",    body: "Every state change pushes to your CDP, BI, marketing automation. Auditable, replayable, no nightly drift." },
];

function Capabilities() {
  return (
    <div style={{
      maxWidth: "var(--rail)", margin: "40px auto 0",
      display: "grid", gridTemplateColumns: "repeat(3, 1fr)",
      gap: 0,
      border: `1px solid ${RULE}`, borderRadius: 8, overflow: "hidden",
      background: PAPER,
    }}>
      {CAPS.map((c, i) => (
        <div key={c.idx} style={{
          padding: "18px 22px",
          borderRight: (i % 3 !== 2) ? `1px solid ${RULE}` : "none",
          borderBottom: (i < 3) ? `1px solid ${RULE}` : "none",
        }}>
          <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginBottom: 6 }}>
            <Mono size={10.5} color={FUCHSIA} style={{ letterSpacing: "0.12em", fontWeight: 700 }}>{c.idx}</Mono>
            <div style={{
              fontFamily: "'Inter', system-ui, sans-serif",
              fontSize: 13, fontWeight: 600, color: INK, letterSpacing: "-0.005em",
            }}>{c.title}</div>
          </div>
          <div style={{ fontSize: 12.5, color: DIM, lineHeight: 1.5 }}>
            {c.body}
          </div>
        </div>
      ))}
    </div>
  );
}

// The Header() that used to live here duplicated crm.html's own hero
// verbatim ("Every contact, every channel. / One omnichannel record.")
// in the old Source Serif 4 / #CE5188 palette — but nothing ever rendered
// it (no <Header /> call, no window export). crm.html's real hero already
// uses the shared .h-display/.eyebrow classes; this was dead duplicate
// code left over from before the page was restructured. Removed rather
// than fixed, since fixing a function nothing calls fixes nothing.

// ────────────────────────── playback bar ──────────────────────────
function PlayBar({ time, playing, onToggle, onSeek, speed, onSpeed }) {
  const dedupCount = PACKETS.filter(p => time >= p.t && p.kind === "merge").length;
  const newCount = PACKETS.filter(p => time >= p.t && p.kind === "new").length;

  return (
    <div style={{
      maxWidth: 1100, margin: "30px auto 0",
      background: "rgba(255,255,255,0.7)",
      border: `1px solid ${RULE}`,
      borderRadius: 14, padding: "12px 18px",
      backdropFilter: "blur(8px)",
      WebkitBackdropFilter: "blur(8px)",
      display: "grid",
      gridTemplateColumns: "auto 1fr auto",
      gap: 22, alignItems: "center",
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <button onClick={onToggle} style={{
          display: "inline-flex", alignItems: "center", gap: 9,
          height: 38, padding: "0 16px 0 13px", borderRadius: 999,
          background: playing ? INK : FUCHSIA, border: "none", cursor: "pointer",
          color: "#fff", fontSize: 13, fontWeight: 600, letterSpacing: "-0.005em",
          fontFamily: "'Inter', system-ui, sans-serif",
          boxShadow: playing ? "0 8px 18px -10px rgba(0,0,0,0.5)" : `0 10px 22px -10px ${FUCHSIA}AA`,
          transition: "background 0.2s ease",
        }}>
          <Icon name={playing ? "pause" : "play"} size={11} color="#fff" />
          <span>{playing ? "Pause" : "Play"}</span>
        </button>
        <Mono size={12} color={INK} style={{ fontVariantNumeric: "tabular-nums" }}>
          {fmt(time)} <span style={{ color: FAINT }}>/ {fmt(TIMELINE_END)}</span>
        </Mono>
      </div>

      <div style={{ position: "relative", height: 28, cursor: "pointer" }}
        onClick={(e) => {
          const rect = e.currentTarget.getBoundingClientRect();
          const pct = (e.clientX - rect.left) / rect.width;
          onSeek(Math.max(0, Math.min(TIMELINE_END - 0.05, pct * TIMELINE_END)));
        }}
      >
        <div style={{
          position: "absolute", top: 13, left: 0, right: 0, height: 3,
          background: `${FUCHSIA}15`, borderRadius: 2,
        }} />
        <div style={{
          position: "absolute", top: 13, left: 0,
          width: `${(time / TIMELINE_END) * 100}%`, height: 3,
          background: `linear-gradient(90deg, ${FUCHSIA}, ${FUCHSIA_DEEP})`,
          borderRadius: 2, pointerEvents: "none",
        }} />
        {PACKETS.map((p, i) => {
          const left = (p.t / TIMELINE_END) * 100;
          const tone = p.kind === "new" ? GREEN : FUCHSIA;
          return (
            <span key={i} style={{
              position: "absolute", top: 10, left: `${left}%`,
              width: 2, height: 9, background: tone, opacity: time >= p.t ? 1 : 0.35,
              transform: "translateX(-1px)", borderRadius: 1, pointerEvents: "none",
            }} />
          );
        })}
      </div>

      <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
          <Icon name="spark" size={12} color={GREEN} strokeWidth={2.2} />
          <Mono size={9} color={FAINT} style={{ letterSpacing: "0.18em", fontWeight: 600 }}>NEW</Mono>
          <Mono size={13} color={GREEN} style={{ fontWeight: 700, fontVariantNumeric: "tabular-nums" }}>{newCount}</Mono>
        </span>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
          <Icon name="merge" size={12} color={FUCHSIA} strokeWidth={2.2} />
          <Mono size={9} color={FAINT} style={{ letterSpacing: "0.18em", fontWeight: 600 }}>MERGED</Mono>
          <Mono size={13} color={FUCHSIA} style={{ fontWeight: 700, fontVariantNumeric: "tabular-nums" }}>{dedupCount}</Mono>
        </span>
      </div>
    </div>
  );
}

// ────────────────────────── main hero ──────────────────────────
function HotelCRMHero({ tweaks }) {
  const speed = tweaks?.speed ?? 1;
  const { time, setTime, playing, setPlaying } = usePlayback(speed);

  // recent packet per source (for source-card highlight)
  const recentBySource = {};
  for (const p of PACKETS) {
    if (time >= p.t && (time - p.t) < 1.0 &&
        (!recentBySource[p.src] || p.t > recentBySource[p.src].t)) {
      recentBySource[p.src] = p;
    }
  }

  return (
    <section data-screen-label="Hotel CRM" style={{
      position: "relative",
      minHeight: "100vh",
      background: CREAM,
      color: INK,
      fontFamily: "'Inter', system-ui, sans-serif",
      overflow: "hidden",
      padding: "56px clamp(20px,4vw,48px) 48px",
    }}>
      {/* ambient gradients */}
      <div style={{
        position: "absolute", top: -200, left: "50%", width: 900, height: 700,
        transform: "translateX(-50%)",
        background: `radial-gradient(ellipse, rgba(237, 77, 134, 0.10) 0%, transparent 60%)`,
        filter: "blur(40px)", pointerEvents: "none",
      }} />

      {/* Fixed 1000px diagram grid — shrunk to fit rather than restacked, so a
          phone sees the same composition a desktop does (see ScaleFrame). */}
      <ScaleFrame nativeWidth={1000} style={{ maxWidth: 1000, margin: "0 auto" }}>
        <Diagram time={time} playing={playing} recentBySource={recentBySource} />
      </ScaleFrame>
      <RecordsStrip time={time} />
      <Capabilities />
    </section>
  );
}

window.HotelCRMHero = HotelCRMHero;
