/* ================================================================
   insights.jsx — Insights hub. Hero + sticky control bar, curated
   "What we're watching", the "Latest analysis" feed spine, events,
   podcasts, explore-by-topic and a conversion band. States: default,
   filtered, empty, loading. Data/cards from insights-data.jsx.
================================================================ */
const { MAX, Eyebrow, SectionHead, SiteHeader, SiteFooter } = window;
const { Button } = window.CapitalEconomicsDesignSystem_f2a5b2;
const {
  NS_INS: NS, SERIF_INS: SERIF, IIcon, TYPES, TOPICS, REGIONS, ROLES, SECTORS,
  ITEMS, EVENTS, PODCASTS, EXPLORE, ArticleCard, LeadCard, EventCard, PodcastCard, ConversionModule,
} = window;

/* Combined pool for filtering (written research + events + podcasts). */
const ALL = [
  ...ITEMS,
  ...EVENTS.map((e) => ({ ...e, type: "Event", read: e.duration || 0, reads: 0, __event: true })),
  ...PODCASTS.map((p) => ({ ...p, type: "Podcast", topic: p.topic, region: p.region, read: p.duration, reads: 0, __pod: true })),
];

/* ================================================================ CSS */
let cssI = false;
function injectCSS() {
  if (cssI || typeof document === "undefined") return; cssI = true;
  const base = `
  .ins-wrap { min-height: 100vh; background: var(--surface-page); }
  /* Hero */
  .ins-hero { background: #1d233f; }
  .ins-hero-in { max-width: ${MAX}px; margin: 0 auto; padding: 34px 32px 30px; }
  .ins-h1 { font-family: ${SERIF}; font-weight: 600; font-size: 40px; line-height: 1.05; letter-spacing: -0.02em; color: #fff; margin: 0; }
  .ins-h1-sub { font-family: ${NS}; font-size: 16px; line-height: 1.5; color: var(--text-on-navy); margin: 12px 0 0; max-width: 640px; }

  /* Control bar */
  .ins-bar { position: sticky; top: 0; z-index: 30; background: var(--surface-card); border-bottom: 1px solid var(--border-default); transition: box-shadow 160ms cubic-bezier(0.2,0,0,1), padding 160ms cubic-bezier(0.2,0,0,1); }
  .ins-bar.is-stuck { box-shadow: 0 6px 20px rgba(20,30,60,0.08); }
  .ins-bar-in { max-width: ${MAX}px; margin: 0 auto; padding: 14px 32px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .ins-search { display: flex; align-items: center; gap: 9px; background: var(--surface-sunken); border: 1px solid var(--border-default); border-radius: var(--radius-md, 6px); padding: 0 12px; height: 42px; flex: 1 1 260px; min-width: 200px; color: var(--text-muted); }
  .ins-search input { border: 0; outline: 0; background: transparent; font-family: ${NS}; font-size: 15px; color: var(--text-primary); width: 100%; }
  .ins-ms { position: relative; }
  .ins-ms-btn { display: inline-flex; align-items: center; gap: 8px; height: 42px; padding: 0 13px; background: var(--surface-card); border: 1px solid var(--border-default); border-radius: var(--radius-md, 6px); font-family: ${NS}; font-size: 14px; font-weight: 600; color: var(--text-secondary); cursor: pointer; white-space: nowrap; }
  .ins-ms-btn:hover { border-color: var(--border-strong); color: var(--text-primary); }
  .ins-ms-btn.has { color: var(--brand); border-color: var(--brand); background: var(--brand-subtle); }
  .ins-ms-count { font-variant-numeric: tabular-nums; background: var(--brand); color: #fff; border-radius: 999px; min-width: 18px; height: 18px; padding: 0 5px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px; }
  .ins-ms-panel { position: absolute; top: 48px; left: 0; z-index: 40; background: var(--surface-card); border: 1px solid var(--border-default); border-radius: var(--radius-md, 8px); box-shadow: 0 14px 40px rgba(20,30,60,0.16); padding: 8px; min-width: 220px; }
  .ins-opt { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 6px; cursor: pointer; font-family: ${NS}; font-size: 14.5px; color: var(--text-primary); }
  .ins-opt:hover { background: var(--surface-sunken); }
  .ins-opt-box { width: 18px; height: 18px; border-radius: 4px; border: 1.5px solid var(--border-strong); display: inline-flex; align-items: center; justify-content: center; color: #fff; flex: 0 0 auto; }
  .ins-opt.on .ins-opt-box { background: var(--brand); border-color: var(--brand); }
  .ins-sort { margin-left: auto; }
  .ins-bar-mobtoggle { display: none; }

  /* chips */
  .ins-chips-row { max-width: ${MAX}px; margin: 0 auto; padding: 0 32px; }
  .ins-chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 0 2px; align-items: center; }
  .ins-chip { display: inline-flex; align-items: center; gap: 7px; background: var(--brand-subtle); border: 1px solid #cdd8f0; color: var(--brand); border-radius: 999px; padding: 5px 8px 5px 12px; font-family: ${NS}; font-size: 13px; font-weight: 600; }
  .ins-chip button { border: 0; background: transparent; color: var(--brand); cursor: pointer; display: inline-flex; padding: 0; }
  .ins-clear { border: 0; background: transparent; color: var(--text-secondary); font-family: ${NS}; font-size: 13px; font-weight: 700; cursor: pointer; text-decoration: underline; }

  /* section shell */
  .ins-sec { max-width: ${MAX}px; margin: 0 auto; padding: 44px 32px; }
  .ins-sec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 8px; }
  .ins-sec-label { font-family: ${SERIF}; font-weight: 600; font-size: 27px; letter-spacing: -0.015em; color: var(--text-primary); margin: 0; }
  .ins-sec-note { font-family: ${NS}; font-size: 15px; color: var(--text-secondary); margin: 4px 0 22px; }
  .ins-viewall { font-family: ${NS}; font-size: 14.5px; font-weight: 700; color: var(--brand); text-decoration: none; display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
  .ins-viewall:hover { text-decoration: underline; }

  /* cards */
  .ins-card { display: flex; flex-direction: column; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); overflow: hidden; text-decoration: none; color: inherit; transition: border-color 150ms cubic-bezier(0.2,0,0,1), box-shadow 150ms cubic-bezier(0.2,0,0,1), transform 150ms cubic-bezier(0.2,0,0,1); }
  .ins-card:hover { border-color: var(--border-default); box-shadow: var(--shadow-sm); transform: translateY(-2px); }
  .ins-thumb { position: relative; aspect-ratio: 16/9; background: #EDF1FB; overflow: hidden; }
  .ins-thumb svg { width: 100%; height: 100%; }
  .ins-badge-type { font-family: ${NS}; font-weight: 700; font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; color: #fff; background: #1d233f; padding: 4px 9px; border-radius: 4px; }
  .ins-badge-over { position: absolute; top: 12px; left: 12px; }
  .ins-card-body { padding: 16px 18px 18px; display: flex; flex-direction: column; flex: 1; }
  .ins-eyebrow { font-family: ${NS}; font-weight: 700; font-size: 11px; letter-spacing: 0.09em; text-transform: uppercase; color: var(--brand); margin-bottom: 9px; }
  .ins-title { font-family: ${SERIF}; font-weight: 600; font-size: 19px; line-height: 1.24; letter-spacing: -0.01em; color: var(--text-primary); margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .ins-standfirst { font-family: ${NS}; font-size: 14px; line-height: 1.5; color: var(--text-secondary); margin: 9px 0 0; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
  .ins-meta { display: flex; align-items: center; gap: 8px; margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--border-subtle); font-family: ${NS}; font-size: 12.5px; color: var(--text-muted); }
  .ins-dot { opacity: 0.6; }

  /* access badge */
  .ins-access-wrap { position: relative; display: inline-flex; }
  .ins-access { font-family: ${NS}; font-weight: 700; font-size: 11px; letter-spacing: 0.02em; padding: 3px 9px; border-radius: 999px; cursor: help; white-space: nowrap; }
  .ins-access.is-free { background: var(--grey-100); color: var(--grey-600); }
  .ins-access.is-credit { background: transparent; color: var(--brand); border: 1px solid var(--brand); }
  .ins-access.is-sub { background: var(--brand); color: #fff; }
  .ins-tip { position: absolute; bottom: calc(100% + 8px); right: 0; width: 210px; background: #1d233f; color: #fff; font-family: ${NS}; font-size: 12px; line-height: 1.45; padding: 9px 11px; border-radius: 8px; box-shadow: 0 10px 30px rgba(20,30,60,0.28); opacity: 0; visibility: hidden; transform: translateY(4px); transition: opacity 140ms, transform 140ms, visibility 140ms; z-index: 20; }
  .ins-tip a, .ins-tip-link { color: var(--ce-blue-light); font-weight: 700; text-decoration: underline; cursor: pointer; }
  .ins-access-wrap:hover .ins-tip, .ins-access:focus + .ins-tip { opacity: 1; visibility: visible; transform: none; }

  /* compact card (curated list) */
  .ins-card-compact { display: block; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: 16px 18px; text-decoration: none; color: inherit; transition: border-color 150ms, box-shadow 150ms; }
  .ins-card-compact:hover { border-color: var(--border-default); box-shadow: var(--shadow-sm); }
  .ins-cc-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
  .ins-cc-title { font-family: ${SERIF}; font-weight: 600; font-size: 16.5px; line-height: 1.26; letter-spacing: -0.01em; color: var(--text-primary); margin: 0 0 10px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

  /* What we're watching grid */
  .ins-watch { display: grid; grid-template-columns: 7fr 5fr; gap: 24px; align-items: stretch; }
  .ins-watch-list { display: flex; flex-direction: column; gap: 14px; }
  .ins-lead .ins-title-lead { font-size: 27px; line-height: 1.14; -webkit-line-clamp: 3; }
  .ins-lead .ins-standfirst-lead { font-size: 15.5px; -webkit-line-clamp: 2; }
  .ins-lead .ins-thumb-lead { aspect-ratio: 16/8; }

  /* feed */
  .ins-feed { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
  .ins-loadmore { display: flex; justify-content: center; margin-top: 34px; }
  .ins-count { font-family: ${NS}; font-size: 15px; color: var(--text-secondary); margin: 0 0 20px; }
  .ins-count strong { color: var(--text-primary); }

  /* in-feed conversion module */
  .ins-conv-module { grid-column: span 1; display: flex; flex-direction: column; justify-content: center; background: linear-gradient(160deg, #1d233f 0%, #164194 130%); border-radius: var(--radius-lg); padding: 26px 24px; color: #fff; }
  .ins-conv-eyebrow { font-family: ${NS}; font-weight: 700; font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ce-blue-light); margin-bottom: 12px; }
  .ins-conv-h { font-family: ${SERIF}; font-weight: 600; font-size: 21px; line-height: 1.2; letter-spacing: -0.01em; margin: 0 0 10px; color: #fff; }
  .ins-conv-p { font-family: ${NS}; font-size: 14px; line-height: 1.5; color: rgba(255,255,255,0.82); margin: 0 0 20px; }
  .ins-conv-actions { display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
  .ins-conv-sec { font-family: ${NS}; font-size: 14px; font-weight: 700; color: #fff; text-decoration: underline; }
  .ins-conv-primary { background: #fff !important; color: var(--brand) !important; border-color: #fff !important; }

  /* skeleton */
  .ins-skel { border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); overflow: hidden; background: var(--surface-card); }
  .ins-skel-thumb { aspect-ratio: 16/9; background: var(--grey-100); }
  .ins-skel-body { padding: 16px 18px 20px; }
  .ins-skel-line { height: 12px; border-radius: 6px; background: var(--grey-100); margin-bottom: 10px; }
  @keyframes insShimmer { 0% { opacity: 0.55; } 50% { opacity: 1; } 100% { opacity: 0.55; } }
  .ins-skel-thumb, .ins-skel-line { animation: insShimmer 1.3s ease-in-out infinite; }

  /* events */
  .ins-tabs { display: inline-flex; gap: 2px; background: var(--surface-sunken); border-radius: 9px; padding: 3px; margin-bottom: 22px; }
  .ins-tab { border: 0; background: transparent; font-family: ${NS}; font-size: 14px; font-weight: 700; padding: 8px 15px; border-radius: 7px; color: var(--text-secondary); cursor: pointer; }
  .ins-tab.on { background: var(--surface-card); color: var(--brand); box-shadow: var(--shadow-sm); }
  .ins-rail { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
  .ins-event-card { display: flex; flex-direction: column; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); padding: 20px 22px 22px; }
  .ins-event-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
  .ins-date-block { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 58px; height: 58px; border-radius: 10px; background: var(--brand-subtle); border: 1px solid #cdd8f0; color: var(--brand); }
  .ins-date-day { font-family: ${SERIF}; font-weight: 600; font-size: 24px; line-height: 1; }
  .ins-date-mon { font-family: ${NS}; font-weight: 700; font-size: 10px; letter-spacing: 0.08em; margin-top: 2px; }
  .ins-format { font-family: ${NS}; font-weight: 700; font-size: 10.5px; letter-spacing: 0.06em; text-transform: uppercase; padding: 4px 10px; border-radius: 999px; }
  .ins-format.is-inperson { background: var(--positive-100); color: var(--positive); }
  .ins-format.is-online { background: var(--advance-100); color: var(--advance-500); }
  .ins-event-title { font-family: ${SERIF}; font-weight: 600; font-size: 19px; line-height: 1.24; letter-spacing: -0.01em; color: var(--text-primary); margin: 0 0 16px; min-height: 2.5em; }
  .ins-event-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 18px; }
  .ins-event-line { display: inline-flex; align-items: center; gap: 7px; font-family: ${NS}; font-size: 13.5px; color: var(--text-secondary); }
  .ins-event-cta { margin-top: auto; }
  .ins-mini-btn { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--brand); background: transparent; color: var(--brand); font-family: ${NS}; font-size: 14px; font-weight: 700; padding: 9px 16px; border-radius: var(--radius-md, 6px); cursor: pointer; }
  .ins-mini-btn:hover { background: var(--brand-subtle); }
  .ins-mini-btn-solid { background: var(--brand); color: #fff; }
  .ins-mini-btn-solid:hover { background: var(--brand-hover); }

  /* podcasts (dark) */
  .ins-pod-card { display: flex; flex-direction: column; background: #24213a; border-radius: var(--radius-lg); padding: 22px 24px; color: #fff; min-height: 200px; }
  .ins-pod-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
  .ins-pod-ep { font-family: ${NS}; font-weight: 700; font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ce-blue-light); }
  .ins-pod-free { font-family: ${NS}; font-weight: 700; font-size: 10.5px; letter-spacing: 0.04em; color: rgba(255,255,255,0.85); border: 1px solid rgba(255,255,255,0.3); border-radius: 999px; padding: 3px 9px; }
  .ins-pod-title { font-family: ${SERIF}; font-weight: 600; font-size: 20px; line-height: 1.22; letter-spacing: -0.01em; margin: 0 0 auto; color: #fff; }
  .ins-pod-foot { display: flex; align-items: center; gap: 12px; margin-top: 22px; }
  .ins-pod-play { width: 42px; height: 42px; border-radius: 999px; border: 0; background: var(--ce-blue-light); color: #24213a; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; flex: 0 0 auto; }
  .ins-pod-play:hover { background: #fff; }
  .ins-pod-meta { font-family: ${NS}; font-size: 13px; color: rgba(255,255,255,0.7); }

  /* explore */
  .ins-topics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px 24px; border-top: 1px solid var(--border-default); }
  .ins-topic-link { display: flex; align-items: center; justify-content: space-between; font-family: ${NS}; font-size: 15.5px; font-weight: 600; color: var(--text-primary); text-decoration: none; padding: 15px 4px; border-bottom: 1px solid var(--border-subtle); }
  .ins-topic-link:hover { color: var(--brand); }
  .ins-topic-link .ins-ta { color: var(--text-muted); }
  .ins-topic-link:hover .ins-ta { color: var(--brand); }

  /* conversion band */
  .ins-conv-band { background: var(--brand); }
  .ins-conv-band-in { max-width: ${MAX}px; margin: 0 auto; padding: 56px 32px; display: flex; align-items: center; justify-content: space-between; gap: 30px; flex-wrap: wrap; }
  .ins-conv-band-h { font-family: ${SERIF}; font-weight: 600; font-size: 30px; line-height: 1.15; letter-spacing: -0.015em; color: #fff; margin: 0; max-width: 620px; }
  .ins-conv-band-p { font-family: ${NS}; font-size: 15.5px; line-height: 1.55; color: rgba(255,255,255,0.85); margin: 14px 0 0; max-width: 560px; }
  .ins-conv-band-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
  .ins-conv-band-sec { font-family: ${NS}; font-size: 15px; font-weight: 700; color: #fff; text-decoration: underline; }

  /* empty */
  .ins-empty { text-align: center; padding: 56px 20px; border: 1px dashed var(--border-strong); border-radius: var(--radius-lg); background: var(--surface-card); }

  /* mobile filter sheet */
  .ins-sheet-scrim { position: fixed; inset: 0; background: rgba(20,24,46,0.5); z-index: 60; display: none; }
  .ins-sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 61; background: var(--surface-card); border-radius: 18px 18px 0 0; max-height: 82vh; overflow-y: auto; padding: 20px; display: none; }

  @media (max-width: 1080px) { .ins-feed { grid-template-columns: repeat(2, 1fr); } .ins-topics { grid-template-columns: repeat(3, 1fr); } }
  `;
  const mob = `
  @@.ins-hero-in { padding: 26px 18px 22px; }
  @@.ins-h1 { font-size: 30px; }
  @@.ins-bar-in { padding: 10px 16px; gap: 8px; flex-wrap: nowrap; }
  @@.ins-ms, @@.ins-sort, @@.ins-morefilters { display: none !important; }
  @@.ins-bar-mobtoggle { display: inline-flex; }
  @@.ins-chips-row { padding: 0 16px; }
  @@.ins-sec { padding: 34px 16px; }
  @@.ins-watch { grid-template-columns: 1fr; gap: 16px; }
  @@.ins-feed { grid-template-columns: 1fr; }
  @@.ins-conv-module { grid-column: auto; }
  @@.ins-topics { grid-template-columns: 1fr 1fr; }
  @@.ins-rail { grid-template-columns: 1fr; }
  @@.ins-conv-band-in { padding: 40px 18px; }
  @@.ins-conv-band-h { font-size: 24px; }
  @@.ins-sheet-scrim.open { display: block; }
  @@.ins-sheet.open { display: block; }
  `;
  const media = mob.replace(/@@/g, "");
  const classed = mob.replace(/@@/g, ".ins-view-mobile ");
  const el = document.createElement("style"); el.setAttribute("data-ce", "insights");
  el.textContent = base + "\n@media (max-width: 760px){" + media + "}\n" + classed;
  document.head.appendChild(el);
}
injectCSS();

/* ================================================================ Multi-select */
function MultiSelect({ label, options, selected, onChange, forceOpen }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef();
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", h); return () => document.removeEventListener("mousedown", h);
  }, [open]);
  const toggle = (o) => onChange(selected.includes(o) ? selected.filter((x) => x !== o) : [...selected, o]);
  const showPanel = forceOpen || open;
  return (
    <div className="ins-ms" ref={ref}>
      {!forceOpen && (
        <button className={"ins-ms-btn" + (selected.length ? " has" : "")} onClick={() => setOpen((o) => !o)} aria-expanded={open}>
          {label}{selected.length > 0 && <span className="ins-ms-count">{selected.length}</span>}
          <IIcon name="chevron-down" size={16} />
        </button>
      )}
      {forceOpen && <div style={{ fontFamily: NS, fontWeight: 700, fontSize: 12, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--text-muted)", margin: "18px 0 6px" }}>{label}</div>}
      {showPanel && (
        <div className="ins-ms-panel" style={forceOpen ? { position: "static", boxShadow: "none", border: 0, padding: 0, minWidth: 0 } : null}>
          {options.map((o) => (
            <div key={o} className={"ins-opt" + (selected.includes(o) ? " on" : "")} onClick={() => toggle(o)} role="checkbox" aria-checked={selected.includes(o)}>
              <span className="ins-opt-box">{selected.includes(o) && <IIcon name="check" size={13} />}</span>{o}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ================================================================ Control bar */
function ControlBar({ f, setF, stuck, resultCount, onOpenSheet }) {
  const [moreOpen, setMoreOpen] = React.useState(false);
  const upd = (k) => (val) => setF((s) => ({ ...s, [k]: val }));
  return (
    <div className={"ins-bar" + (stuck ? " is-stuck" : "")}>
      <div className="ins-bar-in">
        <div className="ins-search">
          <IIcon name="search" size={17} />
          <input value={f.q} placeholder="Search by topic, region or keyword" onChange={(e) => setF((s) => ({ ...s, q: e.target.value }))} aria-label="Search insights" />
          {f.q && <button aria-label="Clear search" onClick={() => setF((s) => ({ ...s, q: "" }))} style={{ border: 0, background: "transparent", color: "var(--text-muted)", cursor: "pointer", display: "inline-flex" }}><IIcon name="x" size={16} /></button>}
        </div>
        <MultiSelect label="Type" options={TYPES} selected={f.types} onChange={upd("types")} />
        <MultiSelect label="Topic" options={TOPICS} selected={f.topics} onChange={upd("topics")} />
        <MultiSelect label="Region" options={REGIONS} selected={f.regions} onChange={upd("regions")} />
        <div className="ins-ms ins-morefilters">
          <button className={"ins-ms-btn" + ((f.roles.length + f.sectors.length) ? " has" : "")} onClick={() => setMoreOpen((o) => !o)} aria-expanded={moreOpen}>
            <IIcon name="sliders" size={15} />More filters{(f.roles.length + f.sectors.length) > 0 && <span className="ins-ms-count">{f.roles.length + f.sectors.length}</span>}
          </button>
          {moreOpen && (
            <div className="ins-ms-panel" style={{ minWidth: 260 }}>
              <div style={{ fontFamily: NS, fontSize: 12.5, color: "var(--text-muted)", padding: "4px 10px 8px" }}>Sales segmentation, not how most readers browse.</div>
              <div style={{ fontFamily: NS, fontWeight: 700, fontSize: 11, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--text-muted)", padding: "6px 10px 2px" }}>Role</div>
              {ROLES.map((o) => <div key={o} className={"ins-opt" + (f.roles.includes(o) ? " on" : "")} onClick={() => upd("roles")(f.roles.includes(o) ? f.roles.filter((x) => x !== o) : [...f.roles, o])}><span className="ins-opt-box">{f.roles.includes(o) && <IIcon name="check" size={13} />}</span>{o}</div>)}
              <div style={{ fontFamily: NS, fontWeight: 700, fontSize: 11, letterSpacing: "0.08em", textTransform: "uppercase", color: "var(--text-muted)", padding: "10px 10px 2px" }}>Sector</div>
              {SECTORS.map((o) => <div key={o} className={"ins-opt" + (f.sectors.includes(o) ? " on" : "")} onClick={() => upd("sectors")(f.sectors.includes(o) ? f.sectors.filter((x) => x !== o) : [...f.sectors, o])}><span className="ins-opt-box">{f.sectors.includes(o) && <IIcon name="check" size={13} />}</span>{o}</div>)}
            </div>
          )}
        </div>
        <div className="ins-ms ins-sort">
          <select value={f.sort} onChange={(e) => setF((s) => ({ ...s, sort: e.target.value }))} className="ins-ms-btn" style={{ appearance: "auto", paddingRight: 10 }} aria-label="Sort">
            <option value="recent">Most recent</option>
            <option value="read">Most read</option>
          </select>
        </div>
        <button className="ins-ms-btn ins-bar-mobtoggle" onClick={onOpenSheet} style={{ marginLeft: "auto" }}>
          <IIcon name="sliders" size={15} />Filters{(f.types.length + f.topics.length + f.regions.length + f.roles.length + f.sectors.length) > 0 && <span className="ins-ms-count">{f.types.length + f.topics.length + f.regions.length + f.roles.length + f.sectors.length}</span>}
        </button>
      </div>
    </div>
  );
}

/* chips */
function Chips({ f, setF }) {
  const chips = [];
  if (f.q) chips.push(["q", "“" + f.q + "”"]);
  ["types", "topics", "regions", "roles", "sectors"].forEach((k) => f[k].forEach((v) => chips.push([k, v])));
  if (!chips.length) return null;
  const remove = (k, v) => setF((s) => k === "q" ? { ...s, q: "" } : { ...s, [k]: s[k].filter((x) => x !== v) });
  return (
    <div className="ins-chips-row">
      <div className="ins-chips">
        {chips.map(([k, v], i) => (
          <span key={k + v + i} className="ins-chip">{v}<button aria-label={"Remove " + v} onClick={() => remove(k, v)}><IIcon name="x" size={13} /></button></span>
        ))}
        <button className="ins-clear" onClick={() => setF({ q: "", types: [], topics: [], regions: [], roles: [], sectors: [], sort: f.sort })}>Clear all</button>
      </div>
    </div>
  );
}

/* mobile filter sheet */
function FilterSheet({ open, onClose, f, setF }) {
  const upd = (k) => (val) => setF((s) => ({ ...s, [k]: val }));
  return (
    <>
      <div className={"ins-sheet-scrim" + (open ? " open" : "")} onClick={onClose} />
      <div className={"ins-sheet" + (open ? " open" : "")} role="dialog" aria-label="Filters">
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 4 }}>
          <div style={{ fontFamily: SERIF, fontWeight: 600, fontSize: 20, color: "var(--text-primary)" }}>Filters</div>
          <button onClick={onClose} aria-label="Close" style={{ border: 0, background: "transparent", cursor: "pointer", color: "var(--text-secondary)" }}><IIcon name="x" size={22} /></button>
        </div>
        <MultiSelect label="Type" options={TYPES} selected={f.types} onChange={upd("types")} forceOpen />
        <MultiSelect label="Topic" options={TOPICS} selected={f.topics} onChange={upd("topics")} forceOpen />
        <MultiSelect label="Region" options={REGIONS} selected={f.regions} onChange={upd("regions")} forceOpen />
        <div style={{ display: "flex", gap: 12, marginTop: 22 }}>
          <Button variant="secondary" size="md" onClick={() => setF({ q: f.q, types: [], topics: [], regions: [], roles: [], sectors: [], sort: f.sort })}>Clear</Button>
          <Button variant="primary" size="md" block onClick={onClose}>Show results</Button>
        </div>
      </div>
    </>
  );
}

/* ================================================================ Bands */
function WhatWatching({ items }) {
  const [lead, ...rest] = items;
  if (!lead) return null;
  return (
    <section className="ins-sec">
      <h2 className="ins-sec-label">What we're watching</h2>
      <p className="ins-sec-note">The four pieces our economists think matter most this week.</p>
      <div className="ins-watch">
        <LeadCard it={lead} />
        <div className="ins-watch-list">
          {rest.slice(0, 3).map((it) => <ArticleCard key={it.id} it={it} compact />)}
        </div>
      </div>
    </section>
  );
}

function Skeletons({ n }) {
  return <>{Array.from({ length: n }).map((_, i) => (
    <div key={i} className="ins-skel"><div className="ins-skel-thumb" /><div className="ins-skel-body"><div className="ins-skel-line" style={{ width: "40%" }} /><div className="ins-skel-line" style={{ width: "92%" }} /><div className="ins-skel-line" style={{ width: "70%" }} /></div></div>
  ))}</>;
}

function Feed({ items, visible, onMore, loading, filtered, count }) {
  const shown = items.slice(0, visible);
  const cards = [];
  shown.forEach((it, i) => {
    cards.push(it.__event ? <EventCard key={it.id} ev={it} /> : it.__pod ? <PodcastCard key={it.id} p={it} /> : <ArticleCard key={it.id} it={it} />);
    if (i === 5) cards.push(<ConversionModule key="conv" />);
  });
  return (
    <section className="ins-sec" id="latest">
      {filtered
        ? <p className="ins-count"><strong>{count}</strong> result{count === 1 ? "" : "s"}{" "}shown</p>
        : <><h2 className="ins-sec-label">Latest analysis</h2><p className="ins-sec-note">Written research, newest first. Insights, publications and case studies in one feed.</p></>}
      <div className="ins-feed">
        {cards}
        {loading && <Skeletons n={3} />}
      </div>
      {visible < items.length && !loading && (
        <div className="ins-loadmore"><Button variant="secondary" size="lg" onClick={onMore}>Load more</Button></div>
      )}
    </section>
  );
}

function EmptyResults({ f, setF }) {
  const suggest = ["Inflation", "China", "Commodities"];
  return (
    <section className="ins-sec">
      <div className="ins-empty">
        <div style={{ fontFamily: SERIF, fontWeight: 600, fontSize: 24, color: "var(--text-primary)", marginBottom: 10 }}>No results match those filters.</div>
        <p style={{ fontFamily: NS, fontSize: 15.5, color: "var(--text-secondary)", margin: "0 auto 20px", maxWidth: 460 }}>Nothing came back for your current selection. Remove a filter, or try one of these popular topics.</p>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 8, justifyContent: "center", marginBottom: 22 }}>
          {suggest.map((t) => <button key={t} className="ins-chip" style={{ cursor: "pointer" }} onClick={() => setF({ q: "", types: [], topics: [t], regions: [], roles: [], sectors: [], sort: f.sort })}>{t}</button>)}
        </div>
        <Button variant="secondary" size="md" onClick={() => setF({ q: "", types: [], topics: [], regions: [], roles: [], sectors: [], sort: f.sort })}>Clear all filters</Button>
      </div>
    </section>
  );
}

function Events() {
  const [tab, setTab] = React.useState("upcoming");
  const list = EVENTS.filter((e) => tab === "upcoming" ? !e.onDemand : e.onDemand).slice(0, 3);
  return (
    <section className="ins-sec" style={{ background: "var(--surface-card)", maxWidth: "none", borderTop: "1px solid var(--border-subtle)", borderBottom: "1px solid var(--border-subtle)" }}>
      <div style={{ maxWidth: MAX, margin: "0 auto" }}>
        <div className="ins-sec-head" style={{ marginBottom: 4 }}>
          <h2 className="ins-sec-label">Events</h2>
          <a href="#" className="ins-viewall">View all events<IIcon name="arrow-right" size={15} /></a>
        </div>
        <div className="ins-tabs">
          <button className={"ins-tab" + (tab === "upcoming" ? " on" : "")} onClick={() => setTab("upcoming")}>Upcoming</button>
          <button className={"ins-tab" + (tab === "ondemand" ? " on" : "")} onClick={() => setTab("ondemand")}>Watch on demand</button>
        </div>
        <div className="ins-rail">{list.map((e) => <EventCard key={e.id} ev={e} />)}</div>
      </div>
    </section>
  );
}

function Podcasts() {
  return (
    <section className="ins-sec">
      <div className="ins-sec-head">
        <div>
          <h2 className="ins-sec-label">The Weekly Briefing</h2>
          <p className="ins-sec-note" style={{ marginBottom: 0 }}>Free to listen, no account needed.</p>
        </div>
        <a href="#" className="ins-viewall">View all episodes<IIcon name="arrow-right" size={15} /></a>
      </div>
      <div className="ins-rail" style={{ marginTop: 20 }}>{PODCASTS.slice(0, 3).map((p) => <PodcastCard key={p.id} p={p} />)}</div>
    </section>
  );
}

function Explore() {
  return (
    <section className="ins-sec">
      <h2 className="ins-sec-label" style={{ marginBottom: 6 }}>Explore by topic</h2>
      <p className="ins-sec-note">Curated topic and region pages, updated as the story develops.</p>
      <div className="ins-topics">
        {EXPLORE.map(([t, href]) => (
          <a key={t} href={href} className="ins-topic-link">{t}<span className="ins-ta"><IIcon name="arrow-right" size={15} /></span></a>
        ))}
      </div>
    </section>
  );
}

function ConversionBand() {
  return (
    <section className="ins-conv-band">
      <div className="ins-conv-band-in">
        <div>
          <h2 className="ins-conv-band-h">You have been reading the output. The platform is where the work happens.</h2>
          <p className="ins-conv-band-p">A 7-day trial gives you capped, read-only access to the full research platform. No card, no sales call to get started.</p>
        </div>
        <div className="ins-conv-band-actions">
          <Button variant="inverse" size="lg" iconEnd={<IIcon name="arrow-right" size={18} />}>Start free trial</Button>
          <a href="#register" className="ins-conv-band-sec">Register for 3 free credits</a>
        </div>
      </div>
    </section>
  );
}

/* ================================================================ App */
function matches(it, f) {
  if (f.types.length && !f.types.includes(it.type)) return false;
  if (f.topics.length && !f.topics.includes(it.topic)) return false;
  if (f.regions.length && !f.regions.includes(it.region)) return false;
  if (f.q) {
    const q = f.q.toLowerCase();
    const hay = [it.title, it.standfirst, it.topic, it.region, it.author].filter(Boolean).join(" ").toLowerCase();
    if (!hay.includes(q)) return false;
  }
  return true; // role/sector are demoted no-ops for matching
}
const byRecent = (a, b) => (a.date < b.date ? 1 : -1);
const byRead = (a, b) => (b.reads || 0) - (a.reads || 0);

const EMPTY_F = { q: "", types: [], topics: [], regions: [], roles: [], sectors: [], sort: "recent" };

function readURL() {
  const p = new URLSearchParams(location.search);
  const arr = (k) => (p.get(k) ? p.get(k).split(",").filter(Boolean) : []);
  return { q: p.get("q") || "", types: arr("type"), topics: arr("topic"), regions: arr("region"), roles: arr("role"), sectors: arr("sector"), sort: p.get("sort") || "recent" };
}
function writeURL(f) {
  const p = new URLSearchParams();
  if (f.q) p.set("q", f.q);
  if (f.types.length) p.set("type", f.types.join(","));
  if (f.topics.length) p.set("topic", f.topics.join(","));
  if (f.regions.length) p.set("region", f.regions.join(","));
  if (f.roles.length) p.set("role", f.roles.join(","));
  if (f.sectors.length) p.set("sector", f.sectors.join(","));
  if (f.sort !== "recent") p.set("sort", f.sort);
  const qs = p.toString();
  history.replaceState(null, "", qs ? "?" + qs : location.pathname);
}

function Page({ embed }) {
  const [f, setF] = React.useState(embed ? EMPTY_F : readURL());
  const [visible, setVisible] = React.useState(12);
  const [loading, setLoading] = React.useState(false);
  const [stuck, setStuck] = React.useState(false);
  const [sheet, setSheet] = React.useState(false);
  const barTop = React.useRef();

  const filtered = !!(f.q || f.types.length || f.topics.length || f.regions.length || f.roles.length || f.sectors.length);

  React.useEffect(() => { writeURL(f); setVisible(12); if (filtered) { setLoading(true); const t = setTimeout(() => setLoading(false), 480); return () => clearTimeout(t); } }, [f.q, f.types, f.topics, f.regions, f.roles, f.sectors, f.sort]);

  React.useEffect(() => {
    const onScroll = () => setStuck((barTop.current ? barTop.current.getBoundingClientRect().top : 999) < 1);
    window.addEventListener("scroll", onScroll, { passive: true }); onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  const sorter = f.sort === "read" ? byRead : byRecent;
  const pool = filtered ? ALL : ITEMS;
  const results = pool.filter((it) => matches(it, f)).sort(sorter);
  const curated = [...ITEMS].sort(byRecent).slice(0, 4); // fallback = most recent as lead + next 3

  const onMore = () => { setLoading(true); setTimeout(() => { setVisible((v) => v + 12); setLoading(false); }, 480); };

  return (
    <div className={"ins-wrap" + (embed ? " ins-view-mobile" : "")}>
      <SiteHeader />
      <div className="ins-hero"><div className="ins-hero-in">
        <h1 className="ins-h1">Insights</h1>
        <p className="ins-h1-sub">Daily notes, deep-dive publications, live events and podcasts from our economists in London, Toronto and Singapore.</p>
      </div></div>
      <div ref={barTop} />
      <ControlBar f={f} setF={setF} stuck={stuck} resultCount={results.length} onOpenSheet={() => setSheet(true)} />
      <Chips f={f} setF={setF} />
      <FilterSheet open={sheet} onClose={() => setSheet(false)} f={f} setF={setF} />

      {filtered ? (
        (loading || results.length > 0)
          ? <Feed items={results} visible={visible} onMore={onMore} loading={loading} filtered count={results.length} />
          : <EmptyResults f={f} setF={setF} />
      ) : (
        <>
          <WhatWatching items={curated} />
          <Feed items={results} visible={visible} onMore={onMore} loading={loading} filtered={false} count={results.length} />
          <Events />
          <Podcasts />
          <Explore />
        </>
      )}

      <ConversionBand />
      <SiteFooter />
    </div>
  );
}

function App() {
  return <Page />;
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
