/* enterprise-intel.jsx — the entity-and-lens intelligence screens, rendered
 * INSIDE the existing Mira Enterprise shell (enterprise.jsx provides Shell +
 * nav + org controls). Uses the existing ent-* classes / --ent-* tokens
 * (navy/cream) — NOT the retired #/mx burnt-orange shell. Reuses window.MX.
 * enterprise.jsx delegates the country/funder/opportunity/search/funding
 * routes here via window.MiraIntel.render(kind, hash, props).
 */
(function () {
  'use strict';
  var React = window.React, h = React.createElement;
  function MX() { return window.MX; }

  // small local styles that layer on the existing enterprise tokens (no new palette)
  function injectCSS() {
    if (document.getElementById('ent-intel-css')) return;
    var s = document.createElement('style'); s.id = 'ent-intel-css';
    s.textContent = `
    .ei-tabs{display:flex;gap:2px;border-bottom:1px solid var(--ent-border);margin:6px 0 16px}
    .ei-tab{padding:8px 14px;font-size:13px;font-weight:600;color:var(--ent-muted);background:none;border:0;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer}
    .ei-tab.on{color:var(--ent-ink);border-bottom-color:var(--ent-accent)}
    .ei-row{display:grid;grid-template-columns:1fr auto;gap:4px 16px;padding:13px 15px;border:1px solid var(--ent-border);border-radius:10px;background:var(--ent-surface);cursor:pointer;margin-bottom:8px}
    .ei-row:hover{border-color:var(--ent-accent);background:var(--ent-surface-2,var(--ent-tint))}
    .ei-title{font-weight:600;color:var(--ent-ink);font-size:14px}
    .ei-meta{font-size:12px;color:var(--ent-muted);margin-top:3px}
    .ei-reason{font-size:12px;color:var(--ent-ink);margin-top:5px}
    .ei-concern{font-size:12px;color:#8A5A16;margin-top:2px}
    .ei-b{display:inline-block;padding:2px 8px;border-radius:6px;font-size:11px;font-weight:700;white-space:nowrap}
    .ei-b-strong{background:#E2F0E1;color:#2F6B34}.ei-b-review{background:#F3ECD9;color:#8A5A16}
    .ei-b-mon{background:var(--ent-tint,#EEE);color:var(--ent-muted)}.ei-b-nel{background:#F6E4DF;color:#9A3B2C}.ei-b-info{background:#EBE5F3;color:#5A3F8A}
    .ei-grp{display:flex;align-items:center;gap:10px;margin:20px 0 8px;cursor:pointer}
    .ei-grp h3{font-size:15px;color:var(--ent-ink);margin:0}
    .ei-count{font-size:12px;color:var(--ent-muted);font-weight:600}
    .ei-sec{font-size:14px;font-weight:700;color:var(--ent-ink);margin:20px 0 8px;border-bottom:1px solid var(--ent-border);padding-bottom:5px}
    .ei-kv{display:grid;grid-template-columns:190px 1fr;gap:6px 16px;font-size:13px}
    .ei-kv dt{color:var(--ent-muted)}.ei-kv dd{margin:0;color:var(--ent-ink);font-weight:500}
    .ei-dim{display:flex;justify-content:space-between;gap:12px;padding:7px 0;border-bottom:1px solid var(--ent-border);font-size:13px}
    .ei-cov{background:var(--ent-tint,#F4F1EA);border:1px solid var(--ent-border);border-radius:10px;padding:13px 15px;font-size:12.5px}
    .ei-v-strong{color:#2F6B34;font-weight:700}.ei-v-weak{color:#8A5A16;font-weight:600}.ei-v-ineligible{color:#9A3B2C;font-weight:700}
    .ei-v-unclear,.ei-v-possible{color:var(--ent-muted);font-weight:600}
    .ei-empty{text-align:center;padding:36px 20px;color:var(--ent-muted)}
    .ei-empty h3{color:var(--ent-ink);font-size:16px;margin:0 0 6px}
    .ei-search{width:100%;padding:14px 16px;font-size:16px;border:1px solid var(--ent-border);border-radius:11px;background:var(--ent-surface);color:var(--ent-ink)}
    .ei-search:focus{outline:2px solid var(--ent-accent);border-color:var(--ent-accent)}
    .ei-wide{max-width:1680px}
    /* §6: remove the 1360px centred cap for data-heavy intel pages */
    .ent-content:has(.ei-wide){max-width:none !important;padding:24px 34px 48px !important;margin:0 !important}
    .ei-crumb{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--ent-muted);margin-bottom:6px}
    .ei-crumb a,.ei-back{color:var(--ent-accent);cursor:pointer;text-decoration:none}
    .ei-back{display:inline-flex;align-items:center;gap:5px;font-size:13px;font-weight:600;margin-bottom:8px}
    .ei-84{display:grid;grid-template-columns:minmax(0,8fr) minmax(300px,4fr);gap:22px;align-items:start}
    .ei-sticky{position:sticky;top:14px;background:rgba(252,251,247,0.92);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);border:1px solid var(--ent-border);border-radius:12px;padding:16px 18px;box-shadow:0 2px 10px rgba(26,23,18,.05)}
    .ei-panel{background:rgba(252,251,247,0.9);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);border:1px solid var(--ent-border);border-radius:12px;padding:16px 18px}
    .ei-rd{display:flex;align-items:center;gap:9px;padding:6px 0;font-size:13px;border-bottom:1px solid var(--ent-border)}
    .ei-dot{width:9px;height:9px;border-radius:50%;flex:0 0 9px}
    .ei-dot-verified{background:#2F6B34}.ei-dot-unclear{background:#8A5A16}.ei-dot-missing{background:#9A3B2C}
    .ei-nba{background:#EBF0F5;border:1px solid #C9D6E2;border-radius:10px;padding:12px 14px;font-size:13.5px;color:var(--ent-ink)}
    .ei-tl{position:relative;padding-left:16px}
    .ei-tl-item{position:relative;padding:0 0 12px 12px;border-left:2px solid var(--ent-border)}
    .ei-tl-item:before{content:'';position:absolute;left:-5px;top:3px;width:8px;height:8px;border-radius:50%;background:var(--ent-accent)}
    .ei-state{display:inline-block;padding:3px 10px;border-radius:6px;font-size:11.5px;font-weight:700;letter-spacing:.03em}
    .ei-state-watching{background:var(--ent-tint,#EEE);color:var(--ent-muted)}
    .ei-state-assessing{background:#EBF0F5;color:#3A5A73}.ei-state-preparing{background:#EAF3EA;color:#2F6B34}
    .ei-state-awaiting{background:#F3ECD9;color:#8A5A16}.ei-state-deadline{background:#F6E4DF;color:#9A3B2C}
    .ei-state-submitted,.ei-state-won{background:#EAF3EA;color:#2F6B34}.ei-state-lost,.ei-state-withdrawn{background:var(--ent-tint,#EEE);color:var(--ent-muted)}
    .ei-task{display:flex;align-items:center;gap:8px;padding:6px 0;font-size:13px;border-bottom:1px solid var(--ent-border)}
    @media(max-width:1000px){.ei-84{grid-template-columns:1fr}.ei-sticky{position:static}}
    @media(max-width:900px){.ei-today{grid-template-columns:1fr !important}}
    /* §7 Search hero (dotted canvas → sticky bar) */
    .ei-hero{margin:4px 0 10px;transition:padding .2s ease}
    .ei-hero-label{font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--ent-accent);font-weight:700;margin-bottom:10px}
    .ei-hero-h1{font-family:var(--ent-serif);font-size:clamp(21px,3.2vw,31px);font-weight:600;color:var(--ent-ink);line-height:1.16;margin:0 0 20px;letter-spacing:-.01em}
    .ei-hero-sub{font-size:13px;color:var(--ent-muted);margin:13px 0 0}
    .ei-search-wrap{position:relative;max-width:600px;margin:0 auto}
    .ei-search-ic{position:absolute;left:18px;top:50%;transform:translateY(-50%);color:var(--ent-faint);font-size:19px;pointer-events:none}
    .ei-search-hero{width:100%;height:60px;padding:0 96px 0 46px;font-size:17px;border:1px solid var(--ent-border);border-radius:14px;background:#fff;color:var(--ent-ink);box-shadow:0 1px 2px rgba(26,23,18,.05)}
    .ei-search-hero:focus{outline:2px solid var(--ent-accent);border-color:var(--ent-accent)}
    .ei-kbd{position:absolute;right:16px;top:50%;transform:translateY(-50%);font-size:11px;color:var(--ent-faint);border:1px solid var(--ent-border);border-radius:6px;padding:2px 7px;background:var(--ent-surface-2)}
    .ei-clear{position:absolute;right:14px;top:50%;transform:translateY(-50%);border:0;background:none;color:var(--ent-muted);font-size:20px;cursor:pointer;line-height:1;padding:2px 6px}
    .ei-suggest{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-top:20px}
    .ei-suggest button{border:1px solid var(--ent-border);background:rgba(255,253,248,.72);border-radius:999px;padding:7px 15px;font-size:12.5px;color:var(--ent-body);cursor:pointer;transition:border-color .12s,color .12s,background .12s}
    .ei-suggest button:hover{border-color:var(--ent-accent);color:var(--ent-ink);background:#fff}
    .ei-searchbar{position:sticky;top:10px;z-index:6;display:flex;align-items:center;gap:12px;padding:11px 15px;margin-bottom:16px}
    .ei-searchbar input{flex:1;border:0;background:none;font-size:15px;color:var(--ent-ink);outline:none;min-width:0}
    /* §Funding workspace (Phase 2) — table-first, full-width, calm surface */
    .efn-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:6px}
    .efn-crumb{font-size:12px;color:var(--ent-muted);margin-bottom:3px}
    .efn-crumb b{color:var(--ent-ink)}
    .efn-h1{font-family:var(--ent-serif);font-size:23px;font-weight:600;color:var(--ent-ink);margin:0 0 3px;letter-spacing:-.01em}
    .efn-sub{font-size:13px;color:var(--ent-muted);max-width:640px}
    .efn-hactions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
    .efn-nums{display:flex;gap:26px;margin:14px 0 4px}
    .efn-num b{font-size:22px;font-weight:700;color:var(--ent-ink);font-family:var(--ent-serif);line-height:1.1}
    .efn-num span{display:block;font-size:11.5px;color:var(--ent-muted);font-weight:600;margin-top:1px}
    .efn-seg{display:inline-flex;border:1px solid var(--ent-border);border-radius:10px;overflow:hidden;background:var(--ent-surface);flex-wrap:wrap;margin:14px 0 2px}
    .efn-seg button{border:0;border-right:1px solid var(--ent-border);background:transparent;padding:8px 14px;font-size:12.5px;font-weight:600;color:var(--ent-muted);cursor:pointer;display:inline-flex;gap:7px;align-items:center}
    .efn-seg button:last-child{border-right:0}
    .efn-seg button.on{background:var(--ent-accent-t);color:var(--ent-accent-d)}
    .efn-seg .efn-c{font-weight:700;color:var(--ent-ink);font-variant-numeric:tabular-nums}
    .efn-seg button.on .efn-c{color:var(--ent-accent-d)}
    .efn-toolbar{position:sticky;top:10px;z-index:6;display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:10px 12px;margin:12px 0 14px;border-radius:11px}
    .efn-toolbar input,.efn-toolbar select{border:1px solid var(--ent-border);border-radius:8px;padding:7px 10px;font-size:13px;background:#fff;color:var(--ent-ink);max-width:170px}
    .efn-toolbar select{cursor:pointer}
    .efn-search{flex:1;min-width:190px;position:relative;display:flex;align-items:center}
    .efn-search span{position:absolute;left:11px;color:var(--ent-faint);font-size:14px;pointer-events:none}
    .efn-search input{width:100%;max-width:none;padding-left:31px}
    .efn-toggle{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--ent-body);cursor:pointer;white-space:nowrap}
    .efn-layout{display:grid;gap:16px;align-items:start}
    .efn-layout.has-drawer{grid-template-columns:minmax(0,1fr) minmax(360px,35%)}
    .efn-tablewrap{border:1px solid var(--ent-border);border-radius:12px;overflow:auto;background:#fff;max-height:calc(100vh - 260px)}
    .efn-table{width:100%;border-collapse:collapse;font-size:13px}
    .efn-table th{position:sticky;top:0;background:var(--ent-surface-2);text-align:left;font-size:10.5px;letter-spacing:.04em;text-transform:uppercase;color:var(--ent-muted);font-weight:700;padding:9px 12px;border-bottom:1px solid var(--ent-border);white-space:nowrap;z-index:1}
    .efn-table th.srt{cursor:pointer;user-select:none}
    .efn-table th.srt:hover{color:var(--ent-ink)}
    .efn-table td{padding:10px 12px;border-bottom:1px solid var(--ent-border-2);vertical-align:top}
    .efn-table tbody tr{cursor:pointer}
    .efn-table tbody tr:hover{background:var(--ent-surface-2)}
    .efn-table tbody tr.sel{background:var(--ent-accent-t)}
    .efn-otitle{font-weight:600;color:var(--ent-ink);line-height:1.3}
    .efn-oref{color:var(--ent-faint);font-size:11px;margin-top:1px}
    .efn-new{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#2F6B34;background:#E4F0E2;border-radius:4px;padding:1px 5px;margin-left:6px;vertical-align:middle}
    .efn-reason{font-size:12px;color:var(--ent-body);max-width:230px;line-height:1.35}
    .efn-stat{display:inline-block;padding:2px 9px;border-radius:6px;font-size:11px;font-weight:700;white-space:nowrap}
    .efn-stat-strong{background:#E4F0E2;color:#2F6B34}.efn-stat-review{background:#F3ECD9;color:#8A5A16}
    .efn-stat-monitoring{background:var(--ent-tint);color:var(--ent-muted)}.efn-stat-missing{background:#EBE5F3;color:#5A3F8A}.efn-stat-ineligible{background:#F6E4DF;color:#9A3B2C}
    .efn-elig{font-size:12px;font-weight:600}
    .efn-elig-confirmed{color:#2F6B34}.efn-elig-appears{color:#3A5A73}.efn-elig-verify{color:#8A5A16}.efn-elig-ineligible{color:#9A3B2C}.efn-elig-not{color:var(--ent-muted)}
    .efn-row-actions{display:flex;gap:4px;opacity:0;transition:opacity .1s;justify-content:flex-end}
    .efn-table tbody tr:hover .efn-row-actions,.efn-table tbody tr.sel .efn-row-actions{opacity:1}
    .efn-iconbtn{border:1px solid var(--ent-border);background:#fff;border-radius:7px;width:26px;height:26px;display:grid;place-items:center;font-size:13px;color:var(--ent-muted);cursor:pointer}
    .efn-iconbtn:hover{border-color:var(--ent-accent);color:var(--ent-accent-d)}
    .efn-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 2px;font-size:12.5px;color:var(--ent-muted)}
    .efn-drawer{position:sticky;top:10px;max-height:calc(100vh - 90px);overflow:auto;border-radius:14px}
    .efn-dwhd{padding:16px 18px 13px;border-bottom:1px solid var(--ent-border)}
    .efn-dwtabs{display:flex;gap:2px;border-bottom:1px solid var(--ent-border);padding:0 12px}
    .efn-dwtab{padding:9px 12px;font-size:12.5px;font-weight:600;color:var(--ent-muted);background:none;border:0;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer}
    .efn-dwtab.on{color:var(--ent-ink);border-bottom-color:var(--ent-accent)}
    .efn-dwbody{padding:16px 18px}
    .efn-elig-row{display:flex;gap:9px;align-items:flex-start;padding:8px 0;border-bottom:1px solid var(--ent-border-2);font-size:12.5px}
    .efn-skl{height:14px;border-radius:5px;background:linear-gradient(90deg,var(--ent-surface-2) 25%,var(--ent-border-2) 50%,var(--ent-surface-2) 75%);background-size:400% 100%;animation:efn-sh 1.2s infinite}
    @keyframes efn-sh{0%{background-position:100% 0}100%{background-position:-100% 0}}
    
    /* ── Phase 6A: full-width decision-first Funding workspace ────────────── */
    .efn-ws{width:100%;max-width:none;min-width:0;padding:0 28px 40px;box-sizing:border-box}
    .efn-head{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;padding:18px 0 14px;flex-wrap:wrap}
    .efn-h1{font-family:var(--ent-serif);font-size:27px;line-height:1.15;margin:0 0 4px;color:var(--ent-ink)}
    .efn-sub{margin:0;font-size:13px;color:var(--ent-muted);max-width:72ch}
    .efn-headacts{display:flex;gap:8px;flex-shrink:0}
    .efn-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:18px;align-items:start;width:100%}
    .efn-layout.has-drawer{grid-template-columns:minmax(0,1fr) minmax(360px,420px)}
    .efn-main{min-width:0}
    .efn-metrics{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:16px}
    .efn-metric{border:1px solid var(--ent-border);border-radius:12px;background:#fff;padding:12px 14px;min-width:0}
    .efn-metric b{display:block;font-family:var(--ent-serif);font-size:25px;line-height:1.1;color:var(--ent-ink)}
    .efn-metric span{display:block;font-size:11.5px;color:var(--ent-muted);margin-top:3px}
    .efn-na{font-family:var(--ent-sans),system-ui;font-size:12.5px;color:var(--ent-muted);font-weight:600}
    .efn-tabs{display:flex;gap:2px;overflow-x:auto;border-bottom:1px solid var(--ent-border);margin-bottom:12px;scrollbar-width:thin}
    .efn-tab{flex:0 0 auto;appearance:none;background:none;border:0;border-bottom:2px solid transparent;padding:9px 13px;font-size:13px;color:var(--ent-muted);cursor:pointer;white-space:nowrap;font-weight:500}
    .efn-tab:hover{color:var(--ent-ink)}
    .efn-tab.on{color:var(--ent-ink);border-bottom-color:var(--ent-ink);font-weight:650}
    .efn-tab:focus{outline:none}
    .efn-tab:focus-visible{outline:2px solid var(--ent-ink);outline-offset:-2px;border-radius:4px}
    .efn-fits{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:12px}
    .efn-fit{appearance:none;border:1px solid var(--ent-border);background:#fff;border-radius:999px;padding:5px 12px;font-size:12.5px;color:var(--ent-ink);cursor:pointer;display:inline-flex;gap:7px;align-items:center}
    .efn-fit:hover{border-color:var(--ent-border-2)}
    .efn-fit.on{background:var(--ent-ink);color:#fff;border-color:var(--ent-ink)}
    .efn-fitn{font-size:11px;opacity:.75;font-variant-numeric:tabular-nums}
    .efn-filters{display:flex;gap:8px;align-items:center;flex-wrap:wrap;padding:10px 12px;border:1px solid var(--ent-border);border-radius:12px;background:#fff;margin-bottom:12px}
    .efn-filters select{padding:7px 9px;border:1px solid var(--ent-border);border-radius:8px;background:#fff;font-size:12.5px;color:var(--ent-ink);max-width:190px}
    .efn-search{position:relative;flex:1 1 260px;min-width:200px;display:flex;align-items:center}
    .efn-search input{width:100%;padding:7px 10px 7px 28px;border:1px solid var(--ent-border);border-radius:8px;font-size:13px;background:#fff}
    .efn-searchi{position:absolute;left:9px;color:var(--ent-muted);font-size:13px}
    .efn-filtersp{flex:1 1 auto}
    .efn-chk{display:inline-flex;gap:6px;align-items:center;font-size:12.5px;color:var(--ent-muted);white-space:nowrap}
    .efn-sortnote{font-size:12px;color:var(--ent-muted);margin:0 0 10px;padding:0 2px}
    .efn-sortnote b{color:var(--ent-ink)}
    .efn-type{font-size:12px;color:var(--ent-ink);white-space:nowrap}
    .efn-reason{font-size:12.5px;color:var(--ent-muted);max-width:34ch}
    .efn-rec{display:inline-block;padding:3px 9px;border-radius:999px;font-size:11.5px;font-weight:650;white-space:nowrap}
    .efn-rec-best{background:#e7f2ea;color:#1d5c33}
    .efn-rec-good{background:#eaf0f8;color:#24456f}
    .efn-rec-review{background:#fbf1e0;color:#7a5312}
    .efn-rec-info{background:var(--ent-surface-2);color:var(--ent-muted)}
    .efn-rec-no{background:#f6e9e9;color:#8a2f2f}
    .efn-sortable{cursor:pointer;user-select:none}
    .efn-empty{border:1px solid var(--ent-border);border-radius:12px;background:#fff;padding:26px 24px;text-align:left}
    .efn-empty h3{margin:0 0 6px;font-family:var(--ent-serif);font-size:18px;color:var(--ent-ink)}
    .efn-empty p{margin:0 0 14px;font-size:13px;color:var(--ent-muted);max-width:76ch}
    .efn-alts{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px}
    .efn-altcard{appearance:none;text-align:left;border:1px solid var(--ent-border);border-radius:11px;background:var(--ent-surface-2);padding:11px 15px;cursor:pointer;min-width:140px}
    .efn-altcard b{display:block;font-family:var(--ent-serif);font-size:21px;color:var(--ent-ink)}
    .efn-altcard span{display:block;font-size:12px;color:var(--ent-muted);margin-top:2px}
    .efn-emptyacts{display:flex;gap:8px;flex-wrap:wrap}
    /* preview */
    .efn-drawer{position:sticky;top:16px;border:1px solid var(--ent-border);border-radius:14px;background:#fff;overflow:hidden;max-height:calc(100vh - 96px);display:flex;flex-direction:column}
    .efn-dwhead{display:flex;gap:12px;align-items:flex-start;justify-content:space-between;padding:16px 18px 12px;border-bottom:1px solid var(--ent-border-2)}
    .efn-dweyebrow{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ent-muted);margin-bottom:5px}
    .efn-dwtitle{margin:0 0 5px;font-family:var(--ent-serif);font-size:18px;line-height:1.25;color:var(--ent-ink)}
    .efn-dwmeta{font-size:12px;color:var(--ent-muted)}
    .efn-decision{margin:14px 18px 0;padding:13px 15px;border-radius:11px;border:1px solid transparent}
    .efn-decision-l{font-size:11.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;margin-bottom:5px}
    .efn-decision-p{margin:0;font-size:13px;line-height:1.5}
    .efn-decision-best{background:#e7f2ea;border-color:#c6e0cf;color:#1d5c33}
    .efn-decision-good{background:#eaf0f8;border-color:#ccdcf0;color:#24456f}
    .efn-decision-review{background:#fbf1e0;border-color:#ecd9b4;color:#7a5312}
    .efn-decision-info{background:var(--ent-surface-2);border-color:var(--ent-border);color:var(--ent-muted)}
    .efn-decision-no{background:#f6e9e9;border-color:#e6cccc;color:#8a2f2f}
    .efn-dwactions{display:flex;gap:8px;flex-wrap:wrap;padding:13px 18px}
    .efn-dwtabs{display:flex;gap:2px;padding:0 18px;border-bottom:1px solid var(--ent-border-2)}
    .efn-dwtab{appearance:none;background:none;border:0;border-bottom:2px solid transparent;padding:9px 10px;font-size:12.5px;color:var(--ent-muted);cursor:pointer}
    .efn-dwtab.on{color:var(--ent-ink);border-bottom-color:var(--ent-ink);font-weight:650}
    .efn-dwbody{padding:16px 18px 22px;overflow:auto}
    .efn-dwsec{margin-bottom:15px}
    .efn-dwsec-t{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--ent-muted);margin-bottom:5px}
    .efn-p{margin:0;font-size:13px;line-height:1.55;color:var(--ent-ink)}
    .efn-ul{margin:0;padding-left:17px;font-size:13px;line-height:1.55;color:var(--ent-ink)}
    .efn-eligtop{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:14px}
    .efn-eligk{display:block;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ent-muted);margin-bottom:2px}
    .efn-eligtbl{width:100%;border-collapse:collapse;font-size:12.5px}
    .efn-eligtbl th{text-align:left;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ent-muted);padding:6px 8px 6px 0;border-bottom:1px solid var(--ent-border-2)}
    .efn-eligtbl td{padding:7px 8px 7px 0;border-bottom:1px solid var(--ent-border-2);vertical-align:top}
    .efn-verdict{display:inline-block;padding:2px 7px;border-radius:999px;font-size:11px;text-transform:capitalize}
    .efn-verdict-strong{background:#e7f2ea;color:#1d5c33}
    .efn-verdict-weak{background:#fbf1e0;color:#7a5312}
    .efn-verdict-unclear{background:var(--ent-surface-2);color:var(--ent-muted)}
    .efn-verdict-ineligible{background:#f6e9e9;color:#8a2f2f}
    @media (max-width: 1180px){ .efn-layout.has-drawer{grid-template-columns:minmax(0,1fr)} .efn-metrics{grid-template-columns:repeat(2,minmax(0,1fr))} }
    @media (max-width: 900px){
      .efn-ws{padding:0 16px 32px}
      .efn-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}
      .efn-filters{position:sticky;top:0;z-index:5}
      .efn-drawer{position:fixed;inset:0;max-height:none;border-radius:0;z-index:60}
      .efn-eligtop{grid-template-columns:1fr}
      .efn-headacts{width:100%}
      .efn-tab,.efn-fit,.ent-btn--sm{min-height:44px}
    }

    
    /* ══ Conversational Mira Funding ══════════════════════════════════════ */
    /* Orange is an accent only — Mira mark, composer border, send, starters,
       shortlist icon. Light blue carries the conversation. */
    .mfx-page{--mfx-o:#FF6600;--mfx-o-h:#E85C00;--mfx-o-s:rgba(255,102,0,.32);--mfx-o-bd:rgba(255,102,0,.28);--mfx-o-soft:#FFF4EA;--mfx-o-w:#FFF2E8;--mfx-blue:#EAF4FF;
      --mfx-blue-b:#D6E9FF;--mfx-blue-b2:#C9E2FF;--mfx-ink:#22201D;--mfx-mut:#6E6862;
      --mfx-sans:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
      display:flex;flex-direction:column;height:calc(100vh - 56px);width:100%;min-width:0;
      background:#fff;font-family:var(--mfx-sans);overflow:hidden;position:relative}
    .mfx-head{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;
      padding:18px 26px 14px;border-bottom:2px solid var(--mfx-o-bd);flex:0 0 auto;flex-wrap:wrap}
    .mfx-h1{font-family:var(--mfx-sans);font-size:28px;font-weight:680;letter-spacing:-.01em;margin:0 0 3px;color:var(--mfx-ink);line-height:1.15}
    .mfx-sub{margin:0;font-size:13px;color:var(--mfx-mut);max-width:70ch}
    .mfx-head-r{display:flex;gap:8px;align-items:center;flex-shrink:0}
    .mfx-btn{font-family:var(--mfx-sans);font-size:12.5px;color:var(--mfx-ink);background:#fff;
      border:1px solid #E2DCD2;border-radius:9px;padding:7px 12px;cursor:pointer;display:inline-flex;gap:6px;align-items:center;min-height:34px}
    .mfx-btn:hover{border-color:var(--mfx-o-s);background:var(--mfx-o-w)}
    .mfx-btn.on{border-color:var(--mfx-o);background:var(--mfx-o-w)}
    .mfx-btn:focus{outline:none}.mfx-btn:focus-visible{outline:2px solid var(--mfx-o);outline-offset:1px}
    .mfx-badge{background:var(--mfx-o);color:#fff;border-radius:999px;font-size:10.5px;padding:1px 6px;line-height:1.6}
    .mfx-only-narrow{display:none}
    .mfx-ws{flex:1 1 auto;display:grid;grid-template-columns:minmax(0,1fr) 316px;gap:0;min-height:0}
    .mfx-page.rail-collapsed .mfx-ws{grid-template-columns:minmax(0,1fr)}
    .mfx-conv{display:flex;flex-direction:column;min-width:0;min-height:0;position:relative}
    .mfx-scroll{flex:1 1 auto;overflow-y:auto;overflow-x:hidden;padding:18px 26px 8px}
    .mfx-msgs{max-width:900px;margin:0 auto;display:flex;flex-direction:column;gap:18px;padding-bottom:8px}
    /* empty state — deliberately not tall, so the composer never needs scrolling to reach */
    .mfx-empty{max-width:660px;margin:0 auto;text-align:center;padding:26px 0 8px}
    .mfx-empty-mark{width:66px;height:66px;font-size:30px !important;box-shadow:0 0 0 10px rgba(255,102,0,.06);border-radius:16px;margin:0 auto 16px;display:flex;align-items:center;
      justify-content:center;font-size:24px;color:var(--mfx-o);background:var(--mfx-o-w);border:1px solid var(--mfx-o-s)}
    .mfx-empty-h{font-family:var(--mfx-sans);font-size:22px;font-weight:660;line-height:1.3;color:var(--mfx-ink);margin:0 0 8px}
    .mfx-empty-p{font-size:13.5px;line-height:1.6;color:var(--mfx-mut);margin:0 auto 22px;max-width:52ch}
    .mfx-starters{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;text-align:left}
    .mfx-starter{display:flex;align-items:center;justify-content:space-between;gap:10px;background:#fff;
      border:1px solid var(--mfx-o-s);border-radius:11px;padding:12px 14px;font-size:13px;color:var(--mfx-ink);
      font-family:var(--mfx-sans);cursor:pointer;text-align:left;min-height:44px;transition:background .12s,border-color .12s}
    .mfx-starter:hover{background:var(--mfx-o-w);border-color:var(--mfx-o)}
    .mfx-starter:focus{outline:none}.mfx-starter:focus-visible{outline:2px solid var(--mfx-o);outline-offset:1px}
    .mfx-starter-i{color:var(--mfx-o);font-size:14px;flex-shrink:0}
    /* messages */
    .mfx-row{display:flex}
    .mfx-row-user{justify-content:flex-end}
    .mfx-row-mira{justify-content:flex-start}
    .mfx-msg{max-width:68%;font-size:14px;line-height:1.6;color:var(--mfx-ink);font-family:var(--mfx-sans);
      padding:11px 14px;border-radius:12px;word-wrap:break-word;overflow-wrap:anywhere}
    .mfx-msg-user{background:var(--mfx-blue);border:1px solid var(--mfx-blue-b)}
    .mfx-msg-mira{background:transparent;border:1px solid var(--mfx-blue-b2);max-width:800px;white-space:pre-wrap}
    .mfx-msg-head{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;letter-spacing:.05em;
      text-transform:uppercase;color:var(--mfx-mut);margin-bottom:6px}
    .mfx-mark-sm{color:var(--mfx-o);font-size:12px}
    .mfx-result-slot:empty{display:none}
    .mfx-result-slot{margin-top:12px}
    .mfx-typing{display:flex;gap:5px;align-items:center;padding:14px}
    .mfx-typing span{width:6px;height:6px;border-radius:50%;background:var(--mfx-o-s);animation:mfxb 1s infinite}
    .mfx-typing span:nth-child(2){animation-delay:.15s}.mfx-typing span:nth-child(3){animation-delay:.3s}
    @keyframes mfxb{0%,60%,100%{opacity:.35}30%{opacity:1}}
    /* notice + composer */
    .mfx-notice{max-width:900px;margin:0 auto;padding:0 26px 8px;font-size:12px;color:var(--mfx-mut);display:flex;gap:7px;align-items:center}
    .mfx-notice-d{width:5px;height:5px;border-radius:50%;background:var(--mfx-o-s);flex-shrink:0}
    .mfx-composer-wrap{flex:0 0 auto;padding:6px 26px calc(14px + env(safe-area-inset-bottom,0px));background:#fff}
    .mfx-composer{max-width:900px;margin:0 auto;display:flex;align-items:flex-end;gap:12px;background:#fff;
      border:2px solid var(--mfx-o-bd);border-radius:28px;padding:9px 10px 9px 18px;min-height:56px;box-sizing:border-box;transition:border-color .12s,box-shadow .12s}
    .mfx-composer:focus-within{border-color:var(--mfx-o);box-shadow:0 0 0 3px rgba(255,102,0,.12)}
    .mfx-composer.busy{opacity:.75}
    .mfx-comp-mark{color:var(--mfx-o);font-size:15px;line-height:1;padding-bottom:9px;flex-shrink:0}
    .mfx-comp-input{flex:1 1 auto;border:0;outline:none;resize:none;font-family:var(--mfx-sans);font-size:14px;
      line-height:1.5;color:var(--mfx-ink);background:transparent;padding:8px 0;max-height:168px;min-height:22px}
    .mfx-comp-input::placeholder{color:#9A938B}
    .mfx-send{width:38px;height:38px;min-width:38px;border-radius:50%;border:0;background:var(--mfx-o);color:#fff;
      font-size:15px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0}
    .mfx-send:hover:not(:disabled){background:var(--mfx-o-h)}
    .mfx-send:disabled{background:#FFD2B0;color:#fff;cursor:not-allowed}
    .mfx-send:focus{outline:none}.mfx-send:focus-visible{outline:2px solid var(--mfx-o);outline-offset:2px}
    .mfx-spin{width:13px;height:13px;border:2px solid rgba(255,255,255,.45);border-top-color:#fff;border-radius:50%;animation:mfxs .7s linear infinite}
    @keyframes mfxs{to{transform:rotate(360deg)}}
    .mfx-disclaimer{max-width:900px;margin:8px auto 0;text-align:center;font-size:11.5px;color:#9A938B}
    /* history popover — compact, never a second sidebar */
    .mfx-hist{position:absolute;right:26px;top:74px;z-index:40;width:300px;background:#fff;border:1px solid #E2DCD2;
      border-radius:13px;box-shadow:0 8px 24px rgba(30,25,20,.10);padding:12px;max-height:60vh;overflow:auto}
    .mfx-hist-h{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--mfx-mut);margin-bottom:8px}
    .mfx-hist-empty{font-size:12.5px;color:var(--mfx-mut);margin:0}
    .mfx-hist-l{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
    .mfx-hist-i{display:flex;align-items:center;gap:2px;border-radius:8px}
    .mfx-hist-i.on{background:var(--mfx-blue)}
    .mfx-hist-i:hover{background:#F7F4EF}
    .mfx-hist-open{flex:1 1 auto;text-align:left;background:none;border:0;cursor:pointer;padding:7px 8px;font-family:var(--mfx-sans);min-width:0}
    .mfx-hist-t{display:block;font-size:13px;color:var(--mfx-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .mfx-hist-n{display:block;font-size:11px;color:var(--mfx-mut);margin-top:1px}
    .mfx-hist-x{background:none;border:0;cursor:pointer;color:var(--mfx-mut);font-size:13px;padding:6px 7px;border-radius:6px;min-width:30px;min-height:30px}
    .mfx-hist-x:hover{color:var(--mfx-ink);background:#EFEAE1}
    /* shortlist rail */
    .mfx-rail{border-left:1px solid var(--mfx-o-bd);background:#FFFDFB;padding:0 0 20px;overflow-y:auto;min-width:0}
    .mfx-rail-h{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:13px 16px 9px;background:var(--mfx-o-soft);border-bottom:1px solid var(--mfx-o-bd)}
    .mfx-rail-t{display:flex;align-items:center;gap:7px;font-size:13.5px;font-weight:650;color:var(--mfx-ink)}
    .mfx-rail-i{color:var(--mfx-o)}
    .mfx-rail-x{background:none;border:0;cursor:pointer;color:var(--mfx-mut);font-size:15px;padding:5px 8px;border-radius:7px;min-width:32px;min-height:32px}
    .mfx-rail-x:hover{background:#F3EFE8;color:var(--mfx-ink)}
    .mfx-rail-count{font-size:12.5px;color:var(--mfx-o);font-weight:650;padding:11px 16px 10px}
    .mfx-rail-empty{font-size:12.5px;line-height:1.55;color:var(--mfx-mut);margin:0 16px 10px}
    .mfx-rail-sub{font-size:11.5px;line-height:1.5;color:#9A938B;margin:0 16px}
    .mfx-rail-l{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
    .mfx-rail-item{font-size:12.5px;color:var(--mfx-ink);border:1px solid #EFEAE1;border-radius:9px;padding:9px 11px}
    .mfx-rail-tab{position:absolute;right:0;top:16px;z-index:20;background:#fff;border:1px solid #EFEAE1;border-right:0;
      border-radius:10px 0 0 10px;padding:10px 9px;cursor:pointer;color:var(--mfx-o);font-size:14px;min-height:44px}
    .mfx-sheet-back{display:none}
    /* tablet */
    @media (max-width:1180px){
      .mfx-ws{grid-template-columns:minmax(0,1fr)}
      .mfx-rail{display:none}
      .mfx-rail-tab{display:none}
      .mfx-only-narrow{display:inline-flex}
      .mfx-starters{grid-template-columns:repeat(2,minmax(0,1fr))}
    }
    /* mobile */
    @media (max-width:760px){
      .mfx-page{height:calc(100vh - 52px)}
      .mfx-head{padding:14px 16px 12px}
      .mfx-h1{font-size:21px}
      .mfx-scroll{padding:16px 16px 6px}
      .mfx-composer-wrap{padding:6px 16px calc(12px + env(safe-area-inset-bottom,0px))}
      .mfx-notice{padding:0 16px 8px}
      .mfx-starters{grid-template-columns:1fr}
      .mfx-msg{max-width:92%}
      .mfx-msg-mira{max-width:96%}
      .mfx-hist{right:12px;left:12px;width:auto;top:66px}
      .mfx-profile{padding:9px 16px;overflow-x:auto}
      .mfx-chips{flex-wrap:nowrap}
      .mfx-refines{padding:0 16px 8px}
      .mfx-msg{max-width:92%!important}
      .mfx-btn{min-height:44px}
      /* shortlist becomes a bottom sheet */
      .mfx-rail.sheet{display:block;position:fixed;left:0;right:0;bottom:0;z-index:70;max-height:62vh;
        border:1px solid #EFEAE1;border-radius:16px 16px 0 0;box-shadow:0 -8px 28px rgba(30,25,20,.14);
        padding-bottom:calc(20px + env(safe-area-inset-bottom,0px))}
      .mfx-sheet-back{display:block;position:fixed;inset:0;z-index:65;background:rgba(26,23,18,.32)}
    }

    
    /* Full-bleed: the shell's centred 1360px card framed Funding in cream.
       Same escape-hatch pattern already used for .ei-wide. */
    .ent-content:has(.mfx-page){max-width:none !important;padding:0 !important;margin:0 !important;background:#fff}
    .mfx-profile{display:flex;align-items:flex-start;gap:11px;margin:0;padding:11px 26px;
      background:var(--mfx-o-soft);border-bottom:1px solid var(--mfx-o-bd)}
    .mfx-profile-mark{color:var(--mfx-o);font-size:14px;line-height:1.5;flex-shrink:0}
    .mfx-profile-body{flex:1 1 auto;min-width:0}
    .mfx-profile-t{font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
      color:#A85416;margin-bottom:7px}
    .mfx-chips{display:flex;flex-wrap:wrap;gap:6px}
    .mfx-chip{font-size:12px;padding:3px 10px;border-radius:999px;background:#fff;color:var(--mfx-ink);white-space:nowrap;cursor:default}
    .mfx-chip-confirmed{border:1px solid var(--mfx-o-bd)}
    .mfx-chip-inferred{border:1px dashed rgba(255,102,0,.42);color:#6E6862}
    .mfx-profile-edit{flex-shrink:0;background:none;border:0;color:var(--mfx-o);font-size:12.5px;
      font-family:var(--mfx-sans);cursor:pointer;padding:4px 6px;border-radius:7px;min-height:32px}
    .mfx-profile-edit:hover{background:rgba(255,102,0,.09)}
    /* refinement chips above the composer */
    .mfx-refines{display:flex;flex-wrap:wrap;gap:7px;max-width:900px;margin:0 auto;padding:0 26px 8px}
    .mfx-refine{font-size:12px;padding:6px 12px;border-radius:999px;background:#fff;color:var(--mfx-ink);
      border:1px solid var(--mfx-o-bd);cursor:pointer;font-family:var(--mfx-sans);min-height:32px}
    .mfx-refine:hover{background:var(--mfx-o-soft);border-color:var(--mfx-o)}
    .mfx-signin{max-width:900px;margin:5px auto 0;text-align:center;font-size:11px;color:#A39A90}

    
    /* live result cards */
    .mfx-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:10px;margin-top:12px}
    .mfx-card{border:1px solid #E7E1D9;border-radius:12px;padding:12px 13px;background:#fff;min-width:0}
    .mfx-card:hover{border-color:var(--mfx-o-bd)}
    .mfx-card-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:7px}
    .mfx-card-dl{font-size:11.5px;color:var(--mfx-mut);white-space:nowrap}
    .mfx-card-t{font-size:15px;font-weight:600;line-height:1.35;color:var(--mfx-ink);margin-bottom:5px}
    .mfx-card-m{font-size:12.5px;color:var(--mfx-mut);line-height:1.5}
    .mfx-card-why{font-size:12.5px;color:var(--mfx-ink);margin-top:8px;line-height:1.5}
    .mfx-card-con{font-size:12.5px;color:#8A5A16;margin-top:4px;line-height:1.5}
    .mfx-card-acts{display:flex;gap:7px;margin-top:11px}
    .mfx-card-btn{flex:1 1 auto;font-family:var(--mfx-sans);font-size:12.5px;padding:8px 10px;border-radius:9px;
      border:1px solid #E2DCD2;background:#fff;color:var(--mfx-ink);cursor:pointer;min-height:36px}
    .mfx-card-btn:hover{border-color:var(--mfx-o);background:var(--mfx-o-soft)}
    .mfx-card-btn.primary{background:var(--mfx-o);border-color:var(--mfx-o);color:#fff}
    .mfx-card-btn.primary:hover{background:var(--mfx-o-h)}
    .mfx-card-btn.saved{background:var(--mfx-o-soft);border-color:var(--mfx-o-bd);color:var(--mfx-o);cursor:default}
    .mfx-band{font-size:10.5px;font-weight:700;letter-spacing:.03em;padding:3px 8px;border-radius:999px;white-space:nowrap}
    .mfx-band-strong-fit{background:#E7F2EA;color:#1D5C33}
    .mfx-band-good-fit{background:var(--mfx-o-soft);color:#A85416}
    .mfx-band-review{background:#FBF1E0;color:#7A5312}
    .mfx-band-needs-information{background:#F1EFEC;color:#6E6862}
    .mfx-fail{margin-top:10px}
    .mfx-typing-l{font-size:12px;color:var(--mfx-mut);font-style:normal;margin-left:6px}
    .mfx-rail-it{font-size:13px;font-weight:600;line-height:1.35;color:var(--mfx-ink)}
    .mfx-rail-im{font-size:11.5px;color:var(--mfx-mut);margin:3px 0 6px}
    .mfx-rail-ib{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
    .mfx-rail-stage{font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--mfx-mut)}
    /* ── review modal ───────────────────────────────────────────────────────
       Replaces the 460px right drawer. A grant decision needs the eligibility
       evidence, the unresolved checks and the application route side by side;
       a narrow rail forced all of that into a single scrolling column. */
    .mfx-mod-back{position:fixed;inset:0;background:rgba(26,23,18,.42);z-index:80;
      display:flex;align-items:center;justify-content:center;padding:24px}
    .mfx-mod{position:relative;width:min(880px,calc(100vw - 48px));max-height:calc(100vh - 48px);
      background:#fff;border:1px solid #EDE7DE;border-radius:22px;z-index:81;
      box-shadow:0 18px 48px rgba(26,23,18,.16);display:flex;flex-direction:column;
      overflow:hidden;font-family:var(--mfx-sans)}
    .mfx-mod-h{padding:22px 26px 16px;border-bottom:1px solid #F1EDE6;flex-shrink:0}
    .mfx-mod-htop{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
    .mfx-mod-t{font-size:21px;font-weight:650;line-height:1.28;margin:10px 0 5px;color:var(--mfx-ink)}
    .mfx-mod-f{font-size:13.5px;color:var(--mfx-mut);margin:0}
    .mfx-mod-desc{margin:11px 0 0;font-size:13.5px;line-height:1.55;color:#5A5348}
    .mfx-mod-desc.none{color:var(--mfx-mut);font-style:italic}
    .mfx-mod-x{border:0;background:#F6F3EE;border-radius:9px;width:34px;height:34px;
      font-size:19px;line-height:1;color:var(--mfx-mut);cursor:pointer;flex-shrink:0}
    .mfx-mod-x:hover{background:#EDE7DE;color:var(--mfx-ink)}
    .mfx-mod-body{padding:20px 26px 24px;overflow-y:auto;flex:1 1 auto}
    /* key information grid — two rows of three */
    .mfx-kg{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;
      background:#F1EDE6;border:1px solid #F1EDE6;border-radius:14px;overflow:hidden;margin-bottom:20px}
    .mfx-kg-i{background:#fff;padding:13px 15px;min-width:0}
    .mfx-kg-l{display:flex;align-items:center;gap:6px;font-size:10.5px;text-transform:uppercase;
      letter-spacing:.05em;color:var(--mfx-mut);margin-bottom:5px}
    .mfx-kg-v{font-size:13.5px;font-weight:550;color:var(--mfx-ink);line-height:1.4;word-break:break-word}
    .mfx-kg-v.unk{font-weight:450;color:var(--mfx-mut)}
    /* three decision panels */
    .mfx-pan{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-bottom:20px}
    .mfx-pan-i{border:1px solid #EDE7DE;border-radius:14px;padding:14px 15px;background:#FDFCFA;min-width:0}
    .mfx-pan-i.check{background:#FFF1E6;border-color:rgba(255,102,0,.22)}
    .mfx-pan-h{font-size:11px;text-transform:uppercase;letter-spacing:.05em;
      color:var(--mfx-mut);margin:0 0 9px;font-weight:600}
    .mfx-pan-l{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:7px}
    .mfx-pan-l li{font-size:12.5px;line-height:1.5;color:var(--mfx-ink);
      padding-left:14px;position:relative}
    .mfx-pan-l li::before{content:'';position:absolute;left:0;top:7px;width:5px;height:5px;
      border-radius:50%;background:var(--mfx-o)}
    .mfx-pan-i.check .mfx-pan-l li::before{background:#C97A28}
    .mfx-pan-none{font-size:12.5px;color:var(--mfx-mut);margin:0;line-height:1.5}
    /* expandable evidence sections */
    .mfx-ex{border-top:1px solid #F1EDE6}
    .mfx-ex summary{padding:13px 2px;font-size:13.5px;font-weight:550;color:var(--mfx-ink);
      cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between}
    .mfx-ex summary::-webkit-details-marker{display:none}
    .mfx-ex summary::after{content:'+';color:var(--mfx-mut);font-size:16px}
    .mfx-ex[open] summary::after{content:'–'}
    .mfx-ex-b{padding:2px 0 16px}
    .mfx-er{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px 14px;
      padding:9px 0;border-bottom:1px solid #F6F3EE;font-size:12.5px;align-items:start}
    .mfx-er:last-child{border-bottom:0}
    .mfx-er-k{color:var(--mfx-ink)}
    .mfx-er-e{grid-column:1/-1;font-size:11.5px;color:var(--mfx-mut);line-height:1.5;margin-top:-4px}
    .mfx-st{font-size:10.5px;font-weight:600;padding:3px 8px;border-radius:20px;white-space:nowrap}
    .mfx-st-ok{background:#E8F3EA;color:#2E6B3A}
    .mfx-st-maybe{background:#FFF1E6;color:#A85A15}
    .mfx-st-need{background:#F6F3EE;color:#6B6459}
    .mfx-st-no{background:#F7E7E4;color:#9A3B2C}
    /* sticky action bar */
    .mfx-mod-foot{padding:14px 26px;border-top:1px solid #F1EDE6;background:#fff;
      display:flex;align-items:center;gap:10px;flex-wrap:wrap;flex-shrink:0}
    .mfx-cta{display:inline-flex;align-items:center;gap:7px;background:#FF6600;color:#fff;
      border:0;border-radius:11px;padding:0 18px;height:44px;font-size:14px;font-weight:600;
      cursor:pointer;text-decoration:none;font-family:inherit}
    .mfx-cta:hover{background:#E85C00}
    .mfx-cta[aria-disabled="true"],.mfx-cta:disabled{background:#F1EDE6;color:#9A9287;cursor:not-allowed}
    .mfx-cta2{display:inline-flex;align-items:center;gap:7px;background:#FFF1E6;color:#A34C00;
      border:1px solid rgba(255,102,0,.35);border-radius:11px;padding:0 16px;height:44px;
      font-size:13.5px;font-weight:550;cursor:pointer;font-family:inherit}
    .mfx-cta2:hover{background:#FFE6D4}
    .mfx-cta3{background:#fff;border:1px solid #E6E0D7;border-radius:11px;padding:0 15px;height:44px;
      font-size:13.5px;color:var(--mfx-ink);cursor:pointer;font-family:inherit}
    .mfx-cta3:hover{background:#FAF8F5}
    .mfx-cta3.on{background:#F6F3EE;color:var(--mfx-mut);cursor:default}
    .mfx-foot-sp{margin-left:auto}
    .mfx-unver{font-size:12px;color:var(--mfx-mut);line-height:1.45}
    /* Mira support menu */
    .mfx-sup{position:absolute;left:26px;right:26px;bottom:74px;background:#fff;
      border:1px solid #EDE7DE;border-radius:16px;box-shadow:0 12px 32px rgba(26,23,18,.14);
      padding:8px;z-index:3;max-height:52vh;overflow-y:auto}
    .mfx-sup-n{font-size:11.5px;color:var(--mfx-mut);padding:8px 12px 10px;line-height:1.5}
    .mfx-sup-b{display:block;width:100%;text-align:left;background:none;border:0;border-radius:10px;
      padding:11px 12px;font-size:13.5px;color:var(--mfx-ink);cursor:pointer;font-family:inherit;min-height:44px}
    .mfx-sup-b:hover{background:#FFF1E6}
    .mfx-sup-b small{display:block;font-size:11.5px;color:var(--mfx-mut);margin-top:2px}
    /* skeleton */
    .mfx-sk{height:13px;border-radius:6px;background:linear-gradient(90deg,#F4F1EC,#EDE8E0,#F4F1EC);
      background-size:200% 100%;animation:mfxsk 1.2s ease-in-out infinite;margin-bottom:9px}
    @keyframes mfxsk{0%{background-position:200% 0}100%{background-position:-200% 0}}
    @media (max-width:1024px){
      .mfx-mod{width:88vw}
      .mfx-pan{grid-template-columns:1fr}
    }
    @media (max-width:760px){
      .mfx-mod-back{padding:14px}
      .mfx-mod{width:calc(100vw - 28px);max-height:calc(100vh - 28px);border-radius:18px}
      .mfx-mod-h{padding:18px 18px 14px}
      .mfx-mod-body{padding:16px 18px 20px}
      .mfx-mod-foot{padding:12px 18px}
      .mfx-kg{grid-template-columns:1fr 1fr}
      .mfx-sup{left:18px;right:18px}
      .mfx-cta,.mfx-cta2{flex:1 1 100%;justify-content:center}
      .mfx-foot-sp{margin-left:0}
      .mfx-cards{grid-template-columns:1fr}
    }

    
    .mfx-cmp{list-style:decimal;margin:12px 0 0;padding-left:20px;display:flex;flex-direction:column;gap:9px}
    .mfx-cmp-i{font-size:13px;color:var(--mfx-ink)}
    .mfx-cmp-t{font-weight:600;line-height:1.35}
    .mfx-cmp-m{font-size:12px;color:var(--mfx-mut);margin-top:3px;display:flex;gap:6px;align-items:center;flex-wrap:wrap}
    .mfx-superseded{font-size:12px;color:#9A938B;padding:6px 0;font-style:italic}
    /* Suggested next actions. Chips SUGGEST — clicking is the user's approval,
       and it is the only thing that widens a geography. */
    .mfx-chip-acts{display:flex;flex-wrap:wrap;gap:7px;margin-top:11px}
    .mfx-chip-act{font-size:12.5px;font-weight:600;color:var(--mfx-ink);background:transparent;
      border:1px solid var(--ent-border);border-radius:999px;padding:6px 12px;cursor:pointer}
    .mfx-chip-act:hover{border-color:var(--ent-accent);background:var(--ent-tint,#F4F1EA)}
    .mfx-basis{font-size:11.5px;color:var(--mfx-mut);margin-top:8px;font-style:italic}
    .mfx-diag{margin-top:10px;font-size:11px;color:var(--mfx-mut);border-top:1px dashed var(--ent-border);padding-top:6px}
    .mfx-diag summary{cursor:pointer;font-family:ui-monospace,Menlo,monospace}
    .mfx-diag-b{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:6px;font-family:ui-monospace,Menlo,monospace}
    .mfx-diag-i b{font-weight:600;color:var(--mfx-ink)}

    .efn-cards{display:none}
    .efn-card{border:1px solid var(--ent-border);border-radius:11px;background:#fff;padding:12px 14px;margin-bottom:10px;cursor:pointer}
    .efn-card-t{font-weight:600;color:var(--ent-ink);font-size:14px}
    .efn-card-m{font-size:12px;color:var(--ent-muted);margin:3px 0 6px}
    @media(max-width:900px){
      .efn-layout.has-drawer{grid-template-columns:1fr}
      .efn-tablewrap{display:none}
      .efn-cards{display:block}
      .efn-drawer{position:fixed;inset:auto 0 0 0;top:auto;max-height:86vh;border-radius:16px 16px 0 0;z-index:50;box-shadow:0 -8px 30px rgba(26,23,18,.18)}
    }`;
    document.head.appendChild(s);
  }

  function go(h2) { window.location.hash = h2; }
  function daysTo(iso) { return iso ? Math.round((new Date(iso) - Date.now()) / 86400000) : null; }
  function badge(cat) { var m = { 'Strong candidate': 'ei-b-strong', 'Worth reviewing': 'ei-b-review', 'Monitoring': 'ei-b-mon', 'Not eligible': 'ei-b-nel', 'Insufficient information': 'ei-b-info', 'Probably not suitable': 'ei-b-mon' }; return h('span', { className: 'ei-b ' + (m[cat] || 'ei-b-mon') }, cat); }
  function money(o) { var mx = MX(); return o.analyticalGBP != null ? mx.money.fmtGBP(o.analyticalGBP) : (o.amountType === 'total_programme_budget' ? mx.money.fmtGBP(mx.money.toGBP(o.totalBudget, o.currency)) + ' envelope' : 'Amount not published'); }

  function Head(p) {
    return h('div', { className: 'ent-page-hd' },
      h('div', { className: 'ent-page-hd-main' },
        p.eyebrow ? h('div', { style: { fontSize: 11, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--ent-accent)', fontWeight: 700 } }, p.eyebrow) : null,
        h('h1', { className: 'ent-page-title' }, p.title),
        p.sub ? h('p', { className: 'ent-page-sub' }, p.sub) : null),
      p.actions ? h('div', { className: 'ent-page-actions' }, p.actions) : null);
  }
  function Coverage(p) { var c = MX().coverage(p.opps); return h('div', { className: 'ei-cov' }, h('b', null, c.records), ' opportunities · ', h('b', null, c.sources || 1), ' source(s) · amounts for ', h('b', null, c.amountPct + '%'), ' · eligibility for ', h('b', null, c.eligPct + '%'), c.amountPct < 60 ? h('div', { className: 'ent-muted', style: { marginTop: 3 } }, 'Amount coverage limited — many records show “Amount not published”.') : null); }

  function OppRow(p) {
    var o = p.opp, m = p.m, d = daysTo(o.deadline);
    return h('div', { className: 'ei-row', onClick: function () { go('#/enterprise/opportunity/' + o.id); } },
      h('div', null,
        h('div', { className: 'ei-title' }, o.title),
        h('div', { className: 'ei-meta' }, o.funder + ' · ' + o.type + ' · ' + (o.countries[0] || o.regions[0] || 'Global') + ' · ' + money(o) + (d != null ? ' · closes in ' + d + 'd' : '')),
        m.reasons[0] ? h('div', { className: 'ei-reason' }, '✓ ' + m.reasons[0]) : null,
        m.concerns[0] ? h('div', { className: 'ei-concern' }, '△ ' + m.concerns[0]) : null),
      h('div', { style: { textAlign: 'right' } }, badge(m.overall)));
  }
  function Grouped(p) {
    var mx = MX(), org = mx.org();
    var scored = p.opps.map(function (o) { return { o: o, m: mx.match(o, org) }; });
    var col = React.useState({}); var collapsed = col[0], setCol = col[1];
    var groups = {}; mx.ORDER.forEach(function (g) { groups[g] = []; });
    scored.forEach(function (s) { (groups[s.m.overall] = groups[s.m.overall] || []).push(s); });
    if (!scored.length) return h('div', { className: 'ei-empty' }, h('h3', null, 'No opportunities match'), h('p', null, 'Try widening the amount or deadline filters.'));
    return h('div', null, mx.ORDER.map(function (g, i) {
      var items = groups[g] || []; if (!items.length) return null;
      var isCol = collapsed[g] != null ? collapsed[g] : i >= 2;
      // §4: the Insufficient-information group must never be a bare collapsed heading.
      // Show WHY records are here — grouped by the missing dimension — even when collapsed.
      var reasonSummary = null;
      if (g === 'Insufficient information') {
        var buckets = { 'Geography unclear': 0, 'Applicant eligibility unclear': 0, 'Amount undocumented': 0, 'Deadline unclear': 0, 'Narrative missing': 0 };
        items.forEach(function (s) {
          (s.m.missing || []).forEach(function (mi) {
            if (/geograph/i.test(mi)) buckets['Geography unclear']++;
            else if (/applicant|eligib/i.test(mi)) buckets['Applicant eligibility unclear']++;
            else if (/amount|funding size|budget/i.test(mi)) buckets['Amount undocumented']++;
            else if (/deadline|clos/i.test(mi)) buckets['Deadline unclear']++;
            else buckets['Narrative missing']++;
          });
        });
        var parts = Object.keys(buckets).filter(function (k) { return buckets[k]; });
        reasonSummary = h('div', { className: 'ent-muted', style: { fontSize: 12.5, margin: '2px 0 10px', lineHeight: 1.6 } },
          h('span', null, items.length + ' opportunit' + (items.length === 1 ? 'y requires' : 'ies require') + ' more information before Mira can rank them. '),
          h('span', { style: { display: 'inline-flex', gap: 6, flexWrap: 'wrap', marginTop: 4 } },
            parts.map(function (k) { return h('span', { key: k, className: 'ent-chip', style: { fontSize: 11.5 } }, k + ': ' + buckets[k]); })));
      }
      return h('div', { key: g },
        h('div', { className: 'ei-grp', onClick: function () { var n = Object.assign({}, collapsed); n[g] = !isCol; setCol(n); } }, h('h3', null, g), h('span', { className: 'ei-count' }, items.length), h('span', { className: 'ent-muted', style: { fontSize: 12 } }, isCol ? '▸' : '▾')),
        reasonSummary,
        isCol ? null : items.map(function (s, j) { return h(OppRow, { key: s.o.id + j, opp: s.o, m: s.m }); }));
    }));
  }

  function pinBtns(key, entity, name, lens, refresh, toast) {
    var mx = MX(); var pinned = mx.store.pins().some(function (x) { return x.key === key; });
    return [
      h('button', { key: 'p', className: 'ent-btn ent-btn--sm', onClick: function () { if (pinned) mx.store.unpin(key); else mx.store.pin({ key: key, entity: entity, name: name, lens: lens }); refresh && refresh(); } }, pinned ? '✓ Pinned' : 'Pin to Today'),
      h('button', { key: 'a', className: 'ent-btn ent-btn--sm', onClick: function () { mx.store.addAlert({ scope: entity + ':' + name, lens: lens }); toast && toast('Alert created'); } }, 'Alert'),
      h('button', { key: 'm', className: 'ent-btn ent-btn--sm', onClick: function () { toast && toast('Ask Mira: open the panel from any opportunity'); } }, 'Ask Mira')];
  }

  function CountryView(p) {
    var mx = MX(), name = p.id, lens = p.lens || 'funding';
    var direct = p.opps.filter(function (o) { return o.countries.indexOf(name) !== -1; });
    var tabs = ['funding', 'risk', 'sources'].map(function (l) { return h('button', { key: l, className: 'ei-tab' + (l === lens ? ' on' : ''), onClick: function () { go('#/enterprise/country/' + encodeURIComponent(name) + '/' + l); } }, l[0].toUpperCase() + l.slice(1)); });
    var body;
    if (lens === 'funding') body = h('div', null, h('div', { style: { marginBottom: 14 } }, h(Coverage, { opps: direct })), direct.length ? h(Grouped, { opps: direct }) : h('div', { className: 'ei-empty' }, h('h3', null, 'Limited verified coverage for ' + name), h('p', null, 'No opportunities are currently attributed directly to ' + name + '.')));
    else if (lens === 'risk') body = h('div', { className: 'ei-empty' }, h('h3', null, 'No developments require immediate action'), h('p', null, 'Risk signals appear only when an external development plausibly affects your programmes in ' + name + '.'));
    else { var c = mx.coverage(direct); body = h('div', { className: 'ent-card ent-card-pad' }, h('div', { className: 'ei-sec', style: { marginTop: 0 } }, 'Sources for ' + name), h('dl', { className: 'ei-kv' }, h('dt', null, 'Attributed opportunities'), h('dd', null, direct.length), h('dt', null, 'Distinct sources'), h('dd', null, c.sources), h('dt', null, 'Amount coverage'), h('dd', null, c.amountPct + '%'), h('dt', null, 'Local-language sources'), h('dd', null, h('span', { className: 'ent-muted' }, 'None yet — coverage limited')), h('dt', null, 'Last checked'), h('dd', null, new Date().toISOString().slice(0, 10)))); }
    return h('div', { className: 'ent-col ei-wide' }, h(Head, { eyebrow: 'Country', title: name, sub: direct.length + ' opportunities attributed · ' + mx.org().name, actions: pinBtns('country:' + name + ':' + lens, 'Country', name, lens, p.refresh, p.toast) }), h('div', { className: 'ei-tabs' }, tabs), body);
  }

  function FunderView(p) {
    var mx = MX(), name = p.id, lens = p.lens || 'funding';
    var prof = mx.funderProfile(name, p.opps);
    var tabs = ['funding', 'risk', 'sources'].map(function (l) { return h('button', { key: l, className: 'ei-tab' + (l === lens ? ' on' : ''), onClick: function () { go('#/enterprise/funder/' + encodeURIComponent(name) + '/' + l); } }, l[0].toUpperCase() + l.slice(1)); });
    var b = prof.programmeBudgets.slice().sort(function (x, y) { return x - y; });
    var head = h('div', { className: 'ent-card ent-card-pad', style: { marginBottom: 14 } }, h('dl', { className: 'ei-kv' },
      h('dt', null, 'Funder type'), h('dd', null, prof.type || 'Not published'),
      h('dt', null, 'Countries supported'), h('dd', null, prof.countries.length ? prof.countries.slice(0, 8).join(', ') + (prof.countries.length > 8 ? '…' : '') : 'Not published'),
      h('dt', null, 'Sectors'), h('dd', null, prof.sectors.join(', ') || 'Not published'),
      h('dt', null, 'Typical verified award range'), h('dd', null, prof.awardRange ? mx.money.fmtGBP(prof.awardRange.min) + ' – ' + mx.money.fmtGBP(prof.awardRange.max) : 'No per-award amounts published'),
      b.length ? h('dt', null, 'Programme budgets') : null, b.length ? h('dd', null, b.length + ' envelopes · ' + mx.money.fmtGBP(b[0]) + ' – ' + mx.money.fmtGBP(b[b.length - 1]) + ' · median ' + mx.money.fmtGBP(b[Math.floor(b.length / 2)]) + ' (not per-award)') : null,
      h('dt', null, 'Active opportunities'), h('dd', null, prof.active + ' (' + prof.open + ' open)'),
      h('dt', null, 'Last verified'), h('dd', null, new Date().toISOString().slice(0, 10))));
    var body = lens === 'funding' ? h('div', null, h('div', { style: { marginBottom: 14 } }, h(Coverage, { opps: prof.opps })), h(Grouped, { opps: prof.opps }))
      : lens === 'risk' ? h('div', { className: 'ei-empty' }, h('h3', null, 'No material funder changes detected'), h('p', null, 'Funder risk is flagged only on evidence of budget cuts, country exit, suspension or eligibility change.'))
      : h('div', { className: 'ent-card ent-card-pad' }, h('div', { className: 'ei-sec', style: { marginTop: 0 } }, 'Sources for ' + name), h('p', { className: 'ent-muted', style: { fontSize: 13 } }, 'Derived from ' + prof.active + ' opportunity listings. Local-language coverage limited.'));
    return h('div', { className: 'ent-col ei-wide' }, h(Head, { eyebrow: 'Funder', title: name, sub: prof.active + ' opportunities · ' + mx.org().name, actions: pinBtns('funder:' + name + ':' + lens, 'Funder', name, lens, p.refresh, p.toast) }), h('div', { className: 'ei-tabs' }, tabs), head, body);
  }

  function vc(v) { return 'ei-v-' + v; }
  function money2(mx, o) {
    return h('dl', { className: 'ei-kv' },
      h('dt', null, 'Minimum award'), h('dd', null, o.amountMin != null ? mx.money.fmtGBP(mx.money.toGBP(o.amountMin, o.currency)) : 'Amount not published'),
      h('dt', null, 'Maximum award'), h('dd', null, o.amountMax != null ? mx.money.fmtGBP(mx.money.toGBP(o.amountMax, o.currency)) : 'Amount not published'),
      h('dt', null, 'Total programme budget'), h('dd', null, o.totalBudget != null ? mx.money.fmtGBP(mx.money.toGBP(o.totalBudget, o.currency)) + ' (envelope — not per award)' : 'Amount not published'),
      h('dt', null, 'Original currency'), h('dd', null, o.currency || 'Unknown'),
      h('dt', null, 'Analytical GBP'), h('dd', null, o.analyticalGBP != null ? mx.money.fmtGBP(o.analyticalGBP) + ' (as of ' + mx.money.GBP_AS_OF + ')' : 'Not derivable'));
  }
  // Contextual Mira: one honest sentence synthesised from a match result. It
  // distinguishes what is CONFIRMED from what still NEEDS VERIFICATION and never
  // fills a gap it cannot see. Reusable wherever a verdict is shown.
  function miraOneLine(m) {
    if (!m) return 'Mira has not assessed this opportunity yet.';
    if (m.hardFail) return 'Not open to your organisation as it stands: ' + (m.concerns[0] || 'a hard eligibility rule is unmet') + '. Only an eligible lead applicant or a consortium route the call permits could change this.';
    var ok = (m.reasons || []).slice(0, 3), gap = (m.concerns || []).concat(m.missing || []).slice(0, 1);
    if (ok.length && gap.length) return 'You appear to meet ' + ok.join(', ') + '. Still needs verification: ' + gap[0] + '.';
    if (ok.length) return 'You appear to meet ' + ok.join(', ') + '. Nothing on the published information rules you out.';
    if (gap.length) return 'Mira can’t confirm fit yet — unresolved: ' + gap[0] + '.';
    return 'Mira sees no strong signal either way from the published information.';
  }

  function OppView(p) {
    var mx = MX(), o = p.opps.filter(function (x) { return x.id === p.id; })[0];
    var wS = React.useState(false); var showWhy = wS[0], setShowWhy = wS[1];
    var sv = React.useState(mx.store.isSaved(p.id)); var saved = sv[0], setSaved = sv[1];
    var inP = React.useState(mx.store.apps().some(function (a) { return a.oppId === p.id; })); var inPursuits = inP[0], setInP = inP[1];
    if (!o) return h('div', { className: 'ei-empty' }, h('h3', null, 'Opportunity not found'), h('button', { className: 'ent-btn', onClick: function () { go('#/enterprise/funding'); } }, 'Back to Funding'));
    var org = mx.org(), m = mx.match(o, org), d = daysTo(o.deadline);
    var crumbSector = (o.sectors && o.sectors[0]) || (o.countries && o.countries[0]) || 'All';
    var order = ['geography', 'applicantType', 'sector', 'fundingSize', 'deadline', 'evidence'];
    // classification explanation for EVERY category (§7)
    var why = m.overall === 'Not eligible' ? { c: '#9A3B2C', t: 'Why not eligible' }
      : m.overall === 'Strong candidate' ? { c: '#2F6B34', t: 'Why this is a strong candidate' }
      : m.overall === 'Worth reviewing' ? { c: '#8A5A16', t: 'Why this is worth reviewing' }
      : m.overall === 'Insufficient information' ? { c: '#5A3F8A', t: 'Why Mira needs more information' }
      : { c: 'var(--ent-muted)', t: 'Why this is monitoring only' };
    var conf = m.hardFail ? 'High' : (m.missing.length >= 4 ? 'Low' : 'Medium');
    var lead = h('div', { className: 'ei-panel', style: { marginBottom: 18, borderLeft: '3px solid var(--ent-accent)' } },
      h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 10 } },
        badge(m.overall),
        h('span', { className: 'ent-muted', style: { fontSize: 12 } }, 'Confidence ' + conf.toLowerCase())),
      h('div', { style: { display: 'flex', gap: 11, alignItems: 'flex-start' } },
        h('span', { style: { fontSize: 11, fontWeight: 700, letterSpacing: '.08em', color: 'var(--ent-accent)', textTransform: 'uppercase', paddingTop: 2, whiteSpace: 'nowrap' } }, 'Mira'),
        h('div', { style: { flex: 1 } },
          h('div', { style: { fontSize: 14.5, lineHeight: 1.55, color: 'var(--ent-ink)' } }, miraOneLine(m)),
          h('button', { onClick: function () { setShowWhy(!showWhy); }, style: { marginTop: 8, background: 'none', border: 0, padding: 0, cursor: 'pointer', font: 'inherit', fontSize: 12.5, fontWeight: 700, color: 'var(--ent-accent)' } }, showWhy ? 'Hide reasoning ▲' : 'Why? ▼'),
          showWhy ? h('div', { style: { marginTop: 10 } },
            order.map(function (k) { var dd = m.dims[k]; if (!dd) return null; return h('div', { key: k, className: 'ei-dim' }, h('b', null, k.replace(/([A-Z])/g, ' $1').replace(/^./, function (c) { return c.toUpperCase(); })), h('span', { className: vc(dd.verdict) }, dd.verdict + (dd.note ? ' — ' + dd.note : ''))); }),
            h('div', { className: 'ent-muted', style: { fontSize: 11.5, marginTop: 8 } }, 'Each line is a fact, an inference, or an unknown — Mira never fills a gap it cannot verify.')) : null)));
    var main = h('div', null,
      lead,
      h('div', { className: 'ei-sec', style: { marginTop: 0 } }, 'Mira summary'),
      o.summary ? h('p', { style: { fontSize: 13.5 } }, o.summary)
        : h('div', null, h('p', { style: { fontSize: 13.5 } }, 'Mira could not produce a reliable summary from the currently available official information.'),
          h('p', { className: 'ent-muted', style: { fontSize: 12.5 } }, 'Checked: the source listing' + (o.url ? ' (' + (o.sourceName || o.funder) + ')' : '') + '. Still missing: ' + (m.missing.length ? m.missing.join(', ') : 'a published narrative') + '.')),
      h('div', { className: 'ei-sec' }, 'Funding details'), money2(mx, o),
      h('div', { className: 'ei-sec' }, 'Eligibility'),
      h('dl', { className: 'ei-kv' },
        h('dt', null, 'Eligible countries'), h('dd', null, o.countries.length ? o.countries.join(', ') : h('span', { className: 'ent-muted' }, 'Not published')),
        h('dt', null, 'Eligible applicant types'), h('dd', null, o.eligible.length ? o.eligible.join(', ') : h('span', { className: 'ent-muted' }, 'Not published')),
        h('dt', null, 'Eligibility notes'), h('dd', null, o.eligibilityText || h('span', { className: 'ent-muted' }, 'Not published'))),
      h('div', { className: 'ei-sec' }, 'Match analysis'),
      order.map(function (k) { var dd = m.dims[k]; if (!dd) return null; return h('div', { key: k, className: 'ei-dim' }, h('b', null, k.replace(/([A-Z])/g, ' $1').replace(/^./, function (c) { return c.toUpperCase(); })), h('span', { className: vc(dd.verdict) }, dd.verdict + (dd.note ? ' — ' + dd.note : ''))); }),
      // classification explanation (works for every category)
      h('details', { open: true, style: { marginTop: 12, border: '1px solid var(--ent-border)', borderRadius: 10, padding: '12px 14px', background: 'var(--ent-surface)' } },
        h('summary', { style: { fontWeight: 700, fontSize: 13, cursor: 'pointer', color: why.c } }, why.t),
        h('div', { style: { marginTop: 8, fontSize: 13 } },
          m.reasons.length ? h('div', { style: { marginBottom: 6 } }, h('span', { className: 'ent-muted', style: { fontWeight: 700, fontSize: 12 } }, 'CONFIRMED STRENGTHS: '), h('span', { style: { color: '#2F6B34' } }, m.reasons.join(' · '))) : null,
          m.concerns.length ? h('div', { style: { marginBottom: 6 } }, h('span', { className: 'ent-muted', style: { fontWeight: 700, fontSize: 12 } }, m.hardFail ? 'CONFIRMED BLOCKERS: ' : 'CONCERNS: '), h('span', { style: { color: m.hardFail ? '#9A3B2C' : '#8A5A16' } }, m.concerns.join(' · '))) : null,
          m.missing.length ? h('div', { style: { marginBottom: 6 } }, h('span', { className: 'ent-muted', style: { fontWeight: 700, fontSize: 12 } }, 'UNCLEAR / MISSING: '), h('span', { className: 'ent-muted' }, m.missing.join(' · '))) : null,
          m.hardFail ? h('div', null, h('span', { className: 'ent-muted', style: { fontWeight: 700, fontSize: 12 } }, 'CAN THIS CHANGE? '), 'Only via an eligible lead applicant or consortium route the call permits.') : null)),
      h('div', { className: 'ei-sec' }, 'Requirements & documents'),
      h('p', { className: 'ent-muted', style: { fontSize: 13 } }, 'Official document list not yet retrieved. Enrichment (guidance, templates, eligibility PDFs) is queued — see the enrichment state.'),
      h('div', { className: 'ei-sec' }, 'Important dates'),
      h('dl', { className: 'ei-kv' }, h('dt', null, 'Deadline'), h('dd', null, o.deadline ? String(o.deadline).slice(0, 10) + (d != null ? ' (in ' + d + ' days)' : '') : 'Not published'), h('dt', null, 'Posted'), h('dd', null, o.posted ? String(o.posted).slice(0, 10) : 'Unknown')),
      h('div', { className: 'ei-sec' }, 'Sources & evidence'),
      h('dl', { className: 'ei-kv' },
        h('dt', null, 'Enrichment state'), h('dd', null, h('span', { className: 'ei-b ei-b-mon' }, o.url ? 'Partially enriched' : 'Not checked')),
        h('dt', null, 'Official listing'), h('dd', null, o.url ? h('a', { className: 'mx-source-link', href: o.url, target: '_blank', rel: 'noopener', style: { color: 'var(--ent-accent)' } }, o.url) : 'Not published'),
        h('dt', null, 'Source'), h('dd', null, o.sourceName || o.funder || 'Unknown'),
        h('dt', null, 'Last verified'), h('dd', null, o.posted ? String(o.posted).slice(0, 10) : 'Unknown')));
    var panel = h('div', { className: 'ei-sticky' },
      h('div', { style: { marginBottom: 8 } }, badge(m.overall)),
      h('dl', { className: 'ei-kv', style: { gridTemplateColumns: '1fr auto', fontSize: 12.5 } },
        h('dt', null, 'Confidence'), h('dd', null, m.hardFail ? 'High (hard rule)' : (m.missing.length >= 4 ? 'Low' : 'Medium')),
        h('dt', null, 'Geography'), h('dd', { className: vc(m.dims.geography.verdict) }, m.dims.geography.verdict),
        h('dt', null, 'Applicant type'), h('dd', { className: vc(m.dims.applicantType.verdict) }, m.dims.applicantType.verdict),
        h('dt', null, 'Deadline'), h('dd', { className: vc(m.dims.deadline.verdict) }, m.dims.deadline.verdict),
        h('dt', null, 'Amount fit'), h('dd', { className: vc(m.dims.fundingSize.verdict) }, m.dims.fundingSize.verdict)),
      h('div', { className: 'ei-nba', style: { margin: '12px 0' } }, h('div', { style: { fontSize: 11, fontWeight: 700, letterSpacing: '.06em', color: 'var(--ent-muted)', marginBottom: 3 } }, 'NEXT BEST ACTION'), mx.nextBestAction({}, o, org)),
      h('div', { style: { display: 'flex', flexDirection: 'column', gap: 7 } },
        inPursuits ? h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/pursuits'); } }, '✓ In Pursuits — open')
          : h('button', { className: 'ent-btn ent-btn--primary ent-btn--sm', onClick: function () { mx.store.createApp(o); setInP(true); p.toast && p.toast('Added to Pursuits'); } }, 'Add to Pursuits'),
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { if (saved) { mx.store.unsave(o.id); setSaved(false); } else { mx.store.save(o); setSaved(true); p.toast && p.toast('Saved'); } } }, saved ? '✓ Saved' : 'Save'),
        o.url ? h('a', { className: 'ent-btn ent-btn--sm', href: o.url, target: '_blank', rel: 'noopener', style: { textAlign: 'center' } }, 'Open official source ↗') : null),
      h('div', { className: 'ent-muted', style: { fontSize: 11, marginTop: 10 } }, 'Last checked ' + (o.posted ? String(o.posted).slice(0, 10) : 'unknown')));
    return h('div', { className: 'ent-col ei-wide' },
      h('a', { className: 'ei-back', onClick: function () { if (history.length > 1) history.back(); else go('#/enterprise/funding'); } }, '← Back to funding'),
      h('div', { className: 'ei-crumb' }, h('a', { onClick: function () { go('#/enterprise/funding'); } }, 'Funding'), h('span', null, '/'), h('span', null, crumbSector), h('span', null, '/'), h('span', { style: { color: 'var(--ent-ink)' } }, o.title.slice(0, 40) + (o.title.length > 40 ? '…' : ''))),
      h(Head, { eyebrow: o.funder, title: o.title, sub: o.type + ' · ' + o.status.replace(/_/g, ' ') + ' · ' + (o.trust === 'high' ? 'verified source' : 'source unclear') + (d != null ? ' · closes in ' + d + ' days' : '') }),
      h('div', { className: 'ei-84' }, main, panel));
  }

  function SearchView(p) {
    var mx = MX(), org = mx.org();
    var qs = React.useState(''); var q = qs[0], setQ = qs[1];
    var rs = React.useState(null); var res = rs[0], setRes = rs[1];
    function run(query) { var qq = String(query || '').trim(); if (!qq) { setRes(null); return; } setRes(mx.parse(qq, p.opps)); }  // §8: stay on the Search page
    function setFilters(nf) { setRes(Object.assign({}, res, { filters: nf, entity: nf.country ? { type: 'country', id: nf.country } : (res.entity && res.entity.type === 'funder' ? res.entity : null) })); }
    function removeFilter(k) { var nf = Object.assign({}, res.filters); delete nf[k]; setFilters(nf); }
    // §7: personalised suggestions from REAL organisation data (countries/sectors).
    var oc = org.countries || [], c0 = oc[0], s0 = (org.sectors || org.topics || [])[0];
    var suggested = [
      c0 ? { t: 'Find funding for our programmes in ' + c0, run: 'funding in ' + c0 } : { t: 'Find funding for our current programmes', run: (s0 || 'health') + ' funding' },
      c0 ? { t: 'What changed in ' + c0 + '?', go: '#/enterprise/country/' + encodeURIComponent(c0) + '/risk' } : null,
      { t: 'World Bank opportunities we may be eligible for', run: 'world bank opportunities' },
      { t: 'Which pursuits need a decision?', go: '#/enterprise/pursuits' },
      { t: 'What risks affect our active countries?', go: c0 ? '#/enterprise/country/' + encodeURIComponent(c0) + '/risk' : '#/enterprise/signals' },
    ].filter(Boolean);
    // §7: live interpretation preview while typing (before submit).
    var live = (!res && q.trim().length >= 3) ? mx.parse(q, p.opps) : null;
    var filtered = res ? mx.applyFilters(p.opps, res.filters) : [];
    function chip(label, value, k) { return h('span', { key: (k || label) + value, className: 'ent-chip', style: { display: 'inline-flex', alignItems: 'center', gap: 6 } }, h('span', { style: { fontSize: 10, textTransform: 'uppercase', letterSpacing: '.05em', color: 'var(--ent-muted)' } }, label), value, k ? h('button', { onClick: function () { removeFilter(k); }, style: { border: 0, background: 'none', cursor: 'pointer', color: 'var(--ent-faint)', fontSize: 13, lineHeight: 1, padding: 0 }, title: 'Remove ' + label }, '×') : null); }
    function interpChips(r, editable) {
      return [
        r.entity && r.entity.type === 'funder' ? chip('Funder', r.entity.id, null) : null,
        r.filters.country ? chip('Country', r.filters.country, editable ? 'country' : null) : null,
        r.filters.region ? chip('Region', r.filters.region, editable ? 'region' : null) : null,
        chip('Lens', r.lens, null),
        r.filters.fundingType ? chip('Type', r.filters.fundingType, editable ? 'fundingType' : null) : null,
        r.filters.sector ? chip('Sector', r.filters.sector, editable ? 'sector' : null) : null,
        r.filters.amountMax ? chip('Max', mx.money.fmtGBP(r.filters.amountMax), editable ? 'amountMax' : null) : null,
        r.filters.deadlineDays ? chip('Closing', '≤ ' + r.filters.deadlineDays + 'd', editable ? 'deadlineDays' : null) : null,
      ];
    }

    // ── BEFORE RESULTS: dotted translucent hero canvas (§7) ──
    if (!res) {
      return h('div', { className: 'ent-col ei-wide' },
        h('div', { className: 'ent-dotted ent-dotted--panel ei-hero', style: { padding: 'clamp(30px,6vw,66px) 24px' } },
          h('div', { style: { maxWidth: 660, margin: '0 auto', textAlign: 'center' } },
            h('div', { className: 'ei-hero-label' }, 'Mira Enterprise Search'),
            h('h1', { className: 'ei-hero-h1' }, 'What does your organisation need to know?'),
            h('form', { onSubmit: function (e) { e.preventDefault(); run(q); }, className: 'ei-search-wrap' },
              h('span', { className: 'ei-search-ic' }, '⌕'),
              h('input', { className: 'ei-search-hero', autoFocus: true, placeholder: 'Search funding, countries, funders, risks…', value: q, onChange: function (e) { setQ(e.target.value); }, onKeyDown: function (e) { if (e.key === 'Escape') setQ(''); } }),
              q ? h('button', { type: 'button', className: 'ei-clear', title: 'Clear', onClick: function () { setQ(''); } }, '×') : h('span', { className: 'ei-kbd' }, '⌘K')),
            h('div', { className: 'ei-hero-sub' }, 'Search funding, countries, funders, risks and sources.'),
            live ? h('div', { style: { display: 'flex', gap: 7, flexWrap: 'wrap', justifyContent: 'center', marginTop: 15, alignItems: 'center' } },
              h('span', { className: 'ent-muted', style: { fontSize: 11.5, fontWeight: 600 } }, 'Mira reads:'), interpChips(live, false)) : null,
            h('div', { className: 'ei-suggest' }, suggested.map(function (s, i) { return h('button', { key: i, onClick: function () { if (s.go) go(s.go); else { setQ(s.run); run(s.run); } } }, s.t); })))));
    }

    // ── RESULTS: hero contracts to a sticky translucent bar + 8/4 with context panel (§7) ──
    var searchbar = h('form', { onSubmit: function (e) { e.preventDefault(); run(q); }, className: 'ent-glass ent-glass--sticky ei-searchbar' },
      h('span', { style: { color: 'var(--ent-faint)', fontSize: 16 } }, '⌕'),
      h('input', { value: q, onChange: function (e) { setQ(e.target.value); }, placeholder: 'Search funding, countries, funders, risks…' }),
      h('button', { type: 'submit', className: 'ent-btn ent-btn--primary ent-btn--sm' }, 'Search'),
      h('button', { type: 'button', className: 'ent-btn ent-btn--sm', onClick: function () { setRes(null); setQ(''); } }, 'Clear'));
    var left;
    if (!filtered.length && res.filters.country) left = h('div', { className: 'ent-card ent-card-pad' },
      h('h3', { style: { fontSize: 15, color: 'var(--ent-ink)', margin: '0 0 4px' } }, 'No verified opportunities matching ' + res.filters.country + ' were found in Mira’s current searchable sources.'),
      h('p', { className: 'ent-muted', style: { fontSize: 13 } }, 'Mira did not fall back to another country. Coverage is limited to ' + p.opps.length + ' canonical records from Grants.gov and World Bank.'),
      h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 8 } },
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { removeFilter('country'); } }, 'Remove country filter'),
        regionFor(res.filters.country) ? h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { var nf = Object.assign({}, res.filters); delete nf.country; nf.region = regionFor(res.filters.country); setFilters(nf); } }, 'Broaden to ' + regionFor(res.filters.country)) : null,
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { mx.store.addAlert({ scope: 'country:' + res.filters.country, lens: 'funding' }); p.toast && p.toast('Monitor created for ' + res.filters.country); } }, 'Create a ' + res.filters.country + ' funding monitor')));
    else if (!filtered.length) left = h('div', { className: 'ei-empty' }, h('h3', null, 'No results'), h('p', null, 'Try widening the amount or deadline filters, or remove a chip.'));
    else left = h('div', null,
      h('div', { className: 'ent-muted', style: { fontSize: 13, margin: '2px 0 10px' } }, filtered.length + ' result' + (filtered.length === 1 ? '' : 's') + (res.filters.country ? ' in ' + res.filters.country : '')),
      h(Grouped, { opps: filtered }));
    // §7 right: transparent contextual panel (understood · filters · coverage · actions)
    var panel = h('div', { className: 'ent-glass ent-glass--panel' },
      h('div', { style: { fontSize: 11, fontWeight: 700, letterSpacing: '.06em', color: 'var(--ent-muted)', marginBottom: 8 } }, 'WHAT MIRA UNDERSTOOD'),
      h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 9 } }, interpChips(res, true)),
      h('div', { className: 'ent-muted', style: { fontSize: 11.5, marginBottom: 14, lineHeight: 1.5 } }, res.filters.country ? 'Explicit country “' + res.filters.country + '” is a hard filter — organisation ranking never overrides it.' : 'Personalised ranking for ' + org.name + '.'),
      h('div', { style: { fontSize: 11, fontWeight: 700, letterSpacing: '.06em', color: 'var(--ent-muted)', margin: '2px 0 7px' } }, 'RESULT COVERAGE'),
      h(Coverage, { opps: filtered }),
      h('div', { style: { display: 'flex', flexDirection: 'column', gap: 7, marginTop: 15 } },
        h('button', { className: 'ent-btn ent-btn--primary ent-btn--sm', onClick: function () { p.toast && p.toast('Ask Mira — open the panel from any opportunity'); } }, 'Ask Mira about these results'),
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { mx.store.addAlert({ scope: 'search:' + (res.filters.country || q || res.lens), lens: res.lens }); p.toast && p.toast('Saved as a monitor'); } }, 'Save as monitor'),
        res.filters.country ? h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/country/' + encodeURIComponent(res.filters.country) + '/sources'); } }, 'View ' + res.filters.country + ' sources') : null));
    return h('div', { className: 'ent-col ei-wide' }, searchbar, h('div', { className: 'ei-84' }, left, panel));
  }
  function regionFor(c) { var m = { 'Sri Lanka': 'South Asia', 'Pakistan': 'South Asia', 'India': 'South Asia', 'Bangladesh': 'South Asia', 'Nepal': 'South Asia', 'Kenya': 'East Africa', 'Ethiopia': 'East Africa', 'Uganda': 'East Africa', 'Nigeria': 'West Africa' }; return m[c] || null; }

  // ── Funding classification → decision segment (§5/§12) ──────────────────────
  var FUND_SEGS = [
    { key: 'strong', label: 'Strong alignment' }, { key: 'review', label: 'Review' },
    { key: 'monitoring', label: 'Monitoring' }, { key: 'missing', label: 'Missing information' },
    { key: 'ineligible', label: 'Ineligible' },
  ];
  function segOf(overall) {
    if (overall === 'Strong candidate') return 'strong';
    if (overall === 'Worth reviewing') return 'review';
    if (overall === 'Insufficient information') return 'missing';
    if (overall === 'Not eligible') return 'ineligible';
    return 'monitoring';
  }
  function segLabel(k) { for (var i = 0; i < FUND_SEGS.length; i++) if (FUND_SEGS[i].key === k) return FUND_SEGS[i].label; return k; }
  var TYPE_LABELS = { grant: 'Grant', tender: 'Tender', procurement: 'Procurement', rfp: 'RFP', rfq: 'RFQ', eoi: 'EOI', devbank: 'Concessional finance', loan: 'Concessional finance', fellowship: 'Fellowship', prize: 'Challenge fund', innovation: 'Challenge fund', partnership: 'Partnership', consultancy: 'Technical assistance', programme: 'Programme' };
  function typeLabel(o) { var t = String(o.type || '').toLowerCase(); return TYPE_LABELS[t] || (o.type ? (o.type.charAt(0).toUpperCase() + o.type.slice(1)) : 'Opportunity'); }
  // §8/§13: show the NARROWEST supported geography; never invents membership.
  function geoLabel(o) {
    if (o.countries.length === 1) return o.countries[0];
    if (o.countries.length > 1) return o.countries[0] + ' · +' + (o.countries.length - 1);
    var ng = o.regions.filter(function (r) { return r !== 'Global'; });
    if (ng.length) return ng[0] + (o.regions.indexOf('Global') !== -1 ? ' · global' : '');
    if (o.regions.indexOf('Global') !== -1) return 'Global';
    return 'Not stated';
  }
  function awardLabel(o) {
    var m = MX().money;
    if (o.amountType === 'per_award_max' && o.analyticalGBP != null) return m.fmtGBP(o.analyticalGBP);
    if (o.amountType === 'per_award_min' && o.analyticalGBP != null) return 'From ' + m.fmtGBP(o.analyticalGBP);
    if (o.amountType === 'total_programme_budget' && o.totalBudget != null) return m.fmtGBP(m.toGBP(o.totalBudget, o.currency)) + ' envelope';
    return 'Not stated';
  }
  function dlLabel(o) {
    if (!o.deadline) return o.status === 'forecast' ? 'Forecast' : 'Rolling';
    var d = daysTo(o.deadline); if (d < 0) return 'Closed';
    if (d === 0) return 'Closes today';
    if (d === 1) return 'Closes tomorrow';
    if (d <= 30) return 'Closes in ' + d + ' days';
    return longDate(o.deadline) || String(o.deadline).slice(0, 10);
  }
  function eligState(m) {
    var v = m.dims.applicantType && m.dims.applicantType.verdict;
    if (v === 'strong') return { t: 'Appears eligible', c: 'appears' };
    if (v === 'ineligible') return { t: 'Ineligible', c: 'ineligible' };
    if (v === 'weak') return { t: 'Verification required', c: 'verify' };
    return { t: 'Not stated', c: 'not' };
  }
  function mainReason(m) { return m.reasons[0] || (m.hardFail && m.concerns[0]) || m.concerns[0] || (m.missing.length ? 'Unresolved: ' + m.missing[0].toLowerCase() : 'No organisation-specific signal yet'); }
  function isNewOpp(o) { return o.posted && (Date.now() - new Date(o.posted).getTime()) < 7 * 864e5; }

  // Quick-preview drawer (§10) — Overview / Eligibility / Evidence tabs.

  // ── Decision-first quick preview (Phase 6A) ────────────────────────────────
  // Opens on the DECISION, not on a data dump. Tabs are Summary / Eligibility /
  // Evidence / Changes — "Overview" is gone. For a record whose deadline has
  // passed the primary action is finding a live alternative, never "Add to
  // Pursuits", which would invite work on something that cannot be pursued.
  function decisionOf(s) {
    var seg = s.seg, o = s.o;
    // Do not pursue is only ever shown for records that genuinely cannot be
    // pursued — a passed deadline or an inactive status. It is never used as a
    // dressed-up "we could not classify this".
    if (isClosedOpp(o)) return { key: 'no', label: 'Do not pursue', tone: 'no' };
    if (seg === 'strong') return { key: 'best', label: 'Best fit', tone: 'best' };
    if (seg === 'review') return { key: 'good', label: 'Good fit', tone: 'good' };
    if (seg === 'monitoring') return { key: 'review', label: 'Review', tone: 'review' };
    if (seg === 'missing') return { key: 'info', label: 'Needs information', tone: 'info' };
    // seg === 'ineligible' on a live record. The server reaches that verdict
    // whenever the org profile does not intersect the stated applicant types —
    // including when NOTHING is stated, which is an absence of evidence, not a
    // rejection. Distinguish the two rather than telling the customer to walk
    // away from an opportunity nobody has actually ruled them out of.
    if (!(o.eligible_types || []).length) return { key: 'info', label: 'Needs information', tone: 'info' };
    return { key: 'review', label: 'Review', tone: 'review' };
  }
  function isClosedOpp(o) {
    if (INACTIVE_STATUSES.indexOf(String(o.status || '')) !== -1) return true;
    return !!(o.deadline && new Date(o.deadline).getTime() < Date.now() - 864e5);
  }
  // One plain sentence. Only claims what the record actually supports.
  function mainReason(s) {
    var o = s.o, m = s.m;
    if (isClosedOpp(o)) return 'The application deadline has passed.';
    if (isIndividualConsultant(o)) return 'Individual applicants only — not an institutional opportunity.';
    if (!(o.eligible_types || o.eligibleTypes || []).length) return 'Applicant organisation types are not stated in the source.';
    if (s.seg === 'ineligible') return 'Stated applicant types are ' + (o.eligible_types || []).slice(0, 2).join(', ') + ' — confirm whether your organisation qualifies.';
    var geo = (o.countries || [])[0];
    if (s.seg === 'strong' && geo) return 'Open to your organisation type for work in ' + geo + '.';
    if (s.seg === 'review') return 'Sector and geography align; applicant eligibility needs confirmation.';
    if (s.seg === 'monitoring') return 'Relevant to your programmes, but the fit is partial.';
    if (m && m.missing && m.missing.length) return 'Unresolved: ' + m.missing.slice(0, 2).join(', ') + '.';
    return 'Requires review against your matching profile.';
  }
  function isIndividualConsultant(o) {
    return String(o.opportunity_type || o.opportunityType || '') === 'individual_consultant';
  }
  // Plain-English purpose. Never dumps raw procurement boilerplate: if the
  // official text is not a usable description we say so instead of guessing.
  function whatThisFunds(o, full) {
    var t = (full && full.summary) || o.summary || '';
    t = String(t).replace(/<[^>]+>/g, ' ').replace(/&[a-z]+;/gi, ' ').replace(/\s+/g, ' ').trim();
    var looksLikeBoilerplate = /reference no|procurement plan|loan no|credit no|ibrd|ida grant/i.test(t) && t.length > 240;
    if (!t || t.length < 40 || looksLikeBoilerplate) return null;
    return t.length > 420 ? t.slice(0, 420).replace(/\s+\S*$/, '') + '…' : t;
  }
  function typeSentence(o) {
    var ty = String(o.opportunity_type || '');
    var map = {
      individual_consultant: 'The source requests an individual consultant. It is not an institutional grant.',
      consulting_firm_eoi: 'The source invites expressions of interest from consulting firms.',
      goods_procurement: 'This is a goods procurement tender.',
      works_procurement: 'This is a works (construction) procurement tender.',
      non_consulting_services: 'This is a non-consulting services tender.',
      prequalification: 'This is a prequalification notice, not yet a call for bids.',
      forecast_notice: 'This is a forward-looking notice. No application is open yet.',
    };
    return map[ty] || null;
  }

  // ── Phase 6A decision model ────────────────────────────────────────────────
  // Statuses that mean "no longer actionable". Mirrors the server's active
  // default so the client never contradicts the query it just made.
  var INACTIVE_STATUSES = ['closed', 'awarded', 'archived', 'cancelled', 'withdrawn', 'expired', 'historical'];

  // Customer-facing tabs. Each binds to real server parameters — no tab filters
  // in the browser. `uncovered` marks a category the corpus genuinely has no
  // source for yet; it renders an honest coverage message rather than a silent
  // zero, and is never quietly hidden.
  var FUND_TABS = [
    { key: 'recommended', label: 'Recommended', params: {} },
    { key: 'grants', label: 'Grants', params: { family: 'grant' }, uncovered: true },
    { key: 'tenders', label: 'Tenders', params: { family: 'procurement' } },
    { key: 'consulting', label: 'Consulting', params: { family: 'consulting' } },
    { key: 'research', label: 'Research', params: { family: 'research' }, uncovered: true },
    { key: 'partnerships', label: 'Partnerships', params: { family: 'partnership' }, uncovered: true },
    { key: 'finance', label: 'Finance', params: { family: 'finance' }, uncovered: true },
    { key: 'forecasts', label: 'Forecasts', params: { surface: 'all', statuses: ['forecast'] } },
    { key: 'saved', label: 'Saved', params: {} },
    { key: 'historical', label: 'Historical', params: { surface: 'all', statuses: INACTIVE_STATUSES } },
  ];
  var FUND_TABS_BY_KEY = {};
  FUND_TABS.forEach(function (t) { FUND_TABS_BY_KEY[t.key] = t; });

  // Match quality. "Monitoring" is deliberately not a fit state — watching is a
  // saved action, not a judgement about fit.
  var FIT_FILTERS = [
    { key: '', label: 'All recommended' },
    { key: 'best', label: 'Best fit' },
    { key: 'good', label: 'Good fit' },
    { key: 'review', label: 'Review' },
    { key: 'info', label: 'Needs information' },
  ];
  var FIT_TO_SEGMENT = { best: 'strong', good: 'review', review: 'monitoring', info: 'missing' };
  var SEGMENT_TO_FIT = { strong: 'best', review: 'good', monitoring: 'review', missing: 'info' };
  var SEGMENT_COUNT_KEY = { strong: 'strongAlignment', review: 'review', monitoring: 'monitoring', missing: 'missingInformation', ineligible: 'ineligible' };
  var DIM_LABELS = { geography: 'Geography', applicantType: 'Applicant type', sector: 'Sector', fundingSize: 'Award size', deadline: 'Deadline', evidence: 'Evidence' };

  // Precise, customer-readable type label from the official taxonomy.
  var PRECISE_TYPES = {
    individual_consultant: 'Individual consultant',
    consulting_firm_eoi: 'Consulting firm EOI',
    goods_procurement: 'Goods tender',
    works_procurement: 'Works tender',
    non_consulting_services: 'Services tender',
    prequalification: 'Prequalification',
    forecast_notice: 'Forecast notice',
    unresolved: 'Type not resolved',
  };
  function preciseType(o) {
    var t = String(o.opportunity_type || '');
    if (PRECISE_TYPES[t]) return PRECISE_TYPES[t];
    return typeLabel(o);
  }
  // A readable English title. Foreign-language source titles are shown as
  // published — Mira does not machine-translate and present it as official —
  // but they are trimmed so one record cannot dominate the row height.
  // Source records sometimes carry raw HTML entities ("&ndash;", "&amp;"). They
  // must never reach the UI as literal text. One reused textarea decodes safely.
  var _decodeEl = null;
  function decodeHtml(s) {
    if (s == null) return s;
    s = String(s);
    if (s.indexOf('&') === -1) return s;
    try {
      if (!_decodeEl && typeof document !== 'undefined') _decodeEl = document.createElement('textarea');
      if (_decodeEl) { _decodeEl.innerHTML = s; return _decodeEl.value; }
    } catch (e) {}
    return s.replace(/&ndash;/g, '–').replace(/&mdash;/g, '—').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&#39;|&apos;/g, "'").replace(/&quot;/g, '"').replace(/&nbsp;/g, ' ');
  }
  function displayTitle(o) {
    var t = decodeHtml(String(o.display_title || o.title || '')).replace(/\s+/g, ' ').trim();
    return t.length > 150 ? t.slice(0, 150).replace(/\s+\S*$/, '') + '…' : (t || 'Untitled record');
  }
  function routeLabel(o) {
    var r = o.participation_routes || o.participationRoutes;
    if (Array.isArray(r) && r.length) return r.map(function (x) { return String(x).replace(/_/g, ' '); }).join(', ');
    if (String(o.opportunity_type || '') === 'individual_consultant') return 'Individual';
    if (String(o.opportunity_type || '') === 'consulting_firm_eoi') return 'Firm';
    return 'Not stated';
  }
  function availabilityLabel(o) {
    if (o.availability) return String(o.availability).replace(/_/g, ' ');
    var s = String(o.status || '');
    if (INACTIVE_STATUSES.indexOf(s) !== -1) return 'Closed';
    if (s === 'verified_open') return 'Open';
    if (s === 'closing_soon') return 'Closing soon';
    if (s === 'forecast') return 'Forecast';
    if (s === 'status_unverified') return 'Availability unconfirmed';
    return s ? s.replace(/_/g, ' ') : 'Not stated';
  }
  function readFundingUrl() {
    try {
      var qs = String(location.hash).split('?')[1] || '';
      var u = new URLSearchParams(qs);
      return { tab: u.get('tab'), fit: u.get('fit'), q: u.get('q'), country: u.get('country'), type: u.get('type'), funder: u.get('funder'), global: u.get('global') };
    } catch (e) { return {}; }
  }
  var SAVED_KEY = 'mira.funding.saved';
  function readSavedIds() { try { return JSON.parse(localStorage.getItem(SAVED_KEY) || '[]'); } catch (e) { return []; } }
  function writeSavedIds(a) { try { localStorage.setItem(SAVED_KEY, JSON.stringify(a || [])); } catch (e) {} }

  function FundingPreview(s, tab, setTab, close, p) {
    var mx = MX(), org = mx.org(), o = s.o, m = s.m, es = eligState(m);
    var dec = decisionOf(s), closed = isClosedOpp(o);
    var order = ['geography', 'applicantType', 'sector', 'fundingSize', 'deadline', 'evidence'];
    function kv(dt, dd) { return [h('dt', { key: dt }, dt), h('dd', { key: dt + '_v' }, dd)]; }
    function sec(title, node) {
      return h('div', { className: 'efn-dwsec' },
        h('div', { className: 'efn-dwsec-t' }, title), node);
    }
    var funds = whatThisFunds(o, s.full);
    var tsent = typeSentence(o);

    // 1 Recommendation · 2 What this funds · 3 Who can apply · 4 Your position
    // 5 Why it may fit · 6 Why it may not · 7 Next step · 8 Related
    var summary = h('div', { className: 'efn-dwbody' },
      sec('What this funds', funds
        ? h('p', { className: 'efn-p' }, funds)
        : h('p', { className: 'efn-p ent-muted' }, 'Purpose is not sufficiently described in the available official source.')),
      sec('Who can apply', h('p', { className: 'efn-p' },
        tsent || ((o.eligible_types || []).length
          ? 'Stated eligible applicants: ' + (o.eligible_types || []).join(', ') + '.'
          : 'The source does not state which organisation types may apply.'))),
      sec('Your organisation’s position', h('p', { className: 'efn-p' },
        closed ? 'Not pursuable — the deadline has passed.'
          : isIndividualConsultant(o) ? 'Not suitable for your organisation’s institutional funding pipeline.'
          : dec.key === 'best' ? 'You appear eligible and the work matches your programmes.'
          : dec.key === 'good' ? 'A likely fit, with at least one requirement still to confirm.'
          : dec.key === 'review' ? 'Partial fit — worth a decision, not an automatic pursuit.'
          : 'Mira cannot judge fit until the missing requirements are published or your profile is completed.')),
      (m && m.reasons && m.reasons.length)
        ? sec('Why it may fit', h('ul', { className: 'efn-ul' }, m.reasons.slice(0, 3).map(function (r, i) { return h('li', { key: i }, r); })))
        : null,
      (m && m.missing && m.missing.length)
        ? sec('Why it may not work', h('ul', { className: 'efn-ul' }, m.missing.slice(0, 3).map(function (r, i) { return h('li', { key: i }, 'Unresolved: ' + r); })))
        : null,
      sec('Recommended next step', h('p', { className: 'efn-p' },
        closed ? 'Look for a current round from this funder rather than this notice.'
          : dec.key === 'info' ? 'Open the official source and confirm the applicant requirements.'
          : 'Review the eligibility tab, then decide whether to track it.')));

    var elig = h('div', { className: 'efn-dwbody' },
      h('div', { className: 'efn-eligtop' },
        h('div', null, h('span', { className: 'efn-eligk' }, 'Applicant eligibility'), h('b', null, es.t)),
        h('div', null, h('span', { className: 'efn-eligk' }, 'Availability'), h('b', null, availabilityLabel(o))),
        h('div', null, h('span', { className: 'efn-eligk' }, 'Recommendation'), h('b', null, dec.label))),
      h('table', { className: 'efn-eligtbl' },
        h('thead', null, h('tr', null, h('th', null, 'Requirement'), h('th', null, 'What the source says'), h('th', null, 'Your position'), h('th', null, 'Verdict'))),
        h('tbody', null, order.map(function (k) {
          var d = m && m.dims && m.dims[k];
          if (!d) return null;
          return h('tr', { key: k },
            h('td', null, DIM_LABELS[k] || k),
            h('td', null, d.source || 'Not stated'),
            h('td', null, d.ours || '—'),
            h('td', null, h('span', { className: 'efn-verdict efn-verdict-' + (d.state || 'unclear') }, d.state || 'unclear')));
        }))));

    var evidence = h('div', { className: 'efn-dwbody' },
      h('dl', { className: 'ei-kv' },
        kv('Official source', o.official_url
          ? h('a', { href: o.official_url, target: '_blank', rel: 'noopener' }, o.official_url.slice(0, 68) + (o.official_url.length > 68 ? '…' : ''))
          : 'Not recorded'),
        kv('Source name', (s.full && s.full.sourceName) || o.funder || 'Not recorded'),
        kv('Publication date', o.source_published_at || 'Not published by the source'),
        kv('First observed', (o.first_seen || '').slice(0, 10) || 'Not recorded'),
        kv('Deadline evidence', o.deadline ? o.deadline + ' (from the notice)' : 'No deadline stated'),
        kv('Applicant evidence', (o.eligible_types || []).length ? (o.eligible_types || []).join(', ') : 'Not stated in the source'),
        kv('Geography evidence', (o.countries || []).join(', ') || 'Not stated'),
        kv('Supporting passage', (s.full && s.full.evidence) || 'No evidence snippet retrieved')));

    var changes = h('div', { className: 'efn-dwbody' },
      h('dl', { className: 'ei-kv' },
        kv('First observed', (o.first_seen || '').slice(0, 10) || 'Not recorded'),
        kv('Last seen', (o.last_seen || '').slice(0, 10) || 'Not recorded'),
        kv('Current status', availabilityLabel(o))),
      h('p', { className: 'ent-muted efn-p' }, 'Deadline, amendment and award history are recorded from the next ingestion onward. Nothing is inferred for periods before Mira observed this record.'));

    return h('div', { className: 'efn-drawer', 'data-ui-part': 'funding-preview-v2' },
      h('div', { className: 'efn-dwhead' },
        h('div', { style: { minWidth: 0 } },
          h('div', { className: 'efn-dweyebrow' }, o.funder, ' · ', preciseType(o)),
          h('h3', { className: 'efn-dwtitle' }, displayTitle(o)),
          h('div', { className: 'efn-dwmeta' }, geoLabel(o), ' · ', availabilityLabel(o))),
        h('button', { className: 'efn-iconbtn', onClick: close, 'aria-label': 'Close preview' }, '×')),
      h('div', { className: 'efn-decision efn-decision-' + dec.tone },
        h('div', { className: 'efn-decision-l' }, dec.label),
        h('p', { className: 'efn-decision-p' }, mainReason(s))),
      h('div', { className: 'efn-dwactions' },
        closed
          ? h('button', { className: 'ent-btn ent-btn--primary', onClick: function () { p.onFindSimilar && p.onFindSimilar(o); } }, 'Find similar open opportunities')
          : h('button', { className: 'ent-btn ent-btn--primary', onClick: function () { setTab('eligibility'); } }, 'Review eligibility'),
        closed
          ? h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { mx.store.addAlert({ scope: 'funder:' + o.funder, lens: 'funding' }); p.toast && p.toast('Monitoring future rounds from ' + o.funder); } }, 'Monitor future rounds')
          : h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { p.onSave && p.onSave(o); } }, 'Save'),
        o.official_url ? h('a', { className: 'ent-btn ent-btn--sm', href: o.official_url, target: '_blank', rel: 'noopener' }, 'View source') : null),
      h('div', { className: 'efn-dwtabs' }, [['summary', 'Summary'], ['eligibility', 'Eligibility'], ['evidence', 'Evidence'], ['changes', 'Changes']].map(function (t) {
        return h('button', { key: t[0], className: 'efn-dwtab' + (tab === t[0] ? ' on' : ''), onClick: function () { setTab(t[0]); } }, t[1]);
      })),
      tab === 'eligibility' ? elig : tab === 'evidence' ? evidence : tab === 'changes' ? changes : summary);
  }

  function CoverageDrawer(stats, close) {
    function row(k, v) { return h('div', { className: 'ei-dim' }, h('span', null, k), h('b', null, v)); }
    function n(v, d) { return v != null ? (typeof v === 'number' ? v.toLocaleString() : v) : (d != null ? d : '…'); }
    return h('div', { style: { position: 'fixed', inset: 0, zIndex: 60, display: 'flex', justifyContent: 'flex-end' } },
      h('div', { style: { position: 'absolute', inset: 0, background: 'rgba(26,23,18,.28)' }, onClick: close }),
      h('div', { className: 'ent-glass ent-glass--panel', style: { position: 'relative', width: 'min(440px,92vw)', height: '100%', overflow: 'auto', borderRadius: 0, padding: '20px 22px' } },
        h('div', { style: { display: 'flex', justifyContent: 'space-between', marginBottom: 8 } }, h('h3', { style: { margin: 0, fontFamily: 'var(--ent-serif)', fontSize: 18 } }, 'Coverage details'), h('button', { className: 'efn-iconbtn', onClick: close }, '×')),
        h('p', { className: 'ent-muted', style: { fontSize: 12.5, marginTop: 0 } }, 'Database records are what Mira has actually ingested — distinct from currently-open opportunities. All figures are live server-side counts; nothing here is an upstream claim.'),
        h('div', { style: { marginTop: 8 } },
          row('Canonical records', n(stats && stats.total)),
          row('Verified open', n(stats && stats.open)),
          row('Forecast', n(stats && stats.forecast)),
          row('Historical / closed', n(stats && stats.historical, 0)),
          row('Funders represented', n(stats && stats.funders)),
          row('Active source connectors', n(stats && stats.sources)),
          row('Last ingestion', (stats && stats.updated) || 'unknown')),
        h('div', { className: 'ei-cov', style: { marginTop: 14 } }, h('b', null, n(stats && stats.total, 0) + ' canonical records currently in Mira.'), ' Coverage is incomplete — South Asia and local-language sources remain limited. Mira never displays an upstream “available” total it has not ingested, deduplicated and verified.')));
  }

  // Advanced filter drawer (§7) — only fields Mira has actually ingested.
  function MoreFiltersDrawer(filters, setFilters, funderOpts, countryOpts, close) {
    function setF(k, v) { var nf = Object.assign({}, filters); nf[k] = v; setFilters(nf); }
    function grp(title, node) { return h('div', { style: { marginBottom: 16 } }, h('div', { style: { fontSize: 11, fontWeight: 700, letterSpacing: '.06em', color: 'var(--ent-muted)', marginBottom: 7 } }, title), node); }
    function sel(k, label, opts) { return h('label', { style: { display: 'block', fontSize: 12, marginBottom: 9 } }, h('span', { style: { display: 'block', color: 'var(--ent-muted)', marginBottom: 3 } }, label), h('select', { value: filters[k] || '', onChange: function (e) { setF(k, e.target.value); }, style: { width: '100%', padding: '7px 9px', border: '1px solid var(--ent-border)', borderRadius: 8, background: '#fff' } }, [h('option', { key: '_', value: '' }, 'Any')].concat(opts.map(function (o) { return h('option', { key: o, value: o }, o); })))); }
    function num(k, label) { return h('label', { style: { flex: 1, fontSize: 12 } }, h('span', { style: { display: 'block', color: 'var(--ent-muted)', marginBottom: 3 } }, label), h('input', { type: 'number', value: filters[k], onChange: function (e) { setF(k, e.target.value); }, style: { width: '100%', padding: '7px 9px', border: '1px solid var(--ent-border)', borderRadius: 8 } })); }
    return h('div', { style: { position: 'fixed', inset: 0, zIndex: 60, display: 'flex', justifyContent: 'flex-end' } },
      h('div', { style: { position: 'absolute', inset: 0, background: 'rgba(26,23,18,.28)' }, onClick: close }),
      h('div', { className: 'ent-glass ent-glass--panel', style: { position: 'relative', width: 'min(420px,92vw)', height: '100%', overflow: 'auto', borderRadius: 0, padding: '20px 22px' } },
        h('div', { style: { display: 'flex', justifyContent: 'space-between', marginBottom: 12 } }, h('h3', { style: { margin: 0, fontFamily: 'var(--ent-serif)', fontSize: 18 } }, 'More filters'), h('button', { className: 'efn-iconbtn', onClick: close }, '×')),
        grp('Geography', h('div', null, sel('country', 'Country', countryOpts), sel('region', 'Region', ['South Asia', 'Southeast Asia', 'East Africa', 'West Africa', 'Sub-Saharan Africa', 'Middle East', 'Latin America', 'Europe', 'Global']))),
        grp('Funder', h('div', null, sel('funder', 'Funder', funderOpts), sel('funderType', 'Funder type', ['development bank', 'bilateral donor', 'multilateral', 'UN agency', 'foundation', 'government']))),
        grp('Opportunity', sel('status', 'Status', ['verified_open', 'forecast', 'likely_open', 'status_unclear'])),
        grp('Funding', h('div', { style: { display: 'flex', gap: 8 } }, num('amountMin', 'Award min £'), num('amountMax', 'Award max £'))),
        grp('Organisation fit', h('div', null, sel('sector', 'Sector', ['Health', 'Climate', 'Education', 'Humanitarian', 'Agriculture', 'Energy', 'Governance', 'Economy']), sel('elig', 'Applicant eligibility', ['appears', 'verify', 'ineligible', 'not']))),
        h('p', { className: 'ent-muted', style: { fontSize: 11.5, marginTop: 6 } }, 'Filters map to fields Mira has ingested. Registration jurisdiction, co-financing, consortium and legal-eligibility filters appear only once those fields are ingested — omitted now rather than shown as controls that do nothing.'),
        h('div', { style: { display: 'flex', gap: 8, marginTop: 8 } }, h('button', { className: 'ent-btn ent-btn--primary ent-btn--sm', style: { flex: 1 }, onClick: close }, 'Show results'), h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setFilters(emptyFilters()); } }, 'Reset'))));
  }
  function emptyFilters() { return { country: '', type: '', funder: '', global: false, region: '', status: '', funderType: '', sector: '', amountMin: '', amountMax: '', elig: '' }; }


  // Phase 7B kept the table workspace intact but it is no longer the default
  // Funding experience. It renders nothing unless something routes to it
  // deliberately; the conversational page below owns #/enterprise/funding.
  function FundingTableView(p) {
    var mx = MX(), org = mx.org();
    var st0 = React.useState(null); var stats = st0[0], setStats = st0[1];
    React.useEffect(function () { var on = true; mx.stats().then(function (s) { if (on) setStats(s); }); return function () { on = false; }; }, []);

    var u0 = readFundingUrl();
    var tabS = React.useState(u0.tab || 'recommended'); var tab = tabS[0], setTab = tabS[1];
    var fitS = React.useState(u0.fit || ''); var fit = fitS[0], setFit = fitS[1];
    var qS = React.useState(u0.q || ''); var q = qS[0], setQ = qS[1];
    var fS = React.useState(Object.assign(emptyFilters(), { country: u0.country || '', type: u0.type || '', funder: u0.funder || '', global: !!u0.global }));
    var filters = fS[0], setFilters = fS[1];
    var sortS = React.useState({ col: 'recommended', dir: 'asc' }); var sort = sortS[0], setSort = sortS[1];
    var pageS = React.useState(0); var page = pageS[0], setPage = pageS[1];
    var selS = React.useState(null); var sel = selS[0], setSel = selS[1];
    var selFullS = React.useState(null); var selFull = selFullS[0], setSelFull = selFullS[1];
    var dtS = React.useState('summary'); var dtab = dtS[0], setDtab = dtS[1];
    var covS = React.useState(false); var covOpen = covS[0], setCov = covS[1];
    var mfS = React.useState(false); var mfOpen = mfS[0], setMf = mfS[1];
    var resS = React.useState(null); var result = resS[0], setResult = resS[1];
    var errS = React.useState(null); var err = errS[0], setErr = errS[1];
    var facS = React.useState({ countries: [], funders: [], types: [] }); var facets = facS[0], setFacets = facS[1];
    var nonceS = React.useState(0); var nonce = nonceS[0], setNonce = nonceS[1];
    var savedS = React.useState(function () { return readSavedIds(); }); var savedIds = savedS[0], setSavedIds = savedS[1];
    var newS = React.useState(undefined); var newRelevant = newS[0], setNewRelevant = newS[1];
    React.useEffect(function () {
      var on = true;
      var since = new Date(Date.now() - 7 * 864e5).toISOString().slice(0, 10);
      mx.searchFunding({ page: 0, pageSize: 1, publishedFrom: since })
        .then(function (r) { if (on) setNewRelevant(r && r.pagination ? r.pagination.totalRecords : null); })
        .catch(function () { if (on) setNewRelevant(null); });
      return function () { on = false; };
    }, []);
    var reqRef = React.useRef ? React.useRef(0) : { current: 0 };
    var PAGE = 25;

    function setF(k, v) { var nf = Object.assign({}, filters); nf[k] = v; setFilters(nf); }
    function openPreview(o) { setSel(o); setSelFull(null); setDtab('summary'); }
    function closePreview() { setSel(null); setSelFull(null); }

    React.useEffect(function () { setPage(0); }, [tab, fit, q, filters, sort]);
    React.useEffect(function () {
      try {
        var u = new URLSearchParams();
        if (tab && tab !== 'recommended') u.set('tab', tab);
        if (fit) u.set('fit', fit);
        if (q) u.set('q', q);
        if (filters.country) u.set('country', filters.country);
        if (filters.type) u.set('type', filters.type);
        if (filters.funder) u.set('funder', filters.funder);
        if (filters.global) u.set('global', '1');
        var s = u.toString();
        history.replaceState(null, '', '#/enterprise/funding' + (s ? '?' + s : ''));
      } catch (e) {}
    }, [tab, fit, q, filters]);
    React.useEffect(function () { var on = true; mx.fundingFacets().then(function (f) { if (on) setFacets(f); }); return function () { on = false; }; }, []);

    // THE server query. Tabs and match filters are server parameters — the
    // browser never receives more than one page, and never filters the corpus.
    React.useEffect(function () {
      var myReq = ++reqRef.current;
      var tabDef = FUND_TABS_BY_KEY[tab] || {};
      var params = Object.assign({
        page: page, pageSize: PAGE, search: q || null,
        segment: fit ? FIT_TO_SEGMENT[fit] : null,
        country: filters.country || null, includeSupportedGlobal: !!filters.global,
        opportunityType: filters.type || null, funder: filters.funder || null,
        funderType: filters.funderType || null, region: filters.region || null,
        sector: filters.sector || null, elig: filters.elig || null,
        amountMin: filters.amountMin || null, amountMax: filters.amountMax || null,
        sortField: sort.col === 'recommended' ? 'deadline' : sort.col,
        sortDirection: sort.dir,
      }, tabDef.params || {});
      if (tab === 'saved') params = Object.assign(params, { pageSize: 100 });
      var t = setTimeout(function () {
        mx.searchFunding(params).then(function (r) { if (myReq !== reqRef.current) return; setResult(r); setErr(null); })
          .catch(function (e) { if (myReq !== reqRef.current) return; setErr((e && e.message) || 'Query failed'); });
      }, q ? 280 : 0);
      return function () { clearTimeout(t); };
    }, [tab, fit, page, q, filters, sort, nonce]);

    React.useEffect(function () {
      var on = true;
      if (sel && sel.id) mx.fundingRecord(sel.id).then(function (full) { if (on && full) setSelFull(full); });
      return function () { on = false; };
    }, [sel && sel.id]);

    var rawRows = ((result && result.records) || []);
    if (tab === 'saved') rawRows = rawRows.filter(function (o) { return savedIds.indexOf(o.id) !== -1; });
    var pageRows = rawRows.map(function (o) {
      var m = mx.match(o, org);
      var s = { o: o, m: m, seg: o._seg || segOf(m.overall) };
      s.full = (selFull && selFull.id === o.id) ? selFull : null;
      return s;
    });
    var counts = (result && result.counts) || {};
    var sumc = (result && result.summaryCounts) || {};
    var pg = (result && result.pagination) || { totalRecords: 0, totalPages: 1 };
    var total = pg.totalRecords || 0, pages = pg.totalPages || 1;
    var countryOpts = facets.countries || [], funderOpts = facets.funders || [], typeOpts = facets.types || [];

    function saveOpp(o) {
      var next = savedIds.indexOf(o.id) === -1 ? savedIds.concat([o.id]) : savedIds;
      setSavedIds(next); writeSavedIds(next);
      p.toast && p.toast('Saved — added to your Funding shortlist');
    }
    function findSimilar(o) {
      closePreview(); setTab('recommended'); setFit(''); setQ('');
      setFilters(Object.assign(emptyFilters(), { funder: o.funder || '' }));
      p.toast && p.toast('Showing current opportunities from ' + (o.funder || 'this funder'));
    }
    var pp = Object.assign({}, p, { onSave: saveOpp, onFindSimilar: findSimilar });

    var selMerged = sel ? Object.assign({}, sel, (selFull && selFull.id === sel.id) ? { summary: selFull.summary, evidence: selFull.evidence, sourceName: selFull.sourceName } : {}) : null;
    var selRow = selMerged ? (function () {
      var m = mx.match(selMerged, org);
      return { o: selMerged, m: m, seg: sel._seg || segOf(m.overall), full: (selFull && selFull.id === sel.id) ? selFull : null };
    })() : null;

    // ── header: compact, one primary action ─────────────────────────────────
    var header = h('div', { className: 'efn-head' },
      h('div', { style: { minWidth: 0 } },
        h('h1', { className: 'efn-h1' }, 'Funding'),
        h('p', { className: 'efn-sub' }, 'Find and prioritise grants, tenders, consulting opportunities and partnerships your organisation can still pursue.')),
      h('div', { className: 'efn-headacts' },
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { p.toast && p.toast('Saved views arrive with programme workspaces'); } }, 'Saved views'),
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setCov(true); } }, 'Coverage')));

    // ── metrics: four, each defined from the server response ────────────────
    // Every active record in Recommended carries one of the four review
    // recommendations (Do not pursue is confined to Historical), so the count of
    // things to review is the size of the Recommended set itself.
    var recommendedToReview = (tab === 'recommended' && result) ? total : null;
    var confirmedOpen = (sumc.verified_open != null || sumc.closing_soon != null)
      ? (sumc.verified_open || 0) + (sumc.closing_soon || 0)
      : (stats && stats.open != null ? stats.open : null);
    var closingSoon = (sumc.closing_soon != null) ? sumc.closing_soon
      : (stats && stats.closingSoon != null ? stats.closingSoon : null);
    var metrics = [
      { k: 'Recommended to review', v: recommendedToReview, t: 'Active opportunities classified Best fit, Good fit, Review or Needs information for your organisation.' },
      { k: 'Confirmed open', v: confirmedOpen, t: 'Records the source confirms are open or closing soon.' },
      { k: 'Closing soon', v: closingSoon, t: 'A subset of Confirmed open with a deadline approaching.' },
      { k: 'New relevant', v: newRelevant === undefined ? null : newRelevant, t: 'Active opportunities the source published in the last 7 days, using source_published_at — the publisher\u2019s own date, never Mira\u2019s ingestion date.' },
    ];
    var nums = h('div', { className: 'efn-metrics' }, metrics.map(function (mm, i) {
      return h('div', { key: i, className: 'efn-metric', title: mm.t },
        h('b', null, mm.v == null ? h('span', { className: 'efn-na' }, 'Not yet available') : mm.v.toLocaleString()),
        h('span', null, mm.k));
    }));

    // ── customer tabs ───────────────────────────────────────────────────────
    var tabs = h('div', { className: 'efn-tabs', role: 'tablist' }, FUND_TABS.map(function (t) {
      return h('button', {
        key: t.key, role: 'tab', 'aria-selected': tab === t.key,
        className: 'efn-tab' + (tab === t.key ? ' on' : ''),
        onClick: function () { setTab(t.key); setFit(''); },
      }, t.label);
    }));

    // ── match filters (Recommended only) ────────────────────────────────────
    var fitRow = tab === 'recommended' ? h('div', { className: 'efn-fits' }, FIT_FILTERS.map(function (f) {
      return h('button', {
        key: f.key || 'all', className: 'efn-fit' + (fit === f.key ? ' on' : ''),
        onClick: function () { setFit(f.key); },
        title: f.key === '' ? 'Every active opportunity awaiting a decision' : 'Filter to ' + f.label.toLowerCase() + ' matches',
      }, f.label);
    })) : null;

    // ── one compact filter row ──────────────────────────────────────────────
    var toolbar = h('div', { className: 'efn-filters' },
      h('div', { className: 'efn-search' },
        h('span', { className: 'efn-searchi' }, '⌕'),
        h('input', { value: q, placeholder: 'Search opportunities, funders, references…', onChange: function (e) { setQ(e.target.value); } })),
      h('select', { value: filters.country, onChange: function (e) { setF('country', e.target.value); }, 'aria-label': 'Implementation geography' },
        [h('option', { key: '_', value: '' }, 'All geographies')].concat(countryOpts.map(function (c) { return h('option', { key: c, value: c }, c); }))),
      h('select', { value: filters.type, onChange: function (e) { setF('type', e.target.value); }, 'aria-label': 'Opportunity type' },
        [h('option', { key: '_', value: '' }, 'All types')].concat(typeOpts.map(function (c) { return h('option', { key: c, value: c }, c); }))),
      h('select', { value: filters.funder, onChange: function (e) { setF('funder', e.target.value); }, 'aria-label': 'Funder' },
        [h('option', { key: '_', value: '' }, 'All funders')].concat(funderOpts.map(function (c) { return h('option', { key: c, value: c }, c); }))),
      h('select', { value: filters.deadlineWindow || '', onChange: function (e) { setF('deadlineWindow', e.target.value); }, 'aria-label': 'Deadline' },
        h('option', { value: '' }, 'Any deadline'), h('option', { value: '30' }, 'Next 30 days'), h('option', { value: '90' }, 'Next 90 days')),
      h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setMf(true); } }, 'More filters'),
      h('div', { className: 'efn-filtersp' }),
      h('label', { className: 'efn-chk' }, h('input', { type: 'checkbox', checked: !!filters.global, onChange: function (e) { setF('global', e.target.checked); } }), 'Include global'),
      h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setQ(''); setFit(''); setFilters(emptyFilters()); } }, 'Clear'));

    // ── results ─────────────────────────────────────────────────────────────
    function th(col, label, sortable) {
      return h('th', {
        className: sortable ? 'efn-sortable' : '', onClick: sortable ? function () { setSort({ col: col, dir: sort.col === col && sort.dir === 'asc' ? 'desc' : 'asc' }); } : null,
      }, label, sortable && sort.col === col ? h('span', null, sort.dir === 'asc' ? ' ▲' : ' ▼') : null);
    }
    var table = h('div', { className: 'efn-tablewrap' }, h('table', { className: 'efn-table' },
      h('thead', null, h('tr', null,
        th('opportunity', 'Opportunity', true), h('th', null, 'Funder'), h('th', null, 'Type'),
        h('th', null, 'Geography'), h('th', null, 'Route'), th('deadline', 'Deadline', true),
        h('th', null, 'Recommendation'), h('th', null, 'Main reason'), h('th', { style: { textAlign: 'right' } }, 'Actions'))),
      h('tbody', null, pageRows.map(function (s) {
        var o = s.o, dec = decisionOf(s);
        return h('tr', {
          key: o.id, className: (sel && sel.id === o.id) ? 'sel' : '', tabIndex: 0,
          onClick: function () { openPreview(o); },
          onKeyDown: function (e) { if (e.key === 'Enter') openPreview(o); },
        },
          h('td', null, h('div', { className: 'efn-otitle' }, displayTitle(o))),
          h('td', null, o.funder),
          h('td', null, h('span', { className: 'efn-type' }, preciseType(o))),
          h('td', null, geoLabel(o)),
          h('td', null, routeLabel(o)),
          h('td', null, dlLabel(o)),
          h('td', null, h('span', { className: 'efn-rec efn-rec-' + dec.tone }, dec.label)),
          h('td', { className: 'efn-reason' }, mainReason(s)),
          h('td', { style: { textAlign: 'right' } },
            h('button', {
              className: 'ent-btn ent-btn--sm', onClick: function (e) { e.stopPropagation(); saveOpp(o); },
            }, savedIds.indexOf(o.id) !== -1 ? 'Saved' : 'Save')));
      }))));

    var cards = h('div', { className: 'efn-cards' }, pageRows.map(function (s) {
      var o = s.o, dec = decisionOf(s);
      return h('div', { key: o.id, className: 'efn-card', onClick: function () { openPreview(o); } },
        h('div', { className: 'efn-card-t' }, displayTitle(o)),
        h('div', { className: 'efn-card-m' }, o.funder + ' · ' + preciseType(o)),
        h('div', { className: 'efn-card-m' }, geoLabel(o) + ' · ' + dlLabel(o)),
        h('div', { className: 'efn-card-r' }, h('span', { className: 'efn-rec efn-rec-' + dec.tone }, dec.label)),
        h('div', { className: 'efn-card-reason' }, mainReason(s)));
    }));

    var pager = h('div', { className: 'efn-pager' },
      h('span', null, total ? ('Showing ' + (page * PAGE + 1) + '–' + Math.min(total, (page + 1) * PAGE) + ' of ' + total.toLocaleString()) : ''),
      h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
        h('button', { className: 'ent-btn ent-btn--sm', disabled: page <= 0, onClick: function () { setPage(Math.max(0, page - 1)); } }, 'Prev'),
        h('span', null, (page + 1) + ' / ' + pages),
        h('button', { className: 'ent-btn ent-btn--sm', disabled: page >= pages - 1, onClick: function () { setPage(Math.min(pages - 1, page + 1)); } }, 'Next')));

    // ── empty states that stay useful ───────────────────────────────────────
    function altCard(label, segKey) {
      var n = counts[SEGMENT_COUNT_KEY[segKey]];
      if (!n) return null;
      return h('button', { key: segKey, className: 'efn-altcard', onClick: function () { setFit(SEGMENT_TO_FIT[segKey]); } },
        h('b', null, n), h('span', null, label));
    }
    var tabDefNow = FUND_TABS_BY_KEY[tab] || {};
    var empty;
    if (fit === 'best') {
      empty = h('div', { className: 'efn-empty' },
        h('h3', null, 'No opportunities currently have every critical eligibility requirement confirmed'),
        h('p', null, 'That is a statement about published evidence, not about your organisation. These are the next most useful groups:'),
        h('div', { className: 'efn-alts' }, [altCard('Good fit', 'review'), altCard('Review', 'monitoring'), altCard('Needs information', 'missing')].filter(Boolean)),
        h('div', { className: 'efn-emptyacts' },
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setFit('good'); } }, 'View good-fit opportunities'),
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setFit('info'); } }, 'Review unresolved eligibility'),
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { p.onOnboard && p.onOnboard(); } }, 'Complete matching profile')));
    } else if (tabDefNow.uncovered) {
      empty = h('div', { className: 'efn-empty' },
        h('h3', null, 'Mira does not cover ' + tabDefNow.label.toLowerCase() + ' yet'),
        h('p', null, 'Current coverage is World Bank procurement, consulting and forecast notices. This tab will populate when a source for ' + tabDefNow.label.toLowerCase() + ' is connected — Mira does not invent records to fill a category.'),
        h('div', { className: 'efn-emptyacts' },
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setTab('recommended'); } }, 'Back to Recommended'),
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setCov(true); } }, 'See coverage')));
    } else if (tab === 'saved') {
      empty = h('div', { className: 'efn-empty' },
        h('h3', null, 'Nothing saved yet'),
        h('p', null, 'Save an opportunity from the results list and it will appear here.'),
        h('div', { className: 'efn-emptyacts' }, h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setTab('recommended'); } }, 'Browse recommended')));
    } else {
      empty = h('div', { className: 'efn-empty' },
        h('h3', null, 'No opportunities match these filters'),
        h('p', null, filters.country && !filters.global
          ? 'Mira did not silently broaden the geography. ' + filters.country + ' has no directly-attributed records in the current source set.'
          : 'Try clearing a filter, or include globally-scoped opportunities.'),
        h('div', { className: 'efn-emptyacts' },
          filters.country && !filters.global ? h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setF('global', true); } }, 'Include global opportunities') : null,
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setQ(''); setFit(''); setFilters(emptyFilters()); } }, 'Clear filters')));
    }

    var skeleton = h('div', { className: 'efn-tablewrap', style: { padding: 16 } }, [0, 1, 2, 3, 4, 5, 6].map(function (i) {
      return h('div', { key: i, className: 'efn-skl', style: { margin: '11px 0', width: (94 - i * 6) + '%' } });
    }));
    var area;
    if (err) area = h('div', { className: 'efn-empty' }, h('h3', null, 'Funding could not load'), h('p', null, err),
      h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { setErr(null); setNonce(Date.now()); } }, 'Retry'));
    else if (!result) area = skeleton;
    else if (!pageRows.length) area = empty;
    else area = h('div', null, table, cards, pager);

    var sortNote = h('div', { className: 'efn-sortnote' },
      h('span', null, 'Sorted by '), h('b', null, 'Recommended for your organisation'),
      h('span', { className: 'ent-muted' }, ' — fit, then confirmed eligibility, then deadline. Mira does not estimate a chance of winning.'));

    var main = h('div', { className: 'efn-main' }, nums, tabs, fitRow, toolbar, tab === 'recommended' ? sortNote : null, area);

    return h('div', { className: 'efn-ws', 'data-ui-version': 'funding-decision-v2' },
      header,
      h('div', { className: 'efn-layout' + (selRow ? ' has-drawer' : '') },
        main,
        selRow ? FundingPreview(selRow, dtab, setDtab, closePreview, pp) : null),
      covOpen ? CoverageDrawer(stats, function () { setCov(false); }) : null,
      mfOpen ? MoreFiltersDrawer(filters, setFilters, funderOpts, countryOpts, function () { setMf(false); }) : null);
  }

  function PursuitsView(p) {
    var mx = MX(), org = mx.org();
    var apps = mx.store.apps();
    if (!apps.length) {
      var recs = p.opps.map(function (o) { return { o: o, m: mx.match(o, org) }; }).filter(function (x) { return x.m.overall === 'Strong candidate' || x.m.overall === 'Worth reviewing'; }).slice(0, 4);
      if (!recs.length) recs = p.opps.slice(0, 4).map(function (o) { return { o: o, m: mx.match(o, org) }; });
      return h('div', { className: 'ent-col ei-wide' },
        h(Head, { eyebrow: 'Pursuits', title: 'Pursuits', sub: 'Opportunities you are actively considering — none yet', actions: [h('button', { key: 'f', className: 'ent-btn ent-btn--primary ent-btn--sm', onClick: function () { go('#/enterprise/funding'); } }, 'Add from Funding')] }),
        h('div', { className: 'ent-card ent-card-pad', style: { marginBottom: 14 } }, h('div', { className: 'ei-sec', style: { marginTop: 0 } }, 'What Pursuits does'), h('p', { className: 'ent-muted', style: { fontSize: 13 } }, 'Pursuits is your decision-and-readiness workspace. Add an opportunity from Funding and Mira assesses readiness, flags unresolved requirements, suggests the next best action and tracks activity — without a manual pipeline to maintain.')),
        h('div', { className: 'ei-sec' }, 'Recommended to consider'), recs.map(function (s, i) { return h(OppRow, { key: i, opp: s.o, m: s.m }); }));
    }
    var rows = apps.map(function (a) { var opp = p.opps.filter(function (x) { return x.id === a.oppId; })[0]; return { a: a, opp: opp, st: mx.pursuitState(a, opp, org) }; });
    function inState(states) { return rows.filter(function (r) { return states.indexOf(r.st.state) !== -1; }); }
    var groups = [
      ['Needs decision', inState(['AWAITING DECISION'])],
      ['Deadline risk', inState(['DEADLINE RISK'])],
      ['Preparing', inState(['PREPARING'])],
      ['Assessing', inState(['ASSESSING'])],
      ['Watching', inState(['WATCHING'])],
      ['Submitted', inState(['SUBMITTED', 'WON', 'LOST', 'WITHDRAWN'])],
    ];
    var decisions = inState(['AWAITING DECISION']).length, drisk = inState(['DEADLINE RISK']).length, prep = inState(['PREPARING']).length;
    var summary = h('div', { style: { display: 'flex', gap: 10, margin: '2px 0 16px', flexWrap: 'wrap' } },
      [['Decisions required', decisions, '#8A5A16'], ['Deadline risks', drisk, '#9A3B2C'], ['Active preparation', prep, '#2F6B34'], ['Total pursuits', rows.length, 'var(--ent-ink)']].map(function (s, i) {
        return h('div', { key: i, style: { display: 'inline-flex', alignItems: 'baseline', gap: 7, padding: '7px 13px', borderRadius: 8, background: 'var(--ent-surface)', border: '1px solid var(--ent-border)' } }, h('span', { style: { fontSize: 17, fontWeight: 700, color: s[2] } }, s[1]), h('span', { style: { fontSize: 12, color: 'var(--ent-muted)', fontWeight: 600 } }, s[0]));
      }));
    // Workflow ROW (deliberately different from Funding discovery cards)
    function pRow(r) {
      var a = r.a, opp = r.opp, d = daysTo(a.deadline);
      var nba = opp ? mx.nextBestAction(a, opp, org) : 'Reconnect source';
      return h('div', { key: a.id, style: { display: 'grid', gridTemplateColumns: '1.6fr auto auto auto', gap: '4px 14px', alignItems: 'center', padding: '12px 14px', borderBottom: '1px solid var(--ent-border)', cursor: 'pointer' }, onClick: function () { go('#/enterprise/pursuits/' + a.id); } },
        h('div', null, h('div', { style: { fontWeight: 600, color: 'var(--ent-ink)', fontSize: 13.5 } }, a.title), h('div', { className: 'ei-meta' }, a.funder + (a.owner ? ' · ' + a.owner : ' · unassigned')), h('div', { style: { fontSize: 12, color: 'var(--ent-accent)', marginTop: 3 } }, '→ ' + nba)),
        h('span', { className: stateClass(r.st.state) }, r.st.state),
        h('span', { className: 'ent-muted', style: { fontSize: 12.5, whiteSpace: 'nowrap' } }, d != null ? d + 'd left' : 'no deadline'),
        h('span', { className: 'ent-btn ent-btn--sm' }, 'Open'));
    }
    return h('div', { className: 'ent-col ei-wide' },
      h(Head, { eyebrow: 'Pursuits', title: 'Pursuits', sub: rows.length + ' active · ' + decisions + ' need a decision · ' + drisk + ' at deadline risk' }),
      summary,
      groups.map(function (g) { if (!g[1].length) return null; return h('div', { key: g[0] }, h('div', { className: 'ei-grp' }, h('h3', null, g[0]), h('span', { className: 'ei-count' }, g[1].length)), h('div', { className: 'ent-card', style: { padding: '2px 4px' } }, g[1].map(pRow))); }));
  }

  function stateClass(s) { return 'ei-state ei-state-' + s.toLowerCase().replace(/[^a-z]/g, '').slice(0, 8); }
  function PursuitView(p) {
    var mx = MX(), apps = mx.store.apps(), app = apps.filter(function (x) { return x.id === p.id; })[0];
    var tk = React.useState(0); var setTick = tk[1];
    var noteSt = React.useState(''); var note = noteSt[0], setNote = noteSt[1];
    if (!app) return h('div', { className: 'ei-empty' }, h('h3', null, 'Pursuit not found'), h('button', { className: 'ent-btn', onClick: function () { go('#/enterprise/pursuits'); } }, 'Back to Pursuits'));
    var org = mx.org(), opp = p.opps.filter(function (x) { return x.id === app.oppId; })[0];
    var m = opp ? mx.match(opp, org) : null;
    var st = mx.pursuitState(app, opp, org);
    var ready = opp ? mx.readiness(app, opp, org) : [];
    var readyOk = ready.filter(function (r) { return r.status === 'verified'; }).length;
    var suggested = opp ? mx.suggestedTasks(app, opp, org) : [];
    var d = daysTo(app.deadline);
    var decIso = opp && opp.deadline ? new Date(new Date(opp.deadline).getTime() - 10 * 86400000).toISOString().slice(0, 10) : null;
    function upd(patch, activity) { mx.store.updateApp(app.id, Object.assign({ updatedAt: Date.now() }, patch)); if (activity) mx.store.addActivity(app.id, activity); setTick(Date.now()); }
    var left = h('div', null,
      h('div', { className: 'ei-sec', style: { marginTop: 0 } }, 'Mira’s assessment'),
      m ? h('div', { className: 'ent-card ent-card-pad' },
        h('div', { style: { fontSize: 13.5, marginBottom: 6 } }, h('b', null, 'Recommendation: '), mx.goNoGo(opp, org).recommendation),
        m.reasons.length ? h('div', { style: { fontSize: 12.5, color: '#2F6B34' } }, 'Confirmed strengths: ' + m.reasons.join('; ')) : null,
        m.concerns.length ? h('div', { style: { fontSize: 12.5, color: m.hardFail ? '#9A3B2C' : '#8A5A16' } }, (m.hardFail ? 'Confirmed blockers: ' : 'Concerns: ') + m.concerns.join('; ')) : null,
        m.missing.length ? h('div', { style: { fontSize: 12.5 }, className: 'ent-muted' }, 'Unresolved questions: ' + m.missing.join('; ')) : null,
        h('div', { className: 'ent-muted', style: { fontSize: 11.5, marginTop: 6 } }, 'Confidence ' + (m.hardFail ? 'high' : (m.missing.length >= 4 ? 'low' : 'medium')) + ' · last verified ' + (opp.posted ? String(opp.posted).slice(0, 10) : 'unknown'))) : h('p', { className: 'ent-muted' }, 'Source opportunity no longer available.'),
      h('div', { className: 'ei-sec' }, 'Next best action'),
      h('div', { className: 'ei-nba' }, opp ? mx.nextBestAction(app, opp, org) : 'Reconnect the source opportunity'),
      h('div', { className: 'ei-sec' }, 'Readiness'),
      h('div', { className: 'ent-card ent-card-pad' }, ready.map(function (r, i) {
        return h('div', { key: i, className: 'ei-rd' }, h('span', { className: 'ei-dot ei-dot-' + r.status }), h('b', { style: { minWidth: 190 } }, r.label), h('span', { className: 'ent-muted', style: { fontSize: 12 } }, r.note));
      }), h('div', { className: 'ent-muted', style: { fontSize: 11.5, marginTop: 6 } }, readyOk + ' of ' + ready.length + ' components verified — the rest need confirmation (no single percentage; each is explained above).')),
      m && m.missing.length ? h('div', null, h('div', { className: 'ei-sec' }, 'Missing information'), h('ul', null, m.missing.map(function (mi, i) { return h('li', { key: i, style: { fontSize: 13 } }, h('b', null, mi), ' — ', h('span', { className: 'ent-muted' }, 'resolve from the official applicant guidance; assign to the funding lead.')); }))) : null,
      h('div', { className: 'ei-sec' }, 'Suggested tasks'),
      suggested.length ? suggested.map(function (t, i) {
        return h('div', { key: i, className: 'ei-task' }, h('span', { style: { flex: 1 } }, t),
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { upd({ tasks: (app.tasks || []).concat([{ text: t, done: false }]) }, 'Accepted task: ' + t); } }, 'Accept'),
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { upd({ dismissedTasks: (app.dismissedTasks || []).concat([t]) }); } }, 'Dismiss'));
      }) : h('p', { className: 'ent-muted', style: { fontSize: 13 } }, 'No further tasks suggested — all evidence gaps are either accepted or resolved.'),
      (app.tasks || []).length ? h('div', null, h('div', { style: { fontSize: 12, fontWeight: 700, color: 'var(--ent-muted)', margin: '10px 0 4px' } }, 'ACCEPTED TASKS'),
        (app.tasks || []).map(function (t, i) { return h('label', { key: i, className: 'ei-task' }, h('input', { type: 'checkbox', checked: !!t.done, onChange: function () { var ts = (app.tasks || []).slice(); ts[i] = Object.assign({}, t, { done: !t.done }); upd({ tasks: ts }, (t.done ? 'Reopened' : 'Completed') + ' task: ' + t.text); } }), h('span', { style: { textDecoration: t.done ? 'line-through' : 'none', color: t.done ? 'var(--ent-faint)' : 'var(--ent-body)' } }, t.text)); })) : null);
    var right = h('div', { className: 'ei-panel' },
      h('div', { style: { fontSize: 11, fontWeight: 700, letterSpacing: '.06em', color: 'var(--ent-muted)', marginBottom: 8 } }, 'PURSUIT CONTROL'),
      h('dl', { className: 'ei-kv', style: { gridTemplateColumns: '1fr auto', fontSize: 12.5 } },
        h('dt', null, 'State'), h('dd', null, h('span', { className: stateClass(st.state) }, st.state)),
        h('dt', null, 'Deadline'), h('dd', null, app.deadline ? String(app.deadline).slice(0, 10) : '—'),
        h('dt', null, 'Suggested decision by'), h('dd', null, decIso || '—'),
        h('dt', null, 'Owner'), h('dd', null, app.owner || h('span', { className: 'ent-muted' }, 'Unassigned'))),
      h('input', { className: 'ei-search', style: { fontSize: 13, padding: '8px 11px', marginTop: 8, width: '100%' }, placeholder: 'Assign owner', defaultValue: app.owner || '', onBlur: function (e) { var v = e.target.value.trim(); if (v && v !== app.owner) upd({ owner: v }, 'Assigned to ' + v); } }),
      h('div', { style: { fontSize: 12, fontWeight: 700, color: 'var(--ent-muted)', margin: '12px 0 4px' } }, 'DECISION (user-confirmed)'),
      h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { upd({ decision: 'Go' }, 'Go decision recorded'); } }, 'Record: Go'),
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { upd({ decision: 'No-go', stage: 'Withdrawn' }, 'No-go decision recorded'); } }, 'Record: No-go'),
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { upd({ stage: 'Submitted' }, 'Marked as submitted (user-confirmed)'); } }, 'Mark submitted')),
      h('div', { className: 'ent-muted', style: { fontSize: 11, marginTop: 4 } }, 'Submission and outcomes are only ever user- or integration-confirmed — never inferred.'),
      h('div', { style: { fontSize: 12, fontWeight: 700, color: 'var(--ent-muted)', margin: '12px 0 4px' } }, 'NOTES'),
      (app.notes || []).map(function (n, i) { return h('div', { key: i, style: { fontSize: 12.5, padding: '4px 0', borderBottom: '1px solid var(--ent-border)' } }, h('span', { className: 'ent-muted', style: { fontSize: 11 } }, new Date(n.at).toLocaleDateString('en-GB') + ' · '), n.text); }),
      h('form', { onSubmit: function (e) { e.preventDefault(); if (!note.trim()) return; upd({ notes: (app.notes || []).concat([{ text: note.trim(), at: Date.now() }]) }, 'Note added'); setNote(''); }, style: { display: 'flex', gap: 6, marginTop: 6 } }, h('input', { className: 'ei-search', style: { flex: 1, fontSize: 12.5, padding: '7px 10px' }, placeholder: 'Add a note…', value: note, onChange: function (e) { setNote(e.target.value); } }), h('button', { className: 'ent-btn ent-btn--sm' }, 'Add')),
      opp && opp.url ? h('a', { className: 'ent-btn ent-btn--sm', href: opp.url, target: '_blank', rel: 'noopener', style: { display: 'block', textAlign: 'center', marginTop: 10 } }, 'Open official application ↗') : null,
      h('div', { style: { fontSize: 12, fontWeight: 700, color: 'var(--ent-muted)', margin: '14px 0 4px' } }, 'ACTIVITY'),
      h('div', { className: 'ei-tl' }, (app.activity || []).slice().reverse().map(function (a, i) {
        return h('div', { key: i, className: 'ei-tl-item' }, h('div', { style: { fontSize: 11, color: 'var(--ent-faint)' } }, new Date(a.at).toLocaleDateString('en-GB', { day: 'numeric', month: 'short' })), h('div', { style: { fontSize: 12.5, color: a.mira ? 'var(--ent-accent)' : 'var(--ent-body)' } }, a.text));
      })));
    return h('div', { className: 'ent-col ei-wide' },
      h('a', { className: 'ei-back', onClick: function () { go('#/enterprise/pursuits'); } }, '← Back to Pursuits'),
      h(Head, { eyebrow: app.funder, title: app.title, sub: [h('span', { key: 's', className: stateClass(st.state) }, st.state), ' ', h('span', { key: 'src', className: 'ent-muted', style: { fontSize: 12 } }, st.source), d != null ? ' · deadline in ' + d + ' days' : '', ' · readiness ' + readyOk + '/' + ready.length],
        actions: [opp ? h('button', { key: 'r', className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/opportunity/' + opp.id); } }, 'View opportunity') : null, h('button', { key: 'a', className: 'ent-btn ent-btn--sm', onClick: function () { upd({ stage: 'Withdrawn', archived: true }); p.toast && p.toast('Archived'); go('#/enterprise/pursuits'); } }, 'Archive')] }),
      h('div', { className: 'ei-84' }, left, right));
  }

  function TodayView(p) {
    var mx = MX(), org = mx.org();
    var apps = mx.store.apps(), saves = mx.store.saves(), pins = mx.store.pins();
    var dueThisWeek = apps.filter(function (a) { return a.deadline && daysTo(a.deadline) != null && daysTo(a.deadline) >= 0 && daysTo(a.deadline) <= 7; });
    var decisions = apps.filter(function (a) { return a.stage === 'Go / no-go' || a.stage === 'Reviewing'; });
    // Grounded status counts (§3/§6). No real risk-change feed exists, so Needs
    // action is 0 — we never fabricate high-priority signals.
    var needsAction = dueThisWeek.length;                 // real: imminent deadlines
    var needsReview = saves.length + decisions.length;    // real: saved + decisions pending
    var monitoring = pins.length;
    // Executive summary derived from real workflow data only.
    var bits = [];
    if (saves.length) bits.push(saves.length + ' saved ' + (saves.length === 1 ? 'opportunity' : 'opportunities') + ' to review');
    if (apps.length) bits.push(apps.length + ' application' + (apps.length === 1 ? '' : 's') + ' in progress');
    if (dueThisWeek.length) bits.push(dueThisWeek.length + ' deadline' + (dueThisWeek.length === 1 ? '' : 's') + ' this week');
    var summary = (bits.length ? bits.join(', ') + '. ' : 'Nothing in your pipeline yet. ') + 'No programme risks currently require action.';
    // ── Command-centre framing (Phase 1) ─────────────────────────────────────
    // A calm greeting, a single count of what genuinely deserves attention, and
    // one Mira recommendation — all derived ONLY from real workflow data. Mira
    // never invents an opportunity, a deadline or an outcome it cannot see.
    var hr = new Date().getHours();
    var greeting = hr < 12 ? 'Good morning.' : hr < 18 ? 'Good afternoon.' : 'Good evening.';
    // Dedupe: an item can be both due-this-week AND a pending decision; it must
    // only appear once in "what needs attention".
    var attnItems = dueThisWeek.concat(decisions).filter(function (a, i, arr) {
      return arr.findIndex(function (b) { return b.id === a.id; }) === i;
    });
    var attnLine = attnItems.length
      ? attnItems.length + ' thing' + (attnItems.length === 1 ? '' : 's') + ' deserve' + (attnItems.length === 1 ? 's' : '') + ' your attention.'
      : 'Nothing needs your immediate attention today.';
    var nearest = dueThisWeek.slice().sort(function (a, b) { return daysTo(a.deadline) - daysTo(b.deadline); })[0];
    var miraRec;
    if (nearest) {
      var nd = daysTo(nearest.deadline);
      miraRec = 'I’d focus on ' + nearest.title + ' first — its deadline is ' + (nd === 0 ? 'today' : 'in ' + nd + ' day' + (nd === 1 ? '' : 's')) + ' and it is already in your pipeline.';
    } else if (decisions.length) {
      miraRec = 'A go/no-go decision on ' + decisions[0].title + ' is waiting — resolve it so the pursuit can move forward.';
    } else if (saves.length) {
      miraRec = 'You have ' + saves.length + ' saved ' + (saves.length === 1 ? 'opportunity' : 'opportunities') + ' to review. Start there when you have a moment.';
    } else {
      miraRec = 'Nothing is time-sensitive right now — a good next step is to find funding that fits your programmes.';
    }
    var status = h('div', { style: { display: 'flex', gap: 10, margin: '4px 0 18px', flexWrap: 'wrap' } },
      [['Needs action', needsAction, '#9A3B2C', '#F6E4DF'], ['Needs review', needsReview, '#8A5A16', '#F3ECD9'], ['Monitoring', monitoring, 'var(--ent-muted)', 'var(--ent-tint,#EEE)']].map(function (s, i) {
        return h('div', { key: i, style: { display: 'inline-flex', alignItems: 'baseline', gap: 7, padding: '7px 13px', borderRadius: 8, background: s[3], border: '1px solid var(--ent-border)' } },
          h('span', { style: { fontSize: 18, fontWeight: 700, color: s[2] } }, s[1]), h('span', { style: { fontSize: 12.5, color: 'var(--ent-body)', fontWeight: 600 } }, s[0]));
      }));
    // LEFT — what needs attention (real, at most 3)
    var attention;
    if (!attnItems.length) attention = h('div', { className: 'ent-card ent-card-pad', style: { textAlign: 'center', color: 'var(--ent-muted)' } }, h('div', { style: { fontWeight: 600, color: 'var(--ent-ink)', marginBottom: 4 } }, 'No developments require immediate action today'), h('div', { style: { fontSize: 13 } }, 'Mira flags an item here only when there is a plausible organisation action to take.'));
    else attention = h('div', null, attnItems.slice(0, 3).map(function (a, i) {
      var d = daysTo(a.deadline);
      return h('div', { key: i, className: 'ent-card ent-card-pad', style: { marginBottom: 8, borderLeft: '3px solid ' + (d != null && d <= 3 ? '#9A3B2C' : '#8A5A16') } },
        h('div', { style: { fontSize: 11, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ent-muted)', fontWeight: 700 } }, a.stage === 'Go / no-go' ? 'Decision required' : 'Deadline'),
        h('div', { style: { fontWeight: 600, color: 'var(--ent-ink)', margin: '2px 0' } }, a.title),
        h('div', { className: 'ei-meta' }, a.funder + (d != null ? ' · closes in ' + d + ' days' : '')),
        h('button', { className: 'ent-btn ent-btn--sm', style: { marginTop: 8 }, onClick: function () { go('#/enterprise/pursuits/' + a.id); } }, 'Open'));
    }));
    // RIGHT — workflow
    var workflow = h('div', { className: 'ent-card ent-card-pad' },
      h('div', { className: 'ei-sec', style: { marginTop: 0 } }, 'Your workflow'),
      h('dl', { className: 'ei-kv', style: { gridTemplateColumns: '1fr auto' } },
        h('dt', null, 'Application deadlines this week'), h('dd', null, dueThisWeek.length),
        h('dt', null, 'Go/no-go decisions required'), h('dd', null, decisions.length),
        h('dt', null, 'Saved opportunities to review'), h('dd', null, saves.length),
        h('dt', null, 'Applications in progress'), h('dd', null, apps.length)),
      h('div', { style: { display: 'flex', gap: 6, marginTop: 12, flexWrap: 'wrap' } },
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/pursuits'); } }, 'Open pipeline'),
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/funding'); } }, 'Find funding'),
        h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { p.toast && p.toast('Weekly briefing: generated from your board and pipeline'); } }, 'Create weekly briefing')));
    // Pinned board
    var board = pins.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill,minmax(240px,1fr))', gap: 12 } }, pins.map(function (pin, i) {
      return h('div', { key: i, className: 'ent-card ent-card-pad' },
        h('div', { style: { fontSize: 11, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ent-accent)', fontWeight: 700 } }, pin.entity + ' · ' + pin.lens),
        h('div', { style: { fontSize: 16, fontWeight: 700, color: 'var(--ent-ink)', margin: '2px 0' } }, pin.name),
        h('div', { className: 'ent-muted', style: { fontSize: 12 } }, 'No material changes since you pinned this'),
        h('div', { style: { display: 'flex', gap: 6, marginTop: 8 } },
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/' + (pin.entity === 'Funder' ? 'funder' : 'country') + '/' + encodeURIComponent(pin.name) + '/' + pin.lens); } }, 'Open'),
          h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { mx.store.unpin(pin.key); p.refresh && p.refresh(); } }, 'Unpin')));
    })) : h('div', { className: 'ent-card ent-card-pad' },
      h('div', { style: { fontWeight: 600, color: 'var(--ent-ink)', marginBottom: 4 } }, 'Build your intelligence board'),
      h('p', { className: 'ent-muted', style: { fontSize: 13, marginTop: 0 } }, 'Pin an entity-lens view — a country’s funding or a funder’s briefing — and Today will show only what changed.'),
      h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } }, (org.countries || ['Pakistan', 'Kenya']).slice(0, 3).map(function (c) { return h('button', { key: c, className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/country/' + encodeURIComponent(c) + '/funding'); } }, c + ' · Funding'); }), h('button', { className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/funder/World%20Bank/funding'); } }, 'World Bank · Funding')));
    return h('div', { className: 'ent-col ei-wide' },
      h(Head, { eyebrow: new Date().toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' }), title: greeting, sub: attnLine, actions: [h('button', { key: 'c', className: 'ent-btn ent-btn--sm', onClick: function () { go('#/enterprise/search'); } }, 'Customise board')] }),
      status,
      h('div', { className: 'ent-card ent-card-pad', style: { display: 'flex', gap: 12, alignItems: 'flex-start', borderLeft: '3px solid var(--ent-accent)', margin: '0 0 18px' } },
        h('span', { style: { fontSize: 11, fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--ent-accent)', whiteSpace: 'nowrap', paddingTop: 2 } }, 'Mira'),
        h('div', null,
          h('div', { style: { color: 'var(--ent-ink)', fontSize: 14, lineHeight: 1.5 } }, miraRec),
          h('div', { className: 'ent-muted', style: { fontSize: 11.5, marginTop: 4 } }, 'Based only on your pipeline and deadlines. Mira never infers an outcome it cannot see.'))),
      h('div', { className: 'ei-today', style: { display: 'grid', gridTemplateColumns: '1.6fr 1fr', gap: 18, alignItems: 'start' } },
        h('div', null, h('div', { className: 'ei-sec', style: { marginTop: 0 } }, 'What needs attention', attnItems.length ? h('span', { className: 'ent-muted', style: { fontWeight: 400, fontSize: 12, marginLeft: 8 } }, 'Filtered from everything Globally is watching') : null), attention),
        workflow),
      h('div', { className: 'ei-sec' }, 'Your pinned intelligence'), board);
  }

  // Async loader wrapper so screens have real opportunities before rendering.
  // §4B: Funding is the exception — it queries the server one page at a time and
  // reads nothing from the bulk set, so it must neither wait for nor trigger the
  // whole-dataset fetch. Every other screen still genuinely needs p.opps.

  // ══ Conversational Mira Funding ═══════════════════════════════════════════
  // Preview-first: the whole experience works with no Supabase session. When a
  // session exists the same calls go through MX.createFundingConversation /
  // MX.addFundingMessage / MX.listFundingShortlist (Checkpoint A) instead of the
  // in-memory adapter. Nothing here claims to have saved anything it has not.

  var MIRA_PREVIEW = {                        // browser-session conversation store
    convs: [], seq: 1,
    list: function () { return this.convs.filter(function (c) { return !c.archived; }); },
    create: function (title) {
      var c = { id: 'preview-' + (this.seq++), title: title || 'New conversation', messages: [], createdAt: Date.now() };
      this.convs.unshift(c); return c;
    },
    get: function (id) { for (var i = 0; i < this.convs.length; i++) if (this.convs[i].id === id) return this.convs[i]; return null; },
    rename: function (id, t) { var c = this.get(id); if (c) c.title = t; },
    archive: function (id) { var c = this.get(id); if (c) c.archived = true; },
  };

  // Honest acknowledgement. It never claims to have searched, never invents a
  // funder, an amount or an opportunity. It reflects what the user asked and
  // asks for the one dimension most likely to narrow the search.

  // Answers using what the profile already contains. It never asks for
  // organisation type, geography or programme area — those are known. The only
  // question it may ask is about a field onboarding genuinely does not collect.
  function miraAcknowledgement(text, pr) {
    if (!pr || !pr.has) {
      return 'I can help you find funding. Your organisation profile is not set up yet, so tell me the geography and programme area to prioritise — or complete your profile and I will use it automatically.';
    }
    var geo = (pr.countries.concat(pr.regions)).filter(Boolean);
    var lines = [];
    lines.push('I’ll look for institutional funding available to organisations like yours, prioritising your programme areas and operating geographies.');
    lines.push('');
    lines.push('I’m using:');
    if (pr.orgType) lines.push('• ' + pr.orgType + ' applicant');
    if (geo.length) lines.push('• ' + geo.slice(0, 4).join(', '));
    if (pr.programmes.length) lines.push('• ' + pr.programmes.join(', '));
    lines.push('• Institutional opportunities only');
    lines.push('');
    lines.push('I’ll exclude individual consultant roles, expired calls and opportunities outside your eligible applicant profile.');
    lines.push('');
    lines.push('Live opportunity results are not connected in this build yet, but your search criteria are ready.');
    if (pr.missing.length) {
      lines.push('');
      lines.push(pr.missing.indexOf('consortium or lead-only preference') !== -1
        ? 'One thing I do not have: should I include consortium opportunities, or only calls you can lead directly?'
        : 'One thing I do not have: your preferred funding range is not confirmed — should I include all award sizes?');
    }
    return lines.join('\n');
  }


  // Reads the onboarding profile and keeps provenance attached to every value.
  // selected_* were chosen by the user during onboarding = confirmed.
  // inferred_* came from the website scan = inferred, shown as such and never
  // presented to the user as something they told us.
  function readOrgProfile() {
    var prof = null;
    try {
      var all = JSON.parse(localStorage.getItem('ent_profile_v1') || '{}');
      var best = null;
      Object.keys(all).forEach(function (k) {
        var c = all[k]; if (!c) return;
        if (!best || (c.completedAt || 0) > (best.completedAt || 0)) best = c;
      });
      prof = best;
    } catch (e) {}
    if (!prof) return { has: false, confirmed: [], inferred: [], missing: [], criteria: {} };

    var confirmed = [], inferred = [];
    function add(list, label, value, meta) { if (value) list.push({ label: label, value: value, meta: meta || null }); }

    var orgType = (prof.derived && prof.derived.orgType) || prof.industry || null;
    add(confirmed, 'Organisation type', orgType);
    (prof.selected_regions || prof.regions || []).forEach(function (r) { add(confirmed, 'Region', r); });
    // Countries only ever come from the scan in the current onboarding flow.
    (prof.inferred_countries || []).forEach(function (c) {
      add(inferred, 'Country', c.value, { confidence: c.confidence, evidence: c.evidence, url: c.evidenceUrl });
    });
    // Programme areas: the top scan topics are the specific ones; the selected
    // list is broad news-monitoring interest, so prefer inferred topics here and
    // label them honestly rather than pretending they are a confirmed remit.
    (prof.inferred_topics || []).slice(0, 3).forEach(function (t) {
      add(inferred, 'Programme area', t.value, { confidence: t.confidence, evidence: t.evidence, url: t.evidenceUrl });
    });

    // Funding-specific fields onboarding never collects. These are the ONLY
    // things Mira has a legitimate reason to ask about.
    var missing = [];
    if (!prof.funding_range && !prof.preferred_amount) missing.push('preferred funding range');
    if (!prof.participation_routes) missing.push('consortium or lead-only preference');

    return {
      has: true,
      orgName: prof.companyName || null,
      orgType: orgType,
      regions: prof.selected_regions || prof.regions || [],
      countries: (prof.inferred_countries || []).map(function (c) { return c.value; }),
      programmes: (prof.inferred_topics || []).slice(0, 3).map(function (t) { return t.value; }),
      confirmed: confirmed, inferred: inferred, missing: missing,
      criteria: {
        applicantType: orgType, regions: prof.selected_regions || prof.regions || [],
        countries: (prof.inferred_countries || []).map(function (c) { return c.value; }),
        programmes: (prof.inferred_topics || []).slice(0, 3).map(function (t) { return t.value; }),
        institutionalOnly: true,
      },
    };
  }

  function MiraProfileStrip(p) {
    var pr = p.profile;
    if (!pr.has) return null;
    function chip(item, kind) {
      var t = kind === 'inferred'
        ? item.label + ': inferred from your website' + (item.meta && item.meta.confidence ? ' (' + item.meta.confidence + '% confidence)' : '') + (item.meta && item.meta.evidence ? ' — ' + item.meta.evidence : '')
        : item.label + ': confirmed during onboarding';
      return h('span', { key: kind + item.value, className: 'mfx-chip mfx-chip-' + kind, title: t }, item.value);
    }
    return h('div', { className: 'mfx-profile' },
      h('span', { className: 'mfx-profile-mark' }, '✦'),
      h('div', { className: 'mfx-profile-body' },
        h('div', { className: 'mfx-profile-t' }, 'Mira is using your organisation profile'),
        h('div', { className: 'mfx-chips' },
          pr.confirmed.map(function (c) { return chip(c, 'confirmed'); }),
          pr.inferred.map(function (c) { return chip(c, 'inferred'); }))),
      h('button', { className: 'mfx-profile-edit', onClick: p.onEdit }, 'Edit profile'));
  }

  // Structured search state. Each message UPDATES this rather than starting a
  // new unrelated search, so refinement is cumulative.
  // A generic organisation-fit search uses the WHOLE confirmed scope, not just
  // the home country.
  //
  // This used to read `countries: slice(0, 1)` — "one country reads best in the
  // RPC" — with includeGlobal false. For an NGO whose confirmed profile is
  // {Sri Lanka, South Asia} that silently collapsed the query to "Sri Lanka
  // only": the confirmed region was passed to the RPC as an `org` ranking hint
  // and never as query scope, so regional and global-eligible calls were not
  // merely ranked lower, they were never searched. Mira then reported "no
  // suitable verified Sri Lanka-specific grant" as if that were the whole
  // answer, and offered to "search wider South Asia" — a region the user had
  // already confirmed.
  //
  // countryOnly is now an explicit user choice, never a side effect of having a
  // home country.
  function baseCriteria(pr) {
    return {
      countries: (pr.countries || []).slice(),
      regions: pr.regions || [],
      programmes: pr.programmes || [],
      status: null, includeForecasts: false,
      includeGlobal: true,          // Lane 3 — global calls that accept this country
      countryOnly: false,           // only true when the user explicitly asks
      amountMin: null, amountMax: null, routes: [], limit: 6, page: 0,
    };
  }

  // The three lanes a generic organisation-fit search runs concurrently. They
  // are reported separately: a regional call must never be presented as though
  // it were country-specific.
  var SEARCH_LANES = [
    { id: 'country',  label: function (c) { return c + '-specific'; } },
    { id: 'regional', label: function (c, r) { return (r || 'Regional') + ' calls accepting ' + c; } },
    { id: 'global',   label: function (c) { return 'Global calls accepting ' + c; } },
  ];

  // Per-lane query parameters, derived from one set of criteria.
  function laneParams(lane, cr, pr) {
    var params = criteriaToParams(cr, pr);
    if (lane === 'country') {
      params.region = null;
      params.includeSupportedGlobal = false;
    } else if (lane === 'regional') {
      // Regional lane is scoped by the confirmed region, not the home country —
      // otherwise it just repeats Lane 1.
      params.country = null;
      params.region = (cr.regions && cr.regions[0]) || (pr.regions || [])[0] || null;
      params.includeSupportedGlobal = false;
    } else if (lane === 'global') {
      params.country = (cr.countries && cr.countries[0]) || null;
      params.region = null;
      params.includeSupportedGlobal = true;   // requires explicit eligibility evidence server-side
      params.globalOnly = true;
    }
    return params;
  }

  // Which lanes to run. An explicit "Sri Lanka only" collapses to Lane 1; a
  // profile with no confirmed region has no Lane 2 to run.
  function lanesFor(cr, pr) {
    if (cr.countryOnly) return ['country'];
    var lanes = ['country'];
    if ((cr.regions && cr.regions.length) || (pr.regions || []).length) lanes.push('regional');
    if (cr.includeGlobal !== false) lanes.push('global');
    return lanes;
  }
  // Turn a message into criteria changes. Deliberately small and predictable —
  // it never guesses a country or sector the profile does not already hold.
  function applyIntent(cr, text, pr) {
    var t = String(text || '').toLowerCase();
    var next = Object.assign({}, cr), touched = false;
    if (/only (currently )?open|currently open|open only/.test(t)) { next.status = 'verified_open'; next.includeForecasts = false; touched = true; }
    // Narrowing to the home country is a deliberate instruction, never a default.
    // Matches "Sri Lanka only", "only country-specific", "no regional calls",
    // "don't include global calls".
    var homeCountry = (pr && pr.countries && pr.countries[0]) || null;
    if ((homeCountry && new RegExp(homeCountry.toLowerCase() + '\\s+only').test(t))
        || /only country[- ]specific|country[- ]specific only/.test(t)
        || /(no|don'?t include|exclude|without)\s+(regional|global|wider)/.test(t)) {
      next.countryOnly = true; next.includeGlobal = false; touched = true;
    }
    if (/include (regional|global|wider)|search wider|also (regional|global)/.test(t)) {
      next.countryOnly = false; next.includeGlobal = true; touched = true;
    }
    if (/include forecast|forecasts? too|upcoming/.test(t)) { next.includeForecasts = true; touched = true; }
    if (/closing within|next 90|90 days/.test(t)) { next.deadlineWindow = 90; touched = true; }
    if (/consorti/.test(t)) { next.routes = ['consortium_lead', 'consortium_member']; touched = true; }
    if (/directly|direct applicant|we can lead|lead on/.test(t)) { next.routes = ['direct_applicant', 'consortium_lead']; touched = true; }
    if (/show more|more results|next page/.test(t)) { next.limit = Math.min((next.limit || 6) + 6, 24); touched = true; }
    var amt = t.match(/(?:above|over|more than|minimum of)\s*[£$€]?\s*([\d.,]+)\s*(k|m|million|thousand)?/);
    if (amt) {
      var n = parseFloat(amt[1].replace(/,/g, ''));
      if (/^k|thousand/.test(amt[2] || '')) n *= 1e3;
      if (/^m|million/.test(amt[2] || '')) n *= 1e6;
      if (!isNaN(n)) { next.amountMin = n; touched = true; }
    }
    if (/global|worldwide/.test(t)) { next.includeGlobal = true; touched = true; }
    // A named geography only takes effect if the profile already knows it —
    // Mira does not invent an operating country from a passing mention.
    var known = (pr.countries || []).concat(pr.regions || []);
    for (var i = 0; i < known.length; i++) {
      if (known[i] && t.indexOf(String(known[i]).toLowerCase()) !== -1) { next.countries = [known[i]]; touched = true; break; }
    }
    (pr.programmes || []).forEach(function (pg) {
      if (t.indexOf(String(pg).toLowerCase()) !== -1) { next.programmes = [pg]; touched = true; }
      if (new RegExp('remove\\s+' + String(pg).toLowerCase()).test(t)) {
        next.programmes = (next.programmes || []).filter(function (x) { return x !== pg; }); touched = true;
      }
    });
    next._touched = touched;
    return next;
  }

  // Criteria → the existing server RPC parameters. Nothing is filtered in the
  // browser; only the requested page ever crosses the wire.
  function criteriaToParams(cr, pr) {
    var params = {
      page: cr.page || 0,
      // Fetch a wider page than we display: the deadline sort surfaces open
      // (future-dated) records first, and the client then drops any that have
      // closed and ranks the rest by fit — so a full page of live options
      // survives that filtering.
      pageSize: Math.max(cr.limit || 6, 18),
      country: (cr.countries && cr.countries[0]) || null,
      includeSupportedGlobal: !!cr.includeGlobal,
      // descending: future deadlines (open calls) first, expired last — the RPC
      // sorts null/rolling deadlines to the far end either way. The old ascending
      // sort surfaced the most-expired records first, which is why closed grants
      // led the results.
      sortField: 'deadline', sortDirection: 'desc',
      org: { countries: (pr.countries || []).concat(pr.regions || []), kinds: orgKindsFor(pr), sectors: pr.programmes || [], preferredMinGBP: cr.amountMin || 0, preferredMaxGBP: cr.amountMax || 1e18 },
    };
    if (cr.status) params.status = cr.status;
    if (cr.includeForecasts) params.statuses = ['verified_open', 'closing_soon', 'forecast'];
    if (cr.amountMin) params.amountMin = cr.amountMin;
    if (cr.deadlineWindow) {
      var d = new Date(Date.now() + cr.deadlineWindow * 864e5);
      params.deadlineTo = d.toISOString().slice(0, 10);
    }
    return params;
  }
  function orgKindsFor(pr) {
    var t = String(pr.orgType || '').toLowerCase();
    var kinds = ['ngo', 'non-profit', 'nonprofit', 'civil society', 'charity'];
    if (/international/.test(t)) kinds.push('international ngo');
    return kinds;
  }

  // Recommendation band + evidence. Missing evidence yields Needs information,
  // never Not eligible — only an explicit prohibition does that.
  function bandFor(o, pr) {
    var geoHit = (o.countries || []).some(function (c) { return (pr.countries || []).concat(pr.regions || []).indexOf(c) !== -1; });
    var progHit = (o.sectors || []).some(function (s) { return (pr.programmes || []).indexOf(s) !== -1; });
    var elig = o.eligible_types || [];
    var stated = elig.length > 0;
    var eligHit = stated && elig.some(function (e) { return orgKindsFor(pr).indexOf(String(e).toLowerCase()) !== -1; });
    var why = [], concern = null;
    if (geoHit) why.push('Delivered in ' + (o.countries || []).filter(function (c) { return (pr.countries || []).concat(pr.regions || []).indexOf(c) !== -1; })[0]);
    if (progHit) why.push('Matches your ' + (o.sectors || []).filter(function (s) { return (pr.programmes || []).indexOf(s) !== -1; })[0].toLowerCase() + ' work');
    if (eligHit) why.push('Open to your organisation type');
    if ((o.participation_routes || []).indexOf('direct_applicant') !== -1) why.push('You can apply directly');
    if (!stated) concern = 'Eligible applicant types are not stated in the notice';
    else if (!eligHit) concern = 'Stated applicants are ' + elig.slice(0, 2).join(', ') + ' — confirm you qualify';
    if (!concern && (o.amount_min == null && o.amount_max == null)) concern = 'Award amount is not stated';
    var band;
    if (!stated) band = 'Needs information';
    else if (eligHit && geoHit && progHit) band = 'Strong fit';
    else if (eligHit && (geoHit || progHit)) band = 'Good fit';
    else band = 'Review';
    // The old fallback was internal database wording about the record being an
    // active institutional opportunity in the stored set —
    // internal database wording that told the user nothing about fit. If no
    // organisation-specific evidence supports the result, say exactly that
    // rather than dressing up an absence of reasons as a reason.
    var noEvidence = !why.length;
    if (noEvidence) why.push('No organisation-specific match evidence — Mira surfaced this as an open opportunity only');
    // Every unresolved check states why it matters (§5B).
    var checks = [];
    if (!stated) checks.push('Eligible applicant types are not stated in the notice — confirm whether locally registered NGOs may apply directly.');
    else if (!eligHit) checks.push('Stated applicants are ' + elig.slice(0, 3).join(', ') + ' — confirm your organisation type qualifies.');
    if (!geoHit) checks.push('Your confirmed geography is not named in the eligible countries — confirm the call accepts applicants based where you are.');
    if (!progHit && (pr.programmes || []).length) checks.push('The notice does not name your programme areas — confirm the scope covers your work.');
    if (o.amount_min == null && o.amount_max == null) checks.push('The award amount is not published — confirm the range before committing effort.');
    if (!(o.official_url || o.url)) checks.push('No official source link has been verified for this record.');
    return {
      band: band, why: why[0], whyAll: why, concern: concern, checks: checks,
      evidenceBacked: !noEvidence,
    };
  }

  function fmtAmount(o) {
    var v = o.amount_max != null ? o.amount_max : o.amount_min;
    if (v == null) return 'Amount not stated';
    var c = (o.currency || 'USD').toUpperCase();
    var sym = c === 'GBP' ? '£' : c === 'EUR' ? '€' : '$';
    return v >= 1e6 ? sym + (v / 1e6).toFixed(1).replace(/\.0$/, '') + 'm'
         : v >= 1e3 ? sym + Math.round(v / 1e3) + 'k' : sym + Math.round(v);
  }
  var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July',
    'August', 'September', 'October', 'November', 'December'];
  // "30 September 2026", not "2026-09-30".
  function longDate(v) {
    if (!v) return null;
    var d = new Date(String(v).slice(0, 10) + 'T00:00:00Z');
    if (isNaN(d)) return null;
    return d.getUTCDate() + ' ' + MONTHS[d.getUTCMonth()] + ' ' + d.getUTCFullYear();
  }
  function daysUntil(v) {
    return Math.ceil((new Date(String(v).slice(0, 10) + 'T00:00:00Z') - Date.now()) / 864e5);
  }
  // The old format produced "2026-07-27 · 0d" — an ISO string and a raw day
  // count, which reads as broken rather than urgent. A deadline is a decision
  // input: say what it means.
  function fmtDeadline(o) {
    if (!o.deadline) {
      if (o.status === 'forecast') {
        var f = longDate(o.forecast_open_at || o.source_published_at);
        return f ? 'Forecast — expected ' + f : 'Forecast';
      }
      return o.rolling ? 'Rolling deadline' : 'Deadline not stated';
    }
    var d = daysUntil(o.deadline), when = longDate(o.deadline) || String(o.deadline).slice(0, 10);
    if (d < 0) return 'Closed ' + when;
    if (d === 0) return 'Closes today';
    if (d === 1) return 'Closes tomorrow · ' + when;
    if (d <= 30) return 'Closes in ' + d + ' days · ' + when;
    return when;
  }
  // An expired call must never be presented as open, whatever its stored status.
  function isExpired(o) { return !!o.deadline && daysUntil(o.deadline) < 0; }
  function routeShort(o) {
    var r = o.participation_routes || [];
    if (r.indexOf('direct_applicant') !== -1) return 'Direct applicant';
    if (r.indexOf('consortium_lead') !== -1) return 'Consortium lead';
    if (r.indexOf('prime_contractor') !== -1) return 'Prime contractor';
    return 'Route not stated';
  }

  function MiraResultCard(p) {
    var o = p.o, b = p.band;
    return h('div', { className: 'mfx-card' },
      h('div', { className: 'mfx-card-top' },
        h('span', { className: 'mfx-band mfx-band-' + b.band.toLowerCase().replace(/\s+/g, '-') }, b.band),
        h('span', { className: 'mfx-card-dl' }, fmtDeadline(o))),
      h('div', { className: 'mfx-card-t' }, displayTitle(o)),
      // Same geography helper the modal uses — the card cannot claim a region
      // the modal reports as unknown.
      h('div', { className: 'mfx-card-m' }, decodeHtml(o.funder), ' · ', preciseType(o), ' · ', geoValue(o).v),
      h('div', { className: 'mfx-card-m' }, fmtAmount(o), ' · ', routeShort(o)),
      h('div', { className: 'mfx-card-why' }, h('b', null, 'Why it fits: '), b.why),
      b.concern ? h('div', { className: 'mfx-card-con' }, h('b', null, 'Check: '), b.concern) : null,
      h('div', { className: 'mfx-card-acts' },
        h('button', { className: 'mfx-card-btn', onClick: function () { p.onReview(o, b); } }, 'Review'),
        h('button', {
          className: 'mfx-card-btn' + (p.saved ? ' saved' : ' primary'),
          disabled: p.saved, onClick: function () { p.onSave(o, b); },
        }, p.saved ? 'Shortlisted' : 'Add to shortlist')));
  }

  // ── review modal ─────────────────────────────────────────────────────────
  // Every value here is read from the canonical opportunity record the search
  // returned — the same object the card renders, so the two cannot disagree.
  // Nothing is inferred, and no URL is ever constructed: a link is either
  // present in the record or the action is disabled.

  // An eligibility field's state, with the evidence that produced it. Absent
  // evidence is "Not stated", never a confirmed match.
  function evidenceState(match) {
    if (match === true) return { cls: 'ok', label: 'Confirmed match' };
    if (match === 'maybe') return { cls: 'maybe', label: 'Possible match' };
    if (match === 'need') return { cls: 'need', label: 'Needs verification' };
    if (match === false) return { cls: 'no', label: 'Mismatch' };
    return { cls: 'need', label: 'Not stated' };
  }

  // Geography shown here must match the group the card was filed under. A
  // record with no country evidence reports exactly that.
  function geoValue(o) {
    var c = (o.countries || []).filter(Boolean);
    if (c.length) return { v: c.join(', '), known: true };
    var impl = (o.implementation_countries || []).filter(Boolean);
    if (impl.length) return { v: impl.join(', ') + ' (implementation)', known: true };
    return { v: 'Not stated in the notice', known: false };
  }

  // Which support tools genuinely exist. An action with no working tool is shown
  // disabled and labelled, never dispatched — a menu item that produces a
  // refusal is worse than one that admits it is not built.
  var GRANT_ACTIONS = [
    { id: 'check_eligibility', label: 'Check our eligibility',
      note: 'Re-reads the stored official eligibility evidence against your confirmed profile.',
      available: true },
    { id: 'gono', label: 'Create go/no-go review',
      note: 'Needs the decision-record workflow.', available: false },
    { id: 'pursuit', label: 'Start a Pursuit',
      note: 'Needs the Pursuit workflow connected to the canonical key.', available: false },
    { id: 'checklist', label: 'Build an application checklist',
      note: 'Needs official guidance documents to be retrieved first.', available: false },
    { id: 'summarise', label: 'Summarise the application guidance',
      note: 'Needs official guidance documents to be retrieved first.', available: false },
  ];

  // The one tool that is real: a deterministic eligibility review computed from
  // the canonical record's stored evidence. It reaches no model and invents
  // nothing — if the evidence is absent it says so.
  function runGrantAction(action, o, band, profile) {
    var def = GRANT_ACTIONS.filter(function (a) { return a.id === action.action; })[0];
    if (!def || !def.available) {
      return { render: 'action_error',
        body: 'That action is not available yet. ' + ((def && def.note) || '') };
    }
    if (action.action !== 'check_eligibility') {
      return { render: 'action_error', body: 'That action is not available yet.' };
    }
    var ev = o.classification_evidence || {};
    var lines = ['ELIGIBILITY REVIEW — ' + o.title, ''];
    var mismatch = (ev.mismatchReasons || []);
    if (mismatch.length) {
      lines.push('Mismatched');
      mismatch.forEach(function (m) { lines.push('  • ' + m); });
      if (ev.evidence && ev.evidence.length && ev.evidence[0].quote) {
        lines.push('  Official wording: "' + ev.evidence[0].quote + '"');
      }
      lines.push('');
      lines.push('Conclusion: your organisation is not eligible for this call. '
        + 'Mira should not recommend it, and no application action is offered.');
      return { render: 'eligibility_review', body: lines.join('\n') };
    }
    lines.push('Unresolved');
    var checks = (band && band.checks) || [];
    if (checks.length) checks.forEach(function (c) { lines.push('  • ' + c); });
    else lines.push('  • Nothing unresolved was recorded for this notice.');
    lines.push('');
    lines.push(ev.eligibilityText
      ? 'Official eligibility text on file: ' + String(ev.eligibilityText).slice(0, 400)
      : 'No official eligibility text has been retrieved for this record, so the '
        + 'conclusion is Needs verification rather than eligible.');
    return { render: 'eligibility_review', body: lines.join('\n') };
  }

  function MiraSupportMenu(p) {
    return h('div', { className: 'mfx-sup', role: 'menu' },
      h('p', { className: 'mfx-sup-n' },
        'Mira prepares and organises your application. It does not submit it, '
        + 'sign in to funder portals, or guarantee eligibility.'),
      GRANT_ACTIONS.map(function (x) {
        return h('button', {
          key: x.id, className: 'mfx-sup-b', role: 'menuitem',
          disabled: !x.available,
          aria: undefined,
          style: x.available ? null : { opacity: .55, cursor: 'not-allowed' },
          onClick: x.available ? function () { p.onPick(x.id); } : null,
        }, x.label + (x.available ? '' : ' — coming later'), h('small', null, x.note));
      }));
  }

  function MiraReviewModal(p) {
    var o = p.o, b = p.band;
    var supS = React.useState(false), supOpen = supS[0], setSup = supS[1];
    var ref = React.useRef(null);
    var titleId = 'mfx-mod-title';

    // Escape to close, focus trap, and focus restored to the Review button.
    React.useEffect(function () {
      var prev = document.activeElement;
      var node = ref.current;
      if (node) {
        var first = node.querySelector('button, a[href], [tabindex]:not([tabindex="-1"])');
        if (first) first.focus();
      }
      function onKey(e) {
        if (e.key === 'Escape') { e.stopPropagation(); p.onClose(); return; }
        if (e.key !== 'Tab' || !node) return;
        var f = Array.prototype.filter.call(
          node.querySelectorAll('button:not(:disabled), a[href], summary, [tabindex]:not([tabindex="-1"])'),
          function (el) { return el.offsetParent !== null; });
        if (!f.length) return;
        var i = f.indexOf(document.activeElement);
        if (e.shiftKey && (i <= 0)) { e.preventDefault(); f[f.length - 1].focus(); }
        else if (!e.shiftKey && i === f.length - 1) { e.preventDefault(); f[0].focus(); }
      }
      document.addEventListener('keydown', onKey, true);
      var overflow = document.body.style.overflow;
      document.body.style.overflow = 'hidden';   // the page behind must not scroll
      return function () {
        document.removeEventListener('keydown', onKey, true);
        document.body.style.overflow = overflow;
        if (prev && prev.focus) prev.focus();
      };
    }, []);

    if (!o) return null;

    var expired = isExpired(o);
    // An explicit legal prohibition outranks everything else on screen. Where the
    // official notice excludes this organisation, Mira must not offer an Apply
    // action at all — the source stays reachable for reference, but encouraging
    // a Sri Lankan NGO to apply for a US-only grant is the failure this gate
    // exists to prevent.
    var mismatchReasons = ((o.classification_evidence || {}).mismatchReasons) || [];
    var explicitMismatch = mismatchReasons.length > 0;
    var geo = geoValue(o);
    var elig = (o.eligible_types || []).filter(Boolean);
    // normOpp flattens the record's official_url onto `url`, so reading only
    // official_url left every CTA disabled even though the corpus has an
    // official link on every grant. Read both, and never build one.
    var applyUrl = o.application_url || null;
    var srcUrl = o.official_url || o.url || null;
    var linkUrl = applyUrl || srcUrl;
    var verified = longDate(o.last_seen) || longDate(o.first_seen);

    function kg(label, value, known) {
      return h('div', { className: 'mfx-kg-i' },
        h('div', { className: 'mfx-kg-l' }, label),
        h('div', { className: 'mfx-kg-v' + (known === false ? ' unk' : '') }, value));
    }
    function erow(k, state, evidence) {
      var s = evidenceState(state);
      return h('div', { className: 'mfx-er' },
        h('div', { className: 'mfx-er-k' }, k),
        h('span', { className: 'mfx-st mfx-st-' + s.cls }, s.label),
        evidence ? h('div', { className: 'mfx-er-e' }, evidence) : null);
    }

    return h('div', {
      className: 'mfx-mod-back',
      onMouseDown: function (e) { if (e.target === e.currentTarget) p.onClose(); },
    },
      h('div', {
        className: 'mfx-mod', ref: ref, role: 'dialog', 'aria-modal': 'true',
        'aria-labelledby': titleId, 'data-ui-part': 'grant-review-modal-v1',
      },
        // ── header ──────────────────────────────────────────────────────────
        h('div', { className: 'mfx-mod-h' },
          h('div', { className: 'mfx-mod-htop' },
            h('span', { className: 'mfx-band mfx-band-' + b.band.toLowerCase().replace(/\s+/g, '-') },
              explicitMismatch ? 'Explicit mismatch' : (expired ? 'Closed' : b.band)),
            h('button', { className: 'mfx-mod-x', onClick: p.onClose, 'aria-label': 'Close grant review' }, '×')),
          h('h2', { className: 'mfx-mod-t', id: titleId }, o.title),
          h('p', { className: 'mfx-mod-f' }, o.funder || 'Grantmaker not stated'),
          o.summary_en
            ? h('p', { className: 'mfx-mod-desc' }, o.summary_en)
            : h('p', { className: 'mfx-mod-desc none' }, 'Description not available from the official source.')),

        h('div', { className: 'mfx-mod-body' },
          expired ? h('div', { className: 'mfx-pan-i check', style: { marginBottom: '18px' } },
            h('p', { className: 'mfx-pan-none' },
              'This call closed on ' + (longDate(o.deadline) || 'an earlier date')
              + '. It is shown for reference and cannot be applied for.')) : null,

          // ── key information grid ─────────────────────────────────────────
          h('div', { className: 'mfx-kg' },
            kg('Deadline', fmtDeadline(o), !!o.deadline || !!o.rolling),
            kg('Award', fmtAmount(o), !(o.amount_min == null && o.amount_max == null)),
            kg('Geography', geo.v, geo.known),
            kg('Grant type', preciseType(o), true),
            kg('Participation route', routeShort(o), (o.participation_routes || []).length > 0),
            kg('Eligible applicants', elig.length ? elig.join(', ') : 'Not stated in the notice', elig.length > 0)),

          // ── three decision panels ────────────────────────────────────────
          h('div', { className: 'mfx-pan' },
            h('div', { className: 'mfx-pan-i' },
              h('p', { className: 'mfx-pan-h' }, 'Why Mira selected this'),
              explicitMismatch
                ? h('p', { className: 'mfx-pan-none' },
                    'Mira should not recommend this opportunity to your organisation because '
                    + mismatchReasons.join(' ').replace(/^F/, 'f'))
                : b.evidenceBacked
                  ? h('ul', { className: 'mfx-pan-l' }, (b.whyAll || [b.why]).map(function (w, i) {
                      return h('li', { key: i }, w);
                    }))
                  : h('p', { className: 'mfx-pan-none' }, b.why)),
            h('div', { className: 'mfx-pan-i check' },
              h('p', { className: 'mfx-pan-h' }, 'What to check'),
              (b.checks || []).length
                ? h('ul', { className: 'mfx-pan-l' }, b.checks.map(function (c, i) {
                    return h('li', { key: i }, c);
                  }))
                : h('p', { className: 'mfx-pan-none' }, 'Nothing unresolved in the notice.')),
            h('div', { className: 'mfx-pan-i' },
              h('p', { className: 'mfx-pan-h' }, 'How to apply'),
              linkUrl
                ? h('ul', { className: 'mfx-pan-l' },
                    h('li', null, applyUrl
                      ? 'Apply through the funder’s official application page.'
                      : 'Open the official grant page for the application route.'),
                    verified ? h('li', null, 'Last verified ' + verified + '.') : null)
                : h('p', { className: 'mfx-pan-none' },
                    'No official application route has been verified for this record yet. '
                    + 'Mira will not guess one.'))),

          // ── eligibility review ───────────────────────────────────────────
          h('details', { className: 'mfx-ex' },
            h('summary', null, 'Eligibility review'),
            h('div', { className: 'mfx-ex-b' },
              erow('Organisation type',
                elig.length ? (b.band === 'Strong fit' || b.band === 'Good fit' ? true : 'maybe') : null,
                elig.length ? 'Notice states: ' + elig.join(', ') : 'The notice does not state eligible applicant types.'),
              erow('Applicant registration country',
                (o.eligible_applicant_countries || []).length ? 'maybe' : null,
                (o.eligible_applicant_countries || []).length
                  ? 'Eligible applicant countries: ' + o.eligible_applicant_countries.join(', ')
                  : 'No applicant-registration requirement stated.'),
              erow('Implementation geography',
                (o.implementation_countries || []).length ? true : null,
                (o.implementation_countries || []).length
                  ? 'Implemented in ' + o.implementation_countries.join(', ')
                  : 'Implementation geography not stated separately from applicant eligibility.'),
              erow('Participation route',
                (o.participation_routes || []).length ? true : null,
                (o.participation_routes || []).length
                  ? 'Routes: ' + o.participation_routes.join(', ') : 'Route not stated.'),
              erow('Co-financing', o.cofinancing == null ? null : (o.cofinancing ? 'maybe' : true),
                o.cofinancing == null ? 'Co-financing requirement not stated.'
                  : (o.cofinancing ? 'Co-financing is referenced — confirm the proportion required.'
                                   : 'No co-financing requirement stated.')))),

          // ── application requirements ─────────────────────────────────────
          h('details', { className: 'mfx-ex' },
            h('summary', null, 'Application requirements'),
            h('div', { className: 'mfx-ex-b' },
              h('p', { className: 'mfx-pan-none' },
                'Mira lists a requirement only where the official notice states it. '
                + 'No document requirements have been extracted for this record, and no '
                + 'Application Vault comparison has been run.'))),

          // ── official source and evidence ─────────────────────────────────
          h('details', { className: 'mfx-ex' },
            h('summary', null, 'Official source and evidence'),
            h('div', { className: 'mfx-ex-b' },
              erow('Grantmaker', o.funder ? true : null, o.funder || 'Not stated.'),
              erow('Official source', srcUrl ? true : null, srcUrl || 'No official source URL on this record.'),
              erow('Official application page', applyUrl ? true : null,
                applyUrl || 'No separate application URL recorded.'),
              erow('Published', o.source_published_at ? true : null,
                longDate(o.source_published_at) || 'Publication date not recorded.'),
              erow('Deadline', o.deadline ? true : null,
                longDate(o.deadline) || 'No deadline recorded.'),
              erow('Last verified', verified ? true : null,
                verified || 'Not recorded.')))),

        // ── sticky action bar ───────────────────────────────────────────────
        supOpen ? h(MiraSupportMenu, {
          onPick: function (k) { setSup(false); p.onSupport(k, o, b); },
        }) : null,
        h('div', { className: 'mfx-mod-foot' },
          linkUrl && !expired && !explicitMismatch
            ? h('a', {
                className: 'mfx-cta', href: linkUrl, target: '_blank', rel: 'noopener noreferrer',
              }, applyUrl ? 'Apply on official source' : 'Open official grant page',
                h('span', { 'aria-hidden': 'true' }, '↗'))
            : h('button', { className: 'mfx-cta', disabled: true, 'aria-disabled': 'true' },
                explicitMismatch ? 'Not eligible to apply'
                  : (expired ? 'Call closed' : 'Official application link not yet verified')),
          explicitMismatch && linkUrl
            ? h('a', { className: 'mfx-cta3', href: linkUrl, target: '_blank', rel: 'noopener noreferrer' },
                'Open official grant page')
            : null,
          !linkUrl && !expired
            ? h('button', {
                className: 'mfx-cta3',
                onClick: function () { p.onSupport('verify_link', o, b); },
              }, 'Ask Mira to verify')
            : null,
          h('button', {
            className: 'mfx-cta2', 'aria-haspopup': 'menu', 'aria-expanded': supOpen ? 'true' : 'false',
            onClick: function () { setSup(!supOpen); },
          }, 'Use Mira to support application'),
          h('div', { className: 'mfx-foot-sp' }),
          h('button', {
            className: 'mfx-cta3' + (p.saved ? ' on' : ''), disabled: !!p.saved,
            onClick: function () { p.onSave(o, b); },
          }, p.saved ? 'Shortlisted' : 'Add to shortlist'),
          h('button', { className: 'mfx-cta3', onClick: p.onClose }, 'Close'))));
  }
  // Short by policy: state the count and the one or two decisions that shaped
  // it. No profile restatement, no process description, no implementation talk.
  function summaryLine(banded, total, cr, pr) {
    if (!banded.length) return 'No active opportunities match every selected criterion.';
    var where = (cr.countries && cr.countries[0]) || (cr.regions && cr.regions[0]) || null;
    var parts = ['Found ' + banded.length + (total > banded.length ? ' of ' + total : '') + ' active ' + (banded.length === 1 ? 'opportunity' : 'opportunities') + (where ? ' in ' + where : '') + '.'];
    var strong = banded.filter(function (b) { return b.band.band === 'Strong fit'; }).length;
    var needs = banded.filter(function (b) { return b.band.band === 'Needs information'; }).length;
    if (strong) parts.push(strong + ' ' + (strong === 1 ? 'is a strong fit' : 'are strong fits') + '.');
    else if (needs === banded.length) parts.push('None state their eligible applicant types, so each needs checking.');
    if (cr.amountMin) parts.push('Filtered above ' + Math.round(cr.amountMin).toLocaleString() + '.');
    return parts.join(' ');
  }


  // ══ Mira Funding orchestrator ═════════════════════════════════════════════
  // Every message produces an ACTION PLAN before any customer-facing text is
  // written. Previously send() ran funding_search unconditionally and printed a
  // template, so "Which is best?" re-ran the identical query and reprinted the
  // identical six cards. Intent now decides whether the corpus is touched at all.

  // ── Eligibility verdicts ────────────────────────────────────────────────────
  // Three states, deliberately distinct. An explicit mismatch is NOT
  // "Needs information" — the notice has told us the customer cannot apply.
  var SUPPLIER_ROUTES = ['prime_contractor', 'subcontractor', 'supplier', 'contractor'];
  var SUPPLIER_TYPES = ['goods_procurement', 'works_procurement', 'non_consulting_services', 'prequalification'];
  var SUPPLIER_APPLICANTS = ['contractors', 'suppliers', 'manufacturers', 'vendors', 'firms'];

  function isNgoLike(pr) {
    return /ngo|non-?profit|charity|civil society|foundation|trust/i.test(String(pr.orgType || ''));
  }
  // Returns { state, reason } where state is eligible | needs_info | mismatch.
  function eligibilityVerdict(o, pr) {
    var elig = (o.eligible_types || []).map(function (e) { return String(e).toLowerCase(); });
    var type = String(o.opportunity_type || '');
    var routes = o.participation_routes || [];

    if (isNgoLike(pr)) {
      // Goods/works/equipment procurement is bought FROM suppliers. An NGO is
      // not the counterparty, so this is an explicit mismatch, not a maybe.
      if (SUPPLIER_TYPES.indexOf(type) !== -1) {
        return { state: 'mismatch', reason: 'a ' + preciseType(o).toLowerCase() + ' for suppliers and contractors, not programme funding' };
      }
      if (elig.length && elig.every(function (e) { return SUPPLIER_APPLICANTS.indexOf(e) !== -1; })) {
        return { state: 'mismatch', reason: 'open only to ' + (o.eligible_types || []).join(' and ').toLowerCase() };
      }
      if (type === 'individual_consultant') {
        return { state: 'mismatch', reason: 'an individual consultant role, not an organisational opportunity' };
      }
      // Supplier-only participation with nothing an organisation can lead.
      if (routes.length && routes.every(function (r) { return SUPPLIER_ROUTES.indexOf(r) !== -1; })
          && SUPPLIER_TYPES.indexOf(type) !== -1) {
        return { state: 'mismatch', reason: 'a supplier-route contract' };
      }
    }
    if (!elig.length) return { state: 'needs_info', reason: 'the notice does not state which applicant types may apply' };
    var hit = elig.some(function (e) { return orgKindsFor(pr).indexOf(e) !== -1; });
    if (hit) return { state: 'eligible', reason: 'your organisation type is explicitly eligible' };
    return { state: 'needs_info', reason: 'stated applicants are ' + (o.eligible_types || []).slice(0, 2).join(', ') };
  }

  // Deterministic ordering score. Never shown as a number and never presented
  // as a chance of winning — it only decides sequence.
  function fitScore(o, pr) {
    var v = eligibilityVerdict(o, pr), s = 0;
    if (v.state === 'mismatch') return -1;
    if (v.state === 'eligible') s += 40;
    var geo = (o.countries || []).filter(function (c) { return (pr.countries || []).concat(pr.regions || []).indexOf(c) !== -1; });
    if (geo.length) s += 25;
    var prog = (o.sectors || []).filter(function (x) { return (pr.programmes || []).indexOf(x) !== -1; });
    if (prog.length) s += 20;
    if ((o.participation_routes || []).indexOf('direct_applicant') !== -1) s += 10;
    if (o.deadline) { var d = Math.ceil((new Date(o.deadline) - Date.now()) / 864e5); if (d > 21) s += 5; }
    if (o.amount_min != null || o.amount_max != null) s += 3;
    return s;
  }

  // ── Result-set quality gate ────────────────────────────────────────────────
  // Runs BEFORE Mira claims anything was found. Two gates, in order:
  //   1. GRANT FAMILY — the product is a grants specialist, so consultancy
  //      assignments, supplier/works tenders, recruitment notices, scholarships
  //      and loans never reach Mira at all. This is what let a French consultant
  //      recruitment notice appear as an NGO grant because its title said "ONG".
  //   2. ELIGIBILITY — of the remaining grants, which can this organisation
  //      actually be the applicant for.
  // The RPC has no excludeTypes parameter, so the family gate is enforced here
  // rather than pretended at the query layer.
  function evaluateResultSet(recs, pr) {
    var kept = [], rejected = [];
    recs.forEach(function (o) {
      var fam = MGC().classifyGrantFamily(o);
      if (!fam.isGrant) { rejected.push({ o: o, reason: fam.reason, reasonPlural: fam.reasonPlural, gate: 'family' }); return; }
      var v = eligibilityVerdict(o, pr);
      if (v.state === 'mismatch') rejected.push({ o: o, reason: v.reason, gate: 'eligibility' });
      else kept.push(o);
    });
    return { kept: kept, rejected: rejected, allRejected: recs.length > 0 && kept.length === 0 };
  }

  // Params for a WIDER search. Only ever reached when the user has explicitly
  // asked for or accepted a wider geography — §13. Mira no longer relaxes the
  // country pin on its own when a country-specific search comes back empty.
  // The region is targeted rather than geography simply being dropped, so
  // "search wider South Asia" does not silently become "search everywhere".
  function widenedParams(cr, pr) {
    var params = criteriaToParams(cr, pr);
    params.country = null;
    var region = (cr.regions && cr.regions[0]) || (pr.regions || [])[0] || null;
    if (region) params.region = region;
    params.includeSupportedGlobal = true;
    params.pageSize = Math.max(cr.limit || 6, 8);
    return params;
  }

  // §13: wider results are described by WHAT CAME BACK, never by the geography
  // that was asked for. A set of calls that state no country is reported as
  // stating no country — it is not relabelled as regional.
  function widenedSummary(banded, focusCountry, region) {
    var n = banded.length;
    var specific = banded.filter(function (b) { return (b.o.countries || []).indexOf(focusCountry) !== -1; }).length;
    var unstated = banded.filter(function (b) { return !(b.o.countries || []).length; }).length;
    var parts = ['Found ' + n + ' ' + (n === 1 ? 'grant' : 'grants') + ' beyond ' + (focusCountry || 'your country') + '.'];
    if (specific) parts.push(specific + ' ' + (specific === 1 ? 'is' : 'are') + ' ' + focusCountry + '-specific.');
    else parts.push('None are ' + (focusCountry || 'country') + '-specific.');
    if (unstated === n) parts.push('None of them state an eligible geography, so confirm that on each notice before applying.');
    else if (unstated) parts.push(unstated + ' do not state an eligible geography, so confirm that on the notice.');
    else if (region) parts.push('They are open across ' + region + ' or globally.');
    return parts.join(' ');
  }

  // ── Intent classification + reference resolution ───────────────────────────
  var ORDINALS = { first: 1, second: 2, third: 3, fourth: 4, fifth: 5, sixth: 6, '1st': 1, '2nd': 2, '3rd': 3 };
  function resolveRefs(text, results) {
    var t = String(text || '').toLowerCase(), keys = [];
    Object.keys(ORDINALS).forEach(function (w) {
      if (new RegExp('\\b' + w + '\\b').test(t)) { var i = ORDINALS[w] - 1; if (results[i]) keys.push(results[i].o.id); }
    });
    var num = t.match(/\bnumber\s+(\d+)/g) || [];
    num.forEach(function (m) { var i = parseInt(m.replace(/\D/g, ''), 10) - 1; if (results[i]) keys.push(results[i].o.id); });
    var word = t.match(/\bnumber\s+(one|two|three|four|five|six)\b/);
    if (word) { var wi = { one: 0, two: 1, three: 2, four: 3, five: 4, six: 5 }[word[1]]; if (results[wi]) keys.push(results[wi].o.id); }
    if (/\b(these|them|those|all of them|the results)\b/.test(t)) results.forEach(function (r) { keys.push(r.o.id); });
    var strongest = t.match(/strongest\s+(two|three|2|3)/);
    if (strongest) {
      var n = /two|2/.test(strongest[1]) ? 2 : 3;
      results.slice(0, n).forEach(function (r) { keys.push(r.o.id); });
    }
    return keys.filter(function (k, i, a) { return a.indexOf(k) === i; });
  }

  // ── Conversational planning ────────────────────────────────────────────────
  // The intent taxonomy, routing contract, coverage tool, knowledge base and
  // product-help base all live in mira-grants-conversation.js so they can be
  // driven by scripts/mira-conversation-test.js without a browser. This file
  // keeps what needs the corpus and the ranking engine.
  //
  // The old classifier here returned `{ intent: 'search' }` as its fallback, so
  // any message it did not recognise ran a query. It no longer exists.
  function MGC() { return window.MiraGrantsConversation; }

  // Everything the conversational core needs to route and answer a turn.
  function conversationContext(text, state, profile, convo) {
    return {
      userMessage: text,
      profile: profile,
      activeCriteria: state.criteria || {},
      currentResults: (state.results || []).map(function (r) { return r.o; }),
      rejectedResults: (convo && convo.lastRejection && convo.lastRejection.items) || [],
      sourceRegistry: (convo && convo.sourceRegistry) || {},
      lastSuccessfulScan: (convo && convo.lastSuccessfulScan) || null,
      searchRan: !!(convo && convo.searchHasRun),
      widenedAutomatically: false,   // §13 — Mira no longer widens on its own
      shortlistCount: (convo && convo.shortlistCount) || 0,
    };
  }

  function classifierState(state, convo) {
    return {
      hasResults: !!(state.results && state.results.length),
      resultCount: (state.results || []).length,
      hadRejection: !!(convo && convo.lastRejection),
      hasCriteria: !!(convo && convo.searchHasRun),
      hasProfile: true,
    };
  }

  // ── LLM planning layer ─────────────────────────────────────────────────────
  // The model decides WHAT TO DO. It never decides what is true: eligibility,
  // ranking, grant facts, coverage numbers and workflow results all stay with
  // the deterministic engine. Any failure — no key, timeout, bad JSON, failed
  // schema, or a plan that tries to turn a question into a search — falls back
  // to the deterministic classifier, which also never defaults to search.
  var MIRA_PLAN_URL = '/api/mira-search?plan=1';
  function planWithModel(text, state, profile, convo) {
    var ctx = {
      userMessage: text,
      organisation: {
        type: profile.orgType || null,
        confirmedOperatingCountries: (profile.countries || []).concat(profile.regions || []),
        confirmedProgrammeAreas: profile.programmes || [],
      },
      conversation: {
        activeCriteria: state.criteria || {},
        lastIntent: (convo && convo.lastIntent) || null,
        excludedRecordCount: (convo && convo.lastRejection && convo.lastRejection.count) || 0,
        excludedBecause: (convo && convo.lastRejection && convo.lastRejection.reason) || null,
        searchHasRun: !!(convo && convo.searchHasRun),
      },
      currentResults: (state.results || []).map(function (r) {
        return {
          title: r.o.title, grantmaker: r.o.funder, grantType: preciseType(r.o),
          eligibilityState: (eligibilityVerdict(r.o, profile) || {}).state, deadline: r.o.deadline,
        };
      }),
      shortlist: [],
    };
    return fetch(MIRA_PLAN_URL, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ context: ctx }),
    }).then(function (r) { return r.ok ? r.json() : { ok: false, reason: 'http_' + r.status }; })
      .catch(function () { return { ok: false, reason: 'network' }; });
  }

  // Resolve which visible results a plan refers to. Model positions first, then
  // the deterministic phrase resolver so "the strongest two" still works.
  function resolveSelection(plan, text, results) {
    var keys = [];
    (plan.referencedResultPositions || []).forEach(function (pos) {
      var r = (results || [])[pos - 1];
      if (r) keys.push(r.o.id);
    });
    if (!keys.length) keys = resolveRefs(text, results || []);
    return keys;
  }

  function MiraMark(size) {
    return h('span', { className: 'mfx-mark', style: { width: size, height: size, minWidth: size } }, '✦');
  }

  function MiraFundingHeader(p) {
    return h('header', { className: 'mfx-head' },
      h('div', { className: 'mfx-head-l' },
        h('h1', { className: 'mfx-h1' }, 'Funding'),
        h('p', { className: 'mfx-sub' }, 'Find grants, contracts and partnerships that fit your organisation with Mira.')),
      h('div', { className: 'mfx-head-r' },
        h('button', { className: 'mfx-btn', onClick: p.onNew }, '+ New conversation'),
        h('button', {
          className: 'mfx-btn' + (p.historyOpen ? ' on' : ''), 'aria-expanded': !!p.historyOpen,
          onClick: p.onToggleHistory,
        }, 'History', p.count ? h('span', { className: 'mfx-badge' }, p.count) : null),
        h('button', { className: 'mfx-btn mfx-only-narrow', onClick: p.onToggleShortlist }, 'Shortlist')));
  }

  function MiraStarterPrompts(p) {
    return h('div', { className: 'mfx-starters' }, p.prompts.map(function (s, i) {
      return h('button', { key: i, className: 'mfx-starter', onClick: function () { p.onPick(s); } },
        h('span', null, s), h('span', { className: 'mfx-starter-i' }, '→'));
    }));
  }

  function MiraFundingEmptyState(p) {
    return h('div', { className: 'mfx-empty' },
      h('div', { className: 'mfx-empty-mark' }, '✦'),
      h('h2', { className: 'mfx-empty-h' }, 'Find funding with Mira'),
      h('p', { className: 'mfx-empty-p' }, 'Mira already understands your organisation. Ask for funding, refine the criteria, and shortlist the opportunities worth pursuing.'),
      h(MiraStarterPrompts, { prompts: p.prompts, onPick: p.onPick }));
  }

  function MiraUserMessage(p) {
    return h('div', { className: 'mfx-row mfx-row-user' },
      h('div', { className: 'mfx-msg mfx-msg-user' }, p.body));
  }

  // Reusable content container + result slot, so Phase 7B result cards drop in
  // below the text without the page being redesigned. The slot stays EMPTY for
  // text_only and text_with_actions turns — that is the whole point of §14.
  function MiraAssistantMessage(p) {
    return h('div', { className: 'mfx-row mfx-row-mira' },
      h('div', { className: 'mfx-msg mfx-msg-mira' },
        h('div', { className: 'mfx-msg-head' }, h('span', { className: 'mfx-mark-sm' }, '✦'), h('span', null, 'Mira')),
        h('div', { className: 'mfx-msg-body' }, p.body),
        p.basis || null,
        p.actions || null,
        p.diagnostics ? h(MiraTurnDiagnostics, { d: p.diagnostics }) : null,
        h('div', { className: 'mfx-result-slot', 'data-slot': 'funding-results' }, p.results || null)));
  }

  // §24: development only. Never rendered on the production hostname, and it
  // carries routing decisions only — no secrets, no documents, no profile data.
  var DEV_DIAGNOSTICS = (function () {
    try {
      var hn = String(window.location.hostname || '');
      return hn === 'localhost' || hn === '127.0.0.1' || /\.local$/.test(hn) || !!window.__MIRA_DEBUG;
    } catch (e) { return false; }
  })();

  function MiraTurnDiagnostics(p) {
    var d = p.d || {};
    function row(k, v) {
      return h('span', { className: 'mfx-diag-i' }, h('b', null, k), ' ', String(v == null ? '—' : v));
    }
    return h('details', { className: 'mfx-diag' },
      h('summary', null, 'dev: ' + (d.intent || 'unknown') + ' · ' + (d.renderMode || '—')),
      h('div', { className: 'mfx-diag-b' },
        row('intent', d.intent), row('confidence', d.confidence),
        row('requiresSearch', d.requiresSearch), row('tool', d.tool),
        row('renderMode', d.renderMode), row('usedCurrentResults', d.usedCurrentResults),
        row('usedCoverage', d.usedCoverage), row('planner', d.source),
        row('fallbackUsed', d.fallbackUsed), row('fallbackReason', d.fallbackReason),
        row('clarifyBecause', d.clarificationReason), row('plannerMs', d.latencyMs)));
  }

  function MiraPreviewPersistenceNotice() {
    return h('div', { className: 'mfx-notice' },
      h('span', { className: 'mfx-notice-d' }),
      'Sign in to save conversations across devices and collaborate with your team.');
  }

  function MiraFundingComposer(p) {
    var taRef = React.useRef ? React.useRef(null) : { current: null };
    function grow(el) { if (!el) return; el.style.height = 'auto'; el.style.height = Math.min(el.scrollHeight, 168) + 'px'; }
    function submit() { var v = String(p.value || '').trim(); if (!v || p.busy) return; p.onSend(v); }
    return h('div', { className: 'mfx-composer-wrap' },
      h('div', { className: 'mfx-composer' + (p.busy ? ' busy' : '') },
        h('span', { className: 'mfx-comp-mark' }, '✦'),
        h('textarea', {
          ref: taRef, className: 'mfx-comp-input', rows: 1, value: p.value, disabled: p.busy,
          placeholder: 'Ask Mira to find funding that fits your organisation…',
          'aria-label': 'Ask Mira to find funding',
          onInput: function (e) { grow(e.target); },
          onChange: function (e) { p.onChange(e.target.value); grow(e.target); },
          onKeyDown: function (e) {
            if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); }
          },
        }),
        h('button', {
          className: 'mfx-send', disabled: !String(p.value || '').trim() || p.busy,
          onClick: submit, 'aria-label': 'Send message',
        }, p.busy ? h('span', { className: 'mfx-spin' }) : '↑')),
      h('p', { className: 'mfx-disclaimer' }, 'Mira can make mistakes. Review important details before acting.'),
      p.showNotice ? h('p', { className: 'mfx-signin' }, 'Sign in to save conversations across devices and collaborate with your team.') : null);
  }

  function MiraConversationHistory(p) {
    if (!p.open) return null;
    var list = p.items;
    return h('div', { className: 'mfx-hist', role: 'dialog', 'aria-label': 'Conversation history' },
      h('div', { className: 'mfx-hist-h' }, 'Conversations'),
      !list.length
        ? h('p', { className: 'mfx-hist-empty' }, 'No conversations in this session yet.')
        : h('ul', { className: 'mfx-hist-l' }, list.map(function (c) {
            return h('li', { key: c.id, className: 'mfx-hist-i' + (c.id === p.activeId ? ' on' : '') },
              h('button', { className: 'mfx-hist-open', onClick: function () { p.onOpen(c.id); } },
                h('span', { className: 'mfx-hist-t' }, c.title),
                h('span', { className: 'mfx-hist-n' }, c.messages.length + (c.messages.length === 1 ? ' message' : ' messages'))),
              h('button', { className: 'mfx-hist-x', title: 'Rename', onClick: function () { p.onRename(c.id); } }, '✎'),
              h('button', { className: 'mfx-hist-x', title: 'Remove', onClick: function () { p.onArchive(c.id); } }, '×'));
          })));
  }

  function MiraShortlistRail(p) {
    if (p.collapsed) {
      return h('button', { className: 'mfx-rail-tab', onClick: p.onToggle, 'aria-label': 'Open shortlist' }, '★');
    }
    return h('aside', { className: 'mfx-rail' + (p.sheet ? ' sheet' : '') },
      h('div', { className: 'mfx-rail-h' },
        h('div', { className: 'mfx-rail-t' }, h('span', { className: 'mfx-rail-i' }, '★'), 'Mira shortlist'),
        h('button', { className: 'mfx-rail-x', onClick: p.onToggle, 'aria-label': 'Collapse shortlist' }, p.sheet ? '×' : '›')),
      h('div', { className: 'mfx-rail-count' }, (p.items.length || 0) + ' selected'),
      !p.items.length
        ? h('div', null,
            h('p', { className: 'mfx-rail-empty' }, 'Select funding opportunities from Mira’s results to compare and track them here.'),
            h('p', { className: 'mfx-rail-sub' }, 'Shortlisted opportunities can later move into your Pursuits workflow.'))
        : h('ul', { className: 'mfx-rail-l' }, p.items.map(function (it, i) {
            return h('li', { key: i, className: 'mfx-rail-item' },
              h('div', { className: 'mfx-rail-it' }, it.title),
              h('div', { className: 'mfx-rail-im' }, it.funder, it.deadline ? ' · ' + it.deadline : ''),
              h('div', { className: 'mfx-rail-ib' },
                h('span', { className: 'mfx-band mfx-band-' + String(it.band || '').toLowerCase().replace(/\s+/g, '-') }, it.band || 'Review'),
                h('span', { className: 'mfx-rail-stage' }, it.stage || 'new')));
          })));
  }

  function MiraFundingPage(p) {
    var mx = MX(), org = mx.org();
    var profile = React.useMemo ? React.useMemo(readOrgProfile, []) : readOrgProfile();
    var cS = React.useState(null); var conv = cS[0], setConv = cS[1];
    var draftS = React.useState(''); var draft = draftS[0], setDraft = draftS[1];
    var busyS = React.useState(false); var busy = busyS[0], setBusy = busyS[1];
    var histS = React.useState(false); var histOpen = histS[0], setHistOpen = histS[1];
    var railS = React.useState(false); var railCollapsed = railS[0], setRailCollapsed = railS[1];
    var sheetS = React.useState(false); var sheetOpen = sheetS[0], setSheetOpen = sheetS[1];
    var shortS = React.useState([]); var shortlist = shortS[0], setShortlist = shortS[1];
    var tickS = React.useState(0); var tick = tickS[0], setTick = tickS[1];
    var critS = React.useState(null); var criteria = critS[0], setCriteria = critS[1];
    var revS = React.useState(null); var review = revS[0], setReview = revS[1];
    var endRef = React.useRef ? React.useRef(null) : { current: null };

    // Signed in? Preview until proven otherwise. Never assumes a session.
    var authS = React.useState(false); var signedIn = authS[0], setSignedIn = authS[1];
    React.useEffect(function () {
      var on = true;
      if (mx.auth && mx.auth.available()) {
        mx.auth.user().then(function (u) { if (on) setSignedIn(!!u); });
      }
      return function () { on = false; };
    }, []);

    // Real shortlist when authenticated; the rail stays honestly empty otherwise.
    React.useEffect(function () {
      var on = true;
      if (signedIn && mx.listFundingShortlist) {
        mx.listFundingShortlist().then(function (r) { if (on && r && r.ok && Array.isArray(r.data)) setShortlist(r.data); });
      }
      return function () { on = false; };
    }, [signedIn]);

    React.useEffect(function () {
      if (endRef.current && endRef.current.scrollIntoView) endRef.current.scrollIntoView({ block: 'end' });
    }, [conv && conv.messages.length, busy]);

    // Starter prompts use confirmed organisation data where present and stay
    // generic otherwise — no organisation or country is ever hardcoded.
    var geoLabel = (profile.countries[0] || profile.regions[0] || null);
    var progLabel = profile.programmes[0] || null;
    var prompts = [
      'Find grants that fit our organisation',
      geoLabel ? ('Show opportunities in ' + geoLabel) : 'Show opportunities in our operating countries',
      'Find institutional funding closing within 90 days',
      progLabel ? ('Find ' + String(progLabel).toLowerCase() + ' funding we could lead on') : 'Find partnerships where we could join a consortium',
    ];
    // Refinement chips appear once the conversation starts — built from the
    // profile, never generic filler.
    var refinements = [
      'Only currently open', 'Include forecasts', 'Closing within 90 days',
      'Institutional grants only', 'Include consortium opportunities',
    ].concat(geoLabel ? [geoLabel + ' only'] : []);

    function ensureConv(firstLine) {
      if (conv) return conv;
      var title = String(firstLine || 'New conversation').slice(0, 60);
      var c = MIRA_PREVIEW.create(title);
      setConv(c);
      if (signedIn && mx.createFundingConversation) mx.createFundingConversation(title, {});
      return c;
    }

    // ── The turn orchestrator ────────────────────────────────────────────────
    // One canonical conversation state per conversation, and the CURRENT
    // MESSAGE always wins. A question asked after a search answers the question
    // and leaves the results exactly where they are.
    //
    // Search is one tool among many. Only search_grants / refine_search /
    // show_more may reach the corpus; every other intent is answered from state
    // that already exists.
    function convState(c) {
      if (!c._mira) c._mira = MGC().createConversationState();
      return c._mira;
    }

    function send(text) {
      var c = ensureConv(text);
      var cs = convState(c);
      c.messages.push({ id: 'm' + Date.now(), role: 'user', body: text });
      setDraft(''); setBusy(true); setTick(tick + 1);
      if (signedIn && mx.addFundingMessage && c.serverId) mx.addFundingMessage(c.serverId, 'user', text, {});

      // The visible result group. Read, never rewritten by a non-search turn.
      var lastGroup = null;
      for (var i = c.messages.length - 1; i >= 0; i--) {
        if (c.messages[i].results && c.messages[i].results.length) { lastGroup = c.messages[i]; break; }
      }
      var state = {
        results: (lastGroup && lastGroup.results) || [],
        criteria: criteria || cs.activeCriteria || baseCriteria(profile),
      };
      var convo = {
        lastIntent: cs.previousIntent,
        lastRejection: c.lastRejection || null,
        searchHasRun: !!cs.lastSuccessfulSearchAt || !!c.lastRejection,
        sourceRegistry: c.sourceRegistry || {},
        shortlistCount: shortlist.length,
      };

      var M = MGC();
      var fallbackPlan = M.classifyGrantsIntent(text, classifierState(state, convo));
      planWithModel(text, state, profile, convo).then(function (res) {
        var planned = fallbackPlan, source = 'deterministic', reason = (res && res.reason) || null;
        if (res && res.ok && res.plan) {
          var adopted = M.adoptModelPlan(res.plan, fallbackPlan);
          if (adopted) { planned = adopted; source = 'llm'; }
          else reason = 'rejected_plan';   // e.g. a question that demanded a search
        }
        planned.selectedKeys = resolveSelection(planned, text, state.results);
        // Belt and braces: whatever decided this, a non-search intent cannot
        // reach the corpus. This is the invariant the whole rebuild rests on.
        if (M.SEARCH_INTENTS.indexOf(planned.intent) === -1) planned.requiresSearch = false;
        execute(planned, source, reason, res);
      });

      function execute(planned, source, reason, res) {
        var diagnostics = {
          intent: planned.intent, confidence: planned.confidence, source: source,
          fallbackUsed: source !== 'llm', fallbackReason: reason,
          requiresSearch: planned.requiresSearch, tool: planned.tool || null,
          renderMode: planned.renderMode, usedCurrentResults: false, usedCoverage: false,
          clarificationReason: planned.intent === 'clarification' ? 'low confidence or ambiguous request' : null,
          latencyMs: (res && res.diagnostics && res.diagnostics.latencyMs) || null,
        };

        function say(body, extra) {
          var msg = Object.assign({ id: 'a' + Date.now(), role: 'assistant', body: body,
            renderMode: (extra && extra.renderMode) || 'text_only', diagnostics: diagnostics }, extra || {});
          c.messages.push(msg);
          cs.previousIntent = planned.intent;
          cs.lastToolExecuted = planned.tool || null;
          cs.lastAnswer = body;
          setBusy(false); setTick(function (t) { return t + 1; });
          return msg;
        }

        if (!profile.has && planned.requiresSearch) return say(miraAcknowledgement(text, profile));

        // ── 1. Answers the conversational core owns outright ─────────────────
        // knowledge, coverage, product help, profile, greeting, off-topic,
        // clarification, monitoring, shortlist/pursuit questions.
        var owned = M.answerGrantsTurn(planned, conversationContext(text, state, profile, convo));
        if (owned) {
          if (owned.coverage) { cs.latestCoverageInspection = owned.coverage; diagnostics.usedCoverage = true; }
          diagnostics.tool = owned.tool || diagnostics.tool;
          diagnostics.renderMode = owned.renderMode;
          return say(owned.answer, {
            renderMode: owned.renderMode,
            actionChips: owned.actionChips || [],
            citedSources: owned.citedSources || [],
            knowledgeBasis: owned.knowledgeBasis || null,
          });
        }

        // ── 2. A search, because the user asked for one ──────────────────────
        if (planned.requiresSearch) {
          var cr = applyIntent(state.criteria, text, profile);
          var patch = planned.criteriaPatch || {};
          if (patch.more) cr.limit = Math.min((cr.limit || 6) + 6, 24);
          if (patch.amountMin) cr.amountMin = patch.amountMin;
          // §13: geography only widens when the user asked for it.
          if (patch.userApprovedWidening) cr.widened = true;
          if (patch.widen === 'region') cr.widened = true;
          setCriteria(cr);
          cs.activeCriteria = cr;
          return runSearch(cr, c, diagnostics);
        }

        // ── 3. Reasoning over what is already on screen ──────────────────────
        var cur = state.results;
        if (!cur.length) {
          // The old code answered every analytical follow-up here by replaying a
          // canned supplier-tender line. A coverage explanation is the honest
          // answer, and it is built fresh from the inspection each time.
          var covTurn = M.answerGrantsTurn({ intent: 'corpus_coverage_question' },
            conversationContext(text, state, profile, convo));
          diagnostics.usedCoverage = true;
          diagnostics.tool = 'inspectGrantCoverage';
          diagnostics.renderMode = covTurn.renderMode;
          cs.latestCoverageInspection = covTurn.coverage;
          return say(covTurn.answer, { renderMode: covTurn.renderMode, actionChips: covTurn.actionChips });
        }
        diagnostics.usedCurrentResults = true;

        var graded = cur.map(function (r) {
          return { r: r, v: eligibilityVerdict(r.o, profile), score: fitScore(r.o, profile) };
        });
        var viable = graded.filter(function (g) { return g.v.state !== 'mismatch'; })
                           .sort(function (a, b) { return b.score - a.score; });

        if (planned.intent === 'recommend_result' || planned.intent === 'compare_results') {
          // The model's plan carries no comparison subject, so recover it here
          // rather than answering "which closes first?" with a fit ranking.
          var subject = planned.subject
            || (/\b(clos|deadline|due|expir)/i.test(text) ? 'deadline' : 'overall');
          if (subject === 'deadline') {
            var dated = viable.filter(function (x) { return x.r.o.deadline; })
              .sort(function (a, b) { return new Date(a.r.o.deadline) - new Date(b.r.o.deadline); });
            if (!dated.length) return say('None of the current results state a deadline.');
            return say(displayTitle(dated[0].r.o).slice(0, 70) + ' closes first, on '
              + String(dated[0].r.o.deadline).slice(0, 10) + '.', { renderMode: 'text_only' });
          }
          if (!viable.length) {
            return say('None of the current results are ones your organisation can apply for. Ask me to search wider and I will, but I will not change the geography without you.',
              { renderMode: 'text_with_actions', actionChips: coverageChips() });
          }
          var top = viable[0], reasons = [];
          if (top.v.state === 'eligible') reasons.push('your organisation type is eligible');
          var g = (top.r.o.countries || []).filter(function (x) { return (profile.countries || []).concat(profile.regions || []).indexOf(x) !== -1; })[0];
          if (g) reasons.push(g + ' is covered');
          var pg = (top.r.o.sectors || []).filter(function (x) { return (profile.programmes || []).indexOf(x) !== -1; })[0];
          if (pg) reasons.push(pg.toLowerCase() + ' is aligned');
          if (!reasons.length) reasons.push('it is the closest match to your profile');
          // §11/§12: a conclusion plus a compact comparison — never the full grid again.
          return say(displayTitle(top.r.o).slice(0, 70) + ' is the strongest fit — ' + reasons.slice(0, 3).join(', ') + '.', {
            renderMode: 'comparison',
            comparison: viable.slice(0, 3).map(function (x) { return { o: x.r.o, band: x.r.band, v: x.v }; }),
          });
        }
        if (planned.intent === 'explain_eligibility' || planned.intent === 'explain_result') {
          var pick = planned.selectedKeys.length
            ? graded.filter(function (x) { return x.r.o.id === planned.selectedKeys[0]; })[0]
            : graded[0];
          if (!pick) return say('I could not find that opportunity in the current results.');
          if (planned.intent === 'explain_result') {
            return say(displayTitle(pick.r.o).slice(0, 60) + ' is there because ' + pick.r.band.why.toLowerCase()
              + '. ' + (pick.r.band.concern ? 'Still to check: ' + pick.r.band.concern + '.' : ''), { renderMode: 'text_only' });
          }
          return say(displayTitle(pick.r.o).slice(0, 60) + ': ' + pick.v.reason + '.', { renderMode: 'text_only' });
        }
        if (planned.intent === 'shortlist_action') {
          if (/^(open|show)\b/.test(String(text).toLowerCase()) && /shortlist/.test(String(text).toLowerCase())) {
            setRailCollapsed(false); setSheetOpen(true);
            return say('Opened your shortlist.', { renderMode: 'workflow_confirmation' });
          }
          var picks = planned.selectedKeys.length
            ? graded.filter(function (x) { return planned.selectedKeys.indexOf(x.r.o.id) !== -1; })
            : viable.slice(0, 1);
          picks.forEach(function (x) { saveOpp(x.r.o, x.r.band); });
          return say(picks.length ? 'Added ' + picks.length + ' to your shortlist.' : 'Nothing suitable to add.',
            { renderMode: 'workflow_confirmation' });
        }
        if (planned.intent === 'pursuit_action') {
          return say('Moving a shortlisted grant into Pursuits from this conversation is not wired up yet, so I have not moved it. Pursuits is in the left-hand navigation.',
            { renderMode: 'text_only' });
        }
        // Anything else that reached here is a follow-up we can answer from the
        // set on screen — and never a reason to re-run the query.
        return say('I have kept the ' + cur.length + ' results on screen. Tell me what you want to know about them, or ask me to search for something else.',
          { renderMode: 'text_only' });
      }
    }

    // Chips the coverage tool offers. Nothing here executes on its own — §7.
    function coverageChips() {
      var region = profile.regions[0] || null, country = profile.countries[0] || null;
      var chips = [];
      // Offering "Search wider South Asia" to an organisation whose confirmed
      // profile already contains South Asia implies Mira did less than it was
      // asked to. The regional lane runs by default now, so the only honest
      // offer in the other direction is to NARROW.
      if (country) chips.push({ id: 'country_only', label: country + ' only' });
      chips.push({ id: 'show_sources', label: 'Show covered sources' });
      if (country) chips.push({ id: 'monitor_country', label: 'Monitor ' + country + ' grants' });
      return chips;
    }

    // An action chip is a SUGGESTION. Clicking one is the user's explicit
    // approval, which is the only thing that may widen the geography.
    function runChip(chip) {
      var region = profile.regions[0] || 'the wider region';
      var country = profile.countries[0] || 'that country';
      if (chip.id === 'widen_region') return send('Okay, search wider ' + region + '.');
      if (chip.id === 'country_only') return send('Show ' + country + ' only.');
      if (chip.id === 'search_grants') return send('Find grants that fit our organisation');
      if (chip.id === 'monitor_country') return send('Monitor ' + country + ' grants.');
      if (chip.id === 'open_profile') return p.onOnboard && p.onOnboard();
      if (chip.id === 'show_sources') return showSources();
      if (chip.id === 'explain_excluded') return explainExcluded();
    }

    // Real coverage, read from the corpus facets — never an estimate.
    function showSources() {
      var c = conv || ensureConv('Funding');
      setBusy(true);
      mx.fundingFacets().then(function (f) {
        var funders = (f && f.funders) || [];
        var body = funders.length
          ? 'Mira currently covers ' + funders.length + ' grantmakers, including ' + funders.slice(0, 6).join(', ') + '. Coverage is strongest for multilateral and large bilateral funders and thinnest for national and local foundations.'
          : 'I could not read the source registry just now, so I will not guess at the coverage figure.';
        c.messages.push({ id: 'a' + Date.now(), role: 'assistant', body: body, renderMode: 'text_only' });
        setBusy(false); setTick(function (t) { return t + 1; });
      }).catch(function () {
        c.messages.push({ id: 'a' + Date.now(), role: 'assistant', renderMode: 'text_only',
          body: 'I could not read the source registry just now, so I will not guess at the coverage figure.' });
        setBusy(false); setTick(function (t) { return t + 1; });
      });
    }

    function explainExcluded() {
      var c = conv || ensureConv('Funding');
      var rej = (c.lastRejection && c.lastRejection.items) || [];
      var body;
      if (!rej.length) body = 'Nothing was excluded from the current results.';
      else {
        var byReason = {};
        rej.forEach(function (r) { byReason[r.reason] = (byReason[r.reason] || 0) + 1; });
        body = 'I excluded ' + rej.length + ' ' + (rej.length === 1 ? 'record' : 'records') + ': '
          + Object.keys(byReason).map(function (k) { return byReason[k] + ' because ' + (k || 'it was not a grant'); }).join('; ') + '.';
      }
      c.messages.push({ id: 'a' + Date.now(), role: 'assistant', body: body, renderMode: 'text_only' });
      setTick(function (t) { return t + 1; });
    }

    // One search path. The grant-family gate and eligibility gate run before
    // Mira claims anything was found. When a country-specific search comes back
    // empty it SAYS SO and offers to widen — it no longer widens on its own.
    function runSearch(cr, convRef, diagnostics) {
      var c = convRef || conv || ensureConv('Funding');
      var cs = convState(c);
      setBusy(true);
      var params = cr.widened ? widenedParams(cr, profile) : criteriaToParams(cr, profile);
      mx.searchFunding(params).then(function (res) {
        var recs = (res && res.records) || [];
        var total = (res && res.pagination && res.pagination.totalRecords) || 0;
        var qc = evaluateResultSet(recs, profile);
        var where = cr.widened ? null : ((cr.countries && cr.countries[0]) || (cr.regions && cr.regions[0]) || null);

        c.lastRejection = qc.rejected.length
          ? { reason: qc.rejected[0].reason, count: qc.rejected.length, where: where, items: qc.rejected }
          : null;
        cs.rejectedResults = qc.rejected;

        // An opportunity whose deadline has passed must never be presented as
        // one "your organisation can pursue". Keep only still-open (or rolling/
        // forecast) records; count what was dropped so the empty state is honest.
        var openKept = qc.kept.filter(function (o) { return !isExpired(o); });
        var closedDropped = qc.kept.length - openKept.length;
        var banded = openKept
          .map(function (o) { return { o: o, band: bandFor(o, profile), score: fitScore(o, profile) }; })
          .sort(function (a, b) { return b.score - a.score; })
          .slice(0, 8);

        if (!banded.length) {
          // If matches existed but had all closed, say that plainly rather than
          // implying nothing was found.
          if (closedDropped > 0) {
            var chipsC = [];
            if (!cr.widened && profile.regions[0]) chipsC.push({ id: 'widen_region', label: 'Search wider ' + profile.regions[0] });
            if (where) chipsC.push({ id: 'monitor_country', label: 'Monitor ' + where + ' grants' });
            c.messages.push({
              id: 'a' + Date.now(), role: 'assistant',
              body: 'The ' + closedDropped + ' matching ' + (closedDropped === 1 ? 'opportunity has' : 'opportunities have') + ' already closed'
                + (where ? ' in ' + where : '') + '. I only show calls you can still apply to — ask me to search wider or set up monitoring and I’ll surface new ones as they open.',
              renderMode: 'text_with_actions', actionChips: chipsC, diagnostics: diagnostics,
            });
            setBusy(false); setTick(function (t) { return t + 1; });
            return;
          }
          // §13: state the honest result and OFFER the wider search. Do not run it.
          var chips = [];
          if (!cr.widened && profile.regions[0]) chips.push({ id: 'widen_region', label: 'Search wider ' + profile.regions[0] });
          chips.push({ id: 'show_sources', label: 'Show covered sources' });
          if (where) chips.push({ id: 'monitor_country', label: 'Monitor ' + where + ' grants' });
          if (qc.rejected.length) chips.push({ id: 'explain_excluded', label: 'Explain excluded results' });
          var body = where
            ? 'Mira did not find a suitable verified ' + where + '-specific grant.'
            : 'Mira did not find a suitable verified grant for those criteria.';
          if (qc.rejected.length) {
            var one = qc.rejected.length === 1;
            body += ' The ' + qc.rejected.length + ' ' + (one ? 'record' : 'records')
              + ' that did match ' + (one ? 'was ' + qc.rejected[0].reason
                                          : 'were ' + (qc.rejected[0].reasonPlural || qc.rejected[0].reason)) + '.';
          }
          c.messages.push({ id: 'a' + Date.now(), role: 'assistant', body: body,
            renderMode: 'text_with_actions', actionChips: chips, diagnostics: diagnostics });
          setBusy(false); setTick(function (t) { return t + 1; });
          return;
        }

        // Wider results are labelled as wider, and only ever by what came back.
        var summary = cr.widened
          ? widenedSummary(banded, (cr.countries && cr.countries[0]) || profile.countries[0] || null, profile.regions[0] || null)
          : 'Found ' + banded.length + ' ' + (banded.length === 1 ? 'grant' : 'grants')
            + (where ? ' in ' + where : '') + ' your organisation can pursue.';
        if (qc.rejected.length) summary += ' I excluded ' + qc.rejected.length + ' non-grant '
          + (qc.rejected.length === 1 ? 'record' : 'records') + '.';

        var groupId = 'g' + Date.now();
        c.messages.push({ id: 'a' + Date.now(), role: 'assistant', body: summary, results: banded,
          total: total, criteria: cr, resultGroupId: groupId, renderMode: 'search_results', diagnostics: diagnostics });
        cs.currentResultGroupId = groupId;
        cs.visibleOpportunityKeys = banded.map(function (b) { return b.o.id; });
        cs.lastSuccessfulSearchAt = Date.now();
        cs.activeCriteria = cr;
        setBusy(false); setTick(function (t) { return t + 1; });
      }).catch(function () {
        c.messages.push({ id: 'a' + Date.now(), role: 'assistant', body: 'I couldn’t complete that funding search.', failed: true });
        setBusy(false); setTick(function (t) { return t + 1; });
      });
    }

    // Shortlisting. Persists through the service when signed in; the preview
    // adapter keeps it for the session otherwise. Duplicates are refused.
    function saveOpp(o, band) {
      if (shortlist.some(function (s) { return s.id === o.id; })) return;
      var item = { id: o.id, title: o.title, funder: o.funder, deadline: o.deadline,
        band: band.band, stage: 'new', opportunity_key: o.canonical_key || null };
      setShortlist(shortlist.concat([item]));
      if (signedIn && mx.addToFundingShortlist && o.canonical_key) {
        mx.addToFundingShortlist(o.canonical_key, { conversationId: conv && conv.serverId });
      }
      p.toast && p.toast('Added to your Mira shortlist');
    }

    function newConversation() { setConv(null); setDraft(''); setHistOpen(false); }

    var messages = (conv && conv.messages) || [];
    var railProps = {
      items: shortlist, collapsed: railCollapsed && !sheetOpen, sheet: sheetOpen,
      onToggle: function () { if (sheetOpen) setSheetOpen(false); else setRailCollapsed(!railCollapsed); },
    };

    return h('div', {
      className: 'mfx-page' + (railCollapsed ? ' rail-collapsed' : ''),
      'data-ui-version': 'mira-conversational-funding-v1',
    },
      h(MiraFundingHeader, {
        onNew: newConversation, onToggleHistory: function () { setHistOpen(!histOpen); },
        onToggleShortlist: function () { setSheetOpen(true); },
        historyOpen: histOpen, count: MIRA_PREVIEW.list().length,
      }),
      h(MiraConversationHistory, {
        open: histOpen, items: MIRA_PREVIEW.list(), activeId: conv && conv.id,
        onOpen: function (id) { setConv(MIRA_PREVIEW.get(id)); setHistOpen(false); setTick(tick + 1); },
        onRename: function (id) {
          var c = MIRA_PREVIEW.get(id); if (!c) return;
          var next = window.prompt('Rename conversation', c.title);
          if (next && next.trim()) { MIRA_PREVIEW.rename(id, next.trim()); setTick(tick + 1); }
        },
        onArchive: function (id) {
          MIRA_PREVIEW.archive(id);
          if (conv && conv.id === id) setConv(null);
          setTick(tick + 1);
        },
      }),
      h(MiraProfileStrip, { profile: profile, onEdit: function () { p.onOnboard && p.onOnboard(); } }),
      h('div', { className: 'mfx-ws' },
        h('section', { className: 'mfx-conv' },
          h('div', { className: 'mfx-scroll' },
            !messages.length
              ? h(MiraFundingEmptyState, { prompts: prompts, onPick: send })
              : h('div', { className: 'mfx-msgs' },
                  messages.map(function (m, idx) {
                    if (m.role === 'user') return h(MiraUserMessage, { key: m.id, body: m.body });
                    // §11: what renders is decided by the orchestrator's render
                    // mode, NOT by whether results happen to exist. A text_only
                    // answer never grows a grid, and the grid above it stays.
                    var mode = m.renderMode || (m.results && m.results.length ? 'search_results' : 'text_only');
                    var slot = null;
                    if (mode === 'comparison' && m.comparison && m.comparison.length) {
                      slot = h('ol', { className: 'mfx-cmp' }, m.comparison.map(function (x) {
                        return h('li', { key: x.o.id, className: 'mfx-cmp-i' },
                          h('div', { className: 'mfx-cmp-t' }, displayTitle(x.o)),
                          h('div', { className: 'mfx-cmp-m' },
                            h('span', { className: 'mfx-band mfx-band-' + String(x.band.band).toLowerCase().replace(/\s+/g, '-') }, x.band.band),
                            ' ', x.v.reason));
                      }));
                    } else if (mode === 'search_results' && m.results && m.results.length) {
                      slot = h('div', { className: 'mfx-cards' }, m.results.map(function (r) {
                        return h(MiraResultCard, {
                          key: r.o.id, o: r.o, band: r.band,
                          saved: shortlist.some(function (s) { return s.id === r.o.id; }),
                          onSave: saveOpp, onReview: function (o, b) { setReview({ o: o, band: b }); },
                        });
                      }));
                    } else if (m.failed) {
                      slot = h('div', { className: 'mfx-fail' },
                        h('button', { className: 'mfx-refine', onClick: function () { send(messages[idx - 1] ? messages[idx - 1].body : 'Find grants that fit our organisation'); } }, 'Retry'));
                    }
                    return h(MiraAssistantMessage, {
                      key: m.id, body: m.body, results: slot,
                      // Suggestions only. Nothing here runs until it is clicked.
                      actions: (m.actionChips && m.actionChips.length)
                        ? h('div', { className: 'mfx-chip-acts' }, m.actionChips.map(function (a, ai) {
                            return h('button', { key: ai, className: 'mfx-chip-act', onClick: function () { runChip(a); } }, a.label);
                          }))
                        : null,
                      basis: m.knowledgeBasis === 'general_guidance'
                        ? h('div', { className: 'mfx-basis' }, 'General guidance — not verified against a specific funder’s rules.')
                        : null,
                      diagnostics: DEV_DIAGNOSTICS ? m.diagnostics : null,
                    });
                  }),
                  busy ? h('div', { className: 'mfx-row mfx-row-mira' },
                    h('div', { className: 'mfx-msg mfx-msg-mira mfx-typing' },
                      h('span', null), h('span', null), h('span', null), h('em', { className: 'mfx-typing-l' }, 'Checking active opportunities…'))) : null,
                  h('div', { ref: endRef }))),
          messages.length ? h('div', { className: 'mfx-refines' }, refinements.map(function (r, i) {
            return h('button', { key: i, className: 'mfx-refine', onClick: function () { send(r); } }, r);
          })) : null,
          h(MiraFundingComposer, { value: draft, onChange: setDraft, onSend: send, busy: busy, showNotice: !signedIn })),
        h(MiraShortlistRail, railProps)),
      review ? h(MiraReviewModal, {
        o: review.o, band: review.band,
        saved: shortlist.some(function (s) { return s.id === review.o.id; }),
        onSave: saveOpp, onClose: function () { setReview(null); },
        // TYPED ACTION DISPATCH.
        //
        // These were previously injected into the conversation as free-form user
        // text ("Build an application checklist for ..."), which then went through
        // normal intent classification and came back off_topic — the modal's own
        // actions producing "I'm most useful for grants...". A product action is
        // not a user message. It is dispatched directly to a deterministic tool
        // and never reaches the planner.
        onSupport: function (kind, o, band) {
          var action = {
            type: 'mira_grant_action', action: kind,
            opportunityKey: o.canonical_key || o.id || null,
            source: 'grant_review_modal',
          };
          var res = runGrantAction(action, o, band, profile);
          setReview(null);
          var c = ensureConv(null);
          c.messages.push({
            id: 'a' + Date.now(), role: 'assistant',
            body: res.body, render: res.render, grantAction: action,
          });
          setTick(tick + 1);
        },
      }) : null,
      sheetOpen ? h('div', { className: 'mfx-sheet-back', onClick: function () { setSheetOpen(false); } }) : null);
  }

  function IntelScreen(props) {
    var k = props.kind, parts = props.parts;
    // §15: Today must not load the whole Funding database. TodayView reads only
    // workspace state (apps/saves/pins) and never touches p.opps, so waiting on
    // the bulk set gained nothing and cost everything — after the Phase 5 corpus
    // growth (102 → 20k+ rows) it blocked the morning dashboard behind a
    // full-table fetch. Funding and Today both query what they actually need.
    var bulkNeeded = k !== 'funding' && k !== 'today' && k !== 'dashboard';
    var od = React.useState(null); var opps = od[0], setOpps = od[1];
    React.useEffect(function () {
      if (!bulkNeeded) return;
      var on = true;
      MX().loadOpportunities().then(function (list) { if (on) setOpps(list); });
      return function () { on = false; };
    }, [bulkNeeded]);
    // Routes excluded from the bulk fetch above must also return BEFORE the
    // !opps guard — otherwise they wait forever on a fetch that never runs.
    if (k === 'funding') return h(MiraFundingPage, props);
    if (k === 'today' || k === 'dashboard') return h(TodayView, props);
    if (!opps) return h('div', { className: 'ent-card ent-card-pad', style: { textAlign: 'center', color: 'var(--ent-muted)' } }, 'Loading funding intelligence…');
    var sub = Object.assign({ opps: opps }, props);
    if (k === 'country') return h(CountryView, Object.assign({}, sub, { id: decodeURIComponent(parts[1] || ''), lens: parts[2] }));
    if (k === 'funder') return h(FunderView, Object.assign({}, sub, { id: decodeURIComponent(parts[1] || ''), lens: parts[2] }));
    if (k === 'opportunity') return h(OppView, Object.assign({}, sub, { id: parts[1] }));
    // Enterprise Search is retired — Funding is the single conversational
    // entry point. Consumer Globally search is untouched.
    if (k === 'search') return h(MiraFundingPage, props);   // safety net; enterprise.jsx normalises first
    if (k === 'applications' || k === 'pursuits') return parts[1] ? h(PursuitView, Object.assign({}, sub, { id: parts[1] })) : h(PursuitsView, sub);
    if (k === 'today' || k === 'dashboard') return h(TodayView, sub);
    return h('div', { className: 'ei-empty' }, h('h3', null, 'Unknown view'));
  }

  window.MiraIntel = {
    handles: function (route) { return ['country', 'funder', 'opportunity', 'search', 'funding', 'applications', 'pursuits', 'today', 'dashboard', 'explore'].indexOf(route) !== -1; },
    render: function (kind, hash, props) {
      injectCSS();
      var parts = String(hash || '').replace(/^#\/enterprise\/?/, '').split('/');
      // The topic explorer is a separate module (enterpriseTopicExplorer.jsx) so
      // this file does not grow another screen. If it failed to load, fall
      // through to the normal screen rather than rendering a blank route.
      if (kind === 'explore' && window.EnterpriseTopicExplorer) {
        return h(window.EnterpriseTopicExplorer, { parts: parts });
      }
      return h(IntelScreen, Object.assign({ kind: kind, parts: parts, toast: props && props.toast, refresh: props && props.refresh }, {}));
    },
  };
})();
