/**
 * DashIQ V2 — competeiq.jsx
 * CompeteIQ module: Landscape + Drug Profile sub-views.
 *
 * Load context: <script type="text/babel" src="src/competeiq.jsx">
 * No bundler. No imports. All colors via var(--token). No mock data.
 * Exposes on window: renderCompeteIQ, setCompView, loadDrugProfile,
 *   loadDrugProfileData, renderDiqTable, openDiqDrawer, closeEvDrawer,
 *   selectCompOrg, compLandSearch, compLandModeSwitch,
 *   toggleOrgTypeFilter, addBreadcrumb, renderCompLandscape
 *
 * Phase 3 Ticket 1 — six structural fixes applied here:
 *   Fix 1 (class-name reconciliation) — JSX→CSS direction. JSX class names
 *         renamed to existing CSS rules where possible (.dt → .iq-table,
 *         .diq-kpi-cell → .diq-kpi-tile, .dt-pagination → .iq-pagination).
 *         Where no analogous rule exists, new rules added in components.css /
 *         modules.css. No mixed direction.
 *   Fix 2 (auto-load) — renderDrugProfileView() seeds STATE.comp_drug_name to
 *         'Pembrolizumab' on first paint per V1 line 21774-21777, so the
 *         rich Drug Profile shell renders immediately. Picker still works
 *         when API returns no profile (silent-fallback behavior preserved).
 *   Fix 3 (quick-pills row) — _buildOrgPanelHTML emits the V1 L22455-22479
 *         labeled TA / TGT / DRUG pills below the org-name header.
 *   Fix 4 (Score Breakdown accordion) — already wired. Weights 25+15+20+20+20=100.
 *   Fix 5 (phase pill light contrast) — token-based, gated to [data-theme="light"].
 *         Changes live in tokens.css + modules.css (this file does not emit hex).
 *   Fix 6 (table column reconciliation) — TA mode now renders Org/Score/Depth/
 *         IP/Momentum (with mini-bars per V1 L22957-22963). Disease mode renders
 *         Org/Score/Drug+(N)/Modality/IP Moat/Trial Vol per V1 L22920-22949.
 */

// ─── 0. CONSTANTS ─────────────────────────────────────────────────────────────

const COMP_PAGE_SIZE = 10;

const COMP_ORG_TYPES = [
  'ALL','PHARMA','BIOTECH','ACADEMIC','CRO','DEVICE','GOVERNMENT','TOOLS','OTHER'
];

const COMP_RADAR_AXES = ['Depth','Breadth','Target','IP','Mom'];
const _rCx = 70, _rCy = 66, _rR = 34, _rLR = 1.52, _rN = 5;

const SCORE_WEIGHTS = {
  depth: 0.25, breadth: 0.15, target: 0.20, ip: 0.20, momentum: 0.20
};

// Phase keys matching API response fields
const PHASE_MAP_KEYS = [
  'EARLY_PHASE1','PHASE1','PHASE1/PHASE2','PHASE2','PHASE2/PHASE3','PHASE3','PHASE4'
];

// Phase bar segments (7-segment, matches V1 org panel)
const PHASE_BAR_SEGMENTS = [
  { key: 'I',     phases: ['EARLY_PHASE1','PHASE1'],    color: 'var(--purple)'  },
  { key: 'I/II',  phases: ['PHASE1/PHASE2'],            color: 'var(--blue)'    },
  { key: 'II',    phases: ['PHASE2'],                   color: 'var(--teal)'    },
  { key: 'II/III',phases: ['PHASE2/PHASE3'],            color: 'var(--teal-bright)'  },
  { key: 'III',   phases: ['PHASE3'],                   color: 'var(--amber)'   },
  { key: 'IV',    phases: ['PHASE4'],                   color: 'var(--rose)'    }
];

// phasePills color map — matches V1 exactly; these are dynamic style values
// embedded inside HTML string construction (not static CSS assignments)
const _phaseColors = {
  'EARLY_PHASE1':'var(--purple)',
  'Early Phase 1':'var(--purple)',
  'PHASE1':'var(--blue)',
  'Phase 1':'var(--blue)',
  'PHASE1/PHASE2':'var(--indigo)',
  'Phase 1/Phase 2':'var(--indigo)',
  'PHASE2':'var(--blue)',
  'Phase 2':'var(--blue)',
  'PHASE2/PHASE3':'var(--teal)',
  'Phase 2/Phase 3':'var(--teal)',
  'PHASE3':'var(--teal-bright)',
  'Phase 3':'var(--teal-bright)',
  'PHASE4':'var(--amber)',
  'Phase 4':'var(--amber)',
  'Approved':'var(--amber-warm)'
};

// ─── 1. MODULE STATE ──────────────────────────────────────────────────────────

const _CS = {
  // landscape
  view: 'landscape',        // 'landscape' | 'drug-ind'
  data: null,               // rankCompete() result rows
  diseases: [],             // fetchCompeteDiseases() list (legacy — picker uses _GLOBAL_POOL.diseases now)
  tas: [],                  // legacy — picker uses _GLOBAL_POOL.tas now (12 distinct TAs in compete data)
  ta: 'ALL',               // active TA filter
  disease: '',              // active disease filter
  diseaseMode: false,       // true = disease mode (6 cols), false = TA mode (5 cols)
  orgTypeFilter: 'PHARMA',  // active org type pill — default to PHARMA so executive audience sees pharma first (per data audit: ACADEMIC dominates 58.9% of compete_iq, NIH would rank above all 10 HEROes if default were ALL)
  orgTypeCounts: {},        // { PHARMA: 12, ... }
  filteredData: [],         // after search + org filter
  page: 0,                  // landscape page
  searchQ: '',              // landscape search text
  selectedOrg: null,        // selected org row object
  panelOpen: false,         // org panel visible

  // drug profile
  drugProfileData: null,    // fetchDrugProfiles() result
  drugNames: [],            // fetchDrugProfileNames() list
  drugName: '',             // active drug
  drugOrg: '',              // active org for drug profile
  diqPage: 0,               // drug profile competitors page
  diqSelectedRow: null,     // selected competitor row
  diqEvidenceOpen: false,   // evidence panel open

  // breadcrumb stack
  breadcrumbs: []
};

// ─── 2. HELPERS ───────────────────────────────────────────────────────────────

function _inferOrgType(orgName) {
  if (!orgName) return 'OTHER';
  const n = orgName.toLowerCase();
  if (/university|college|institute|hospital|health system|children|mayo|cleveland|johns hopkins|stanford|harvard|yale|mgh|ucsf|nih|nci|national cancer|national institute/.test(n)) return 'ACADEMIC';
  if (/cro|quintiles|covance|pra |icon plc|parexel|syneos|medpace|medgenesis|labcorp|charles river|envigo|wuxi apptec|wuxi biologics/.test(n)) return 'CRO';
  if (/medtronic|stryker|boston scientific|becton|bd |zimmer|baxter|edwards|teleflex|integra|natus|nuvasive|globus|alphatec|rtx|cook medical|terumo|olympus|hologic/.test(n)) return 'DEVICE';
  if (/government|department of|ministry of|agency|fda|ema|cdc|who|army|navy|air force|defense|dod|darpa/.test(n)) return 'GOVERNMENT';
  if (/tools|genomics|proteomics|sequencing|illumina|10x genomics|pacific biosciences|oxford nanopore|nanostring|fluidigm|bionanomatrix|complete genomics/.test(n)) return 'TOOLS';
  if (/biotech|biosciences|biotherapeutics|biologics|biopharma|gene therapy|cell therapy|therapeutics|oncology|immuno|gentech|genentech/.test(n)) return 'BIOTECH';
  if (/pfizer|novartis|roche|sanofi|gsk|glaxo|astrazeneca|johnson|merck|abbvie|bristol|lilly|amgen|gilead|bayer|takeda|novo nordisk|boehringer|regeneron|biogen/.test(n)) return 'PHARMA';
  if (/inc\.|llc|corp|ltd|pharma|pharmaceutical/.test(n)) return 'PHARMA';
  return 'OTHER';
}

function assetModality(drugName) {
  if (!drugName) return 'Small Molecule';
  const n = drugName.toLowerCase();
  if (/mab$|umab$|zumab$|ximab$|mumab$|imab$|lizumab$|lumab$/.test(n)) return 'mAb';
  if (/gene therapy|aav|lentivir|retrovir/.test(n)) return 'Gene Therapy';
  if (/cell therapy|car-t|cart|car t/.test(n)) return 'Cell Therapy';
  if (/rna|mrna|sirna|antisense|oligonucleotide/.test(n)) return 'Nucleic Acid';
  if (/peptide|tide$|lin$/.test(n)) return 'Peptide';
  if (/adc$|conjugate/.test(n)) return 'ADC';
  if (/bispecific|bifunctional/.test(n)) return 'Bispecific';
  if (/fusion|etanercept|abatacept/.test(n)) return 'Fusion Protein';
  return 'Small Molecule';
}

function assetTarget(row) {
  if (!row) return '—';
  return row.target || row.primary_target || row.targets || '—';
}

// CTK 2026-05-07: TA label formatter for picker display. Backend stores
// TAs concatenated all-caps ("INFECTIOUSDISEASE"); we want display form
// ("Infectious Disease"). Mirror the table in whitespaceiq.jsx so the
// labels match across modules.
var _TA_LABEL_OVERRIDES_CIQ = {
  INFECTIOUSDISEASE: 'Infectious Disease',
  RAREDISEASE: 'Rare Disease',
  WOMENSHEALTH: "Women's Health",
  MENSHEALTH: "Men's Health",
  HEMATOLOGY: 'Hematology',
  MUSCULOSKELETAL: 'Musculoskeletal',
  GASTROINTESTINAL: 'Gastrointestinal',
  REPRODUCTIVE: 'Reproductive',
  RESPIRATORY: 'Respiratory',
  DERMATOLOGY: 'Dermatology',
  ONCOLOGY: 'Oncology',
  NEUROLOGY: 'Neurology',
  IMMUNOLOGY: 'Immunology',
  CARDIOVASCULAR: 'Cardiovascular',
  METABOLIC: 'Metabolic',
};

function _formatTaLabel(ta) {
  if (!ta) return '';
  var raw = String(ta).trim();
  var key = raw.toUpperCase().replace(/[_\s-]+/g, '');
  if (_TA_LABEL_OVERRIDES_CIQ[key]) return _TA_LABEL_OVERRIDES_CIQ[key];
  return raw.charAt(0).toUpperCase() + raw.slice(1).toLowerCase().replace(/_/g, ' ');
}

function _safeOrg(orgName) {
  return String(orgName || '').replace(/'/g, "\\'").replace(/"/g, '&quot;');
}

function _displayOrg(orgName) {
  return String(orgName || '');
}

function orgXLink(orgName, currentModule) {
  var safe = _safeOrg(orgName);
  var display = _displayOrg(orgName);
  var mod = currentModule || 'compete';
  return '<span class="org-xlink" onclick="event.stopPropagation();if(typeof showOrgXMenu===\'function\'){showOrgXMenu(event,\'' + safe + '\',\'' + mod + '\');}" title="Navigate ' + display.replace(/"/g,'&quot;') + ' across modules">' + display + '</span>';
}

function scoreBarHTML(score) {
  var pct = Math.round((score || 0) * 100);
  var display = ((score || 0) * 100).toFixed(1);
  var clr = pct >= 80 ? 'var(--teal)' : pct >= 60 ? 'var(--amber)' : 'var(--text-muted)';
  return '<div style="display:flex;align-items:center;gap:6px">'
    + '<div class="comp-score-bar-track" style="width:60px;height:5px;border-radius:3px;overflow:hidden">'
    + '<div style="height:100%;width:' + pct + '%;background:' + clr + ';border-radius:3px"></div></div>'
    + '<span style="font-size:10px;font-family:var(--font-mono);color:var(--text);min-width:28px">' + display + '</span>'
    + '</div>';
}

// V1 L22957-22963 verbatim (token-aware). Compact 36px×4px bar + 0.00 label.
// Used in TA-mode landscape table for Depth / IP / Momentum columns.
function _miniBar(val) {
  var v = val || 0;
  var pct = Math.round(v * 100);
  var c = v >= 0.7 ? 'var(--teal)' : v >= 0.4 ? 'var(--amber)' : 'var(--rose)';
  return '<div class="comp-mini-bar-cell" style="display:flex;align-items:center;gap:4px;justify-content:center">'
    + '<div class="comp-mini-bar-track" style="width:36px;height:4px;border-radius:2px;overflow:hidden">'
    + '<div style="height:100%;width:' + pct + '%;background:' + c + ';border-radius:2px"></div></div>'
    + '<span style="font-size:10px;font-family:var(--font-mono);color:var(--text-muted);min-width:24px">' + v.toFixed(2) + '</span></div>';
}

function ipCoverageBarHTML(score) {
  var pct = Math.round(_clampUnit(score) * 100);
  var clr = pct >= 80 ? 'var(--teal)' : pct >= 60 ? 'var(--amber)' : 'var(--rose)';
  return '<div class="comp-score-bar-track" style="width:100%;height:5px;border-radius:3px;overflow:hidden;margin-top:4px">'
    + '<div style="height:100%;width:' + pct + '%;background:' + clr + ';border-radius:3px"></div></div>';
}

// UR-018 fix: clamp any 0-1 score to [0,1] (defensive — backend should
// never ship outside that range, but if it does we don't want "10000%").
function _clampUnit(v) {
  var n = (typeof v === 'number' && isFinite(v)) ? v : 0;
  if (n < 0) return 0;
  if (n > 1) return 1;
  return n;
}

// UR-018 fix: format an IP-moat-style 0-1 score as a "NN%" string with a
// real percent sign. Prior render emitted bare "100" + tiny "/100" below,
// which CTK reported as "IP MOAT of 100" (reading the big number as a
// raw count). Now: never ambiguous.
function _formatIpPct(score) {
  return Math.round(_clampUnit(score) * 100) + '%';
}

// Map raw API phase strings to V2's .phase-pill.{p1,p2,p3,p4,approved} class
// names so the light-mode hotfix in modules.css applies. Shared by phasePills(),
// indication renderer, and the competitors table.
function _phaseClass(p) {
  if (!p) return '';
  // Normalize: backend ships short codes ("Ph3", "P3", "APPROVED") AND legacy
  // forms ("PHASE3", "Phase 3"). Make the matcher case-insensitive + accept all.
  const u = String(p).toUpperCase().replace(/\s+/g, '');
  if (u === 'APPROVED' || u === 'MARKETED' || u === 'PHASE4' || u === 'PH4' || u === 'P4') return 'p4';
  if (u === 'PHASE3' || u === 'PH3' || u === 'P3' || u === 'PHASE2/PHASE3' || u === 'PH2/PH3' || u === 'P2/P3') return 'p3';
  if (u === 'PHASE2' || u === 'PH2' || u === 'P2' || u === 'PHASE1/PHASE2' || u === 'PH1/PH2' || u === 'P1/P2') return 'p2';
  if (u === 'PHASE1' || u === 'PH1' || u === 'P1' || u === 'EARLYPHASE1' || u === 'EARLYP1') return 'p1';
  if (u === 'PRECLINICAL' || u === 'PRECLIN' || u === 'PRECLINIC') return 'p0';
  return '';
}

// Audit hygiene: polish raw phase strings for display. Backend ships
// "APPROVED" (uppercase, jarring next to "Ph3") and "Preclinical" (long).
// Map every variant to a short pill label that reads consistently across
// the indication list, competitor table, and KPI tiles.
function _phaseLabel(p) {
  var cls = _phaseClass(p);
  if (cls === 'p4') return 'Approved';
  if (cls === 'p3') return 'P3';
  if (cls === 'p2') return 'P2';
  if (cls === 'p1') return 'P1';
  if (cls === 'p0') return 'Preclin';
  // Unknown — fall back to a cleaned raw string.
  return String(p || '').replace(/PHASE/i, 'P').replace(/_/g, '');
}

function phasePills(drugs) {
  if (!drugs || !drugs.length) return '<span style="color:var(--text-muted);font-size:11px">—</span>';
  // Sort by phase ordinal
  var phaseOrd = {
    'EARLY_PHASE1':0,'PHASE1':1,'PHASE1/PHASE2':2,
    'PHASE2':3,'PHASE2/PHASE3':4,'PHASE3':5,'PHASE4':6,'Approved':7
  };
  var sorted = drugs.slice().sort(function(a,b){
    var pa = phaseOrd[a.phase] !== undefined ? phaseOrd[a.phase] : 99;
    var pb = phaseOrd[b.phase] !== undefined ? phaseOrd[b.phase] : 99;
    return pa - pb;
  });
  return sorted.slice(0, 5).map(function(d) {
    var phaseCls = _phaseClass(d.phase);
    // Audit bug #1: use _phaseLabel for short consistent labels. Was a
    // chain of replaces that handled only the legacy uppercase forms; new
    // backend short-codes ("Ph3", "APPROVED") rendered as long shouty
    // strings inside the tiny pill.
    var label = _phaseLabel(d.phase);
    return '<span class="phase-pill ' + phaseCls + '">' + label + '</span>';
  }).join(' ') + (sorted.length > 5 ? '<span style="font-size:10px;color:var(--text-muted)"> +' + (sorted.length - 5) + '</span>' : '');
}

function _phaseBarHTML(pMap) {
  var total = 0;
  PHASE_BAR_SEGMENTS.forEach(function(seg) {
    seg.phases.forEach(function(k) { total += pMap[k] || 0; });
  });
  if (!total) return '<div style="color:var(--text-muted);font-size:11px">No pipeline data</div>';
  var segs = PHASE_BAR_SEGMENTS.map(function(seg) {
    var cnt = 0;
    seg.phases.forEach(function(k) { cnt += pMap[k] || 0; });
    if (!cnt) return '';
    var pct = (cnt / total * 100).toFixed(1);
    return '<div style="flex:' + pct + ';height:6px;background:' + seg.color + ';min-width:2px" title="' + seg.key + ': ' + cnt + '"></div>';
  }).join('');
  var legend = PHASE_BAR_SEGMENTS.map(function(seg) {
    var cnt = 0;
    seg.phases.forEach(function(k) { cnt += pMap[k] || 0; });
    if (!cnt) return '';
    return '<span style="font-size:9px;color:var(--text-muted)">' + seg.key + ':' + cnt + '</span>';
  }).filter(Boolean).join(' ');
  return '<div style="display:flex;gap:2px;border-radius:3px;overflow:hidden;margin-bottom:4px">' + segs + '</div>'
    + '<div style="display:flex;flex-wrap:wrap;gap:4px">' + legend + '</div>';
}

function radarSVG(scores, size) {
  var w = size || 140, h = size || 132;
  var cx = _rCx, cy = _rCy, R = _rR, n = _rN;
  var axes = COMP_RADAR_AXES;
  var vals = [
    scores && scores.depth     || 0,
    scores && scores.breadth   || 0,
    scores && scores.target    || 0,
    scores && scores.ip        || 0,
    scores && scores.momentum  || 0
  ];
  var isDark = document.documentElement.getAttribute('data-theme') !== 'light';
  var gridStroke = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.07)';
  var labelFill = isDark ? 'rgba(255,255,255,0.55)' : 'rgba(0,0,0,0.55)';
  // Per CTK Q5: --amber should resolve light-aware. Use color-mix() so the
  // radar fill picks up the tan-amber (#D4A574 dark / #d97706 light) at 12% alpha.
  var radarFill = 'color-mix(in srgb, var(--amber) 12%, transparent)';

  function ptAt(i, r) {
    var ang = (Math.PI * 2 / n) * i - Math.PI / 2;
    return [cx + r * Math.cos(ang), cy + r * Math.sin(ang)];
  }

  // Grid rings
  var rings = [0.25, 0.5, 0.75, 1.0].map(function(f) {
    var pts = [];
    for (var i = 0; i < n; i++) { var p = ptAt(i, R * f); pts.push(p[0] + ',' + p[1]); }
    return '<polygon points="' + pts.join(' ') + '" fill="none" stroke="' + gridStroke + '" stroke-width="0.8"/>';
  }).join('');

  // Spokes
  var spokes = '';
  for (var i = 0; i < n; i++) {
    var p = ptAt(i, R);
    spokes += '<line x1="' + cx + '" y1="' + cy + '" x2="' + p[0] + '" y2="' + p[1] + '" stroke="' + gridStroke + '" stroke-width="0.8"/>';
  }

  // Data polygon
  var dataPts = vals.map(function(v, i) {
    var p = ptAt(i, R * Math.min(1, Math.max(0, v)));
    return p[0] + ',' + p[1];
  }).join(' ');

  // Labels
  var LR = R * _rLR;
  var labels = axes.map(function(ax, i) {
    var p = ptAt(i, LR);
    return '<text x="' + p[0] + '" y="' + p[1] + '" text-anchor="middle" dominant-baseline="middle" font-size="8" fill="' + labelFill + '" font-family="var(--font-body)">' + ax + '</text>';
  }).join('');

  return '<svg viewBox="0 0 ' + w + ' ' + h + '" width="' + w + '" height="' + h + '" overflow="visible">'
    + rings + spokes
    + '<polygon points="' + dataPts + '" fill="' + radarFill + '" stroke="var(--amber)" stroke-width="1.5" stroke-linejoin="round"/>'
    + labels
    + '</svg>';
}

function _buildPMap(org) {
  var pMap = {};
  PHASE_MAP_KEYS.forEach(function(k) { pMap[k] = 0; });
  if (org && org.pipeline) {
    org.pipeline.forEach(function(drug) {
      if (drug.phase && pMap.hasOwnProperty(drug.phase)) pMap[drug.phase]++;
    });
  }
  if (org && org.phase_counts) {
    Object.keys(org.phase_counts).forEach(function(k) {
      if (pMap.hasOwnProperty(k)) pMap[k] = org.phase_counts[k] || 0;
    });
  }
  return pMap;
}

// Audit bug #20: prefer the backend's canonical `org_category` (from the
// compete_iq DuckDB column) over the on-the-fly regex inference. Backend
// classifies "Genentech" → PHARMA (acquired by Roche) where the regex
// would say BIOTECH; same with several other aliased orgs. Fall through to
// _inferOrgType only when the backend hasn't shipped a category.
function _resolveOrgType(r) {
  if (!r) return 'OTHER';
  var cat = r.org_category;
  if (cat && typeof cat === 'string') {
    var u = cat.toUpperCase();
    // Map any non-canonical category back into our pill set.
    if (COMP_ORG_TYPES.indexOf(u) >= 0) return u;
  }
  return _inferOrgType(r.org_name || r.organization);
}

function _computeOrgTypeCounts(rows) {
  var counts = {};
  COMP_ORG_TYPES.forEach(function(t) { counts[t] = 0; });
  counts['ALL'] = rows.length;
  rows.forEach(function(r) {
    var t = _resolveOrgType(r);
    counts[t] = (counts[t] || 0) + 1;
  });
  return counts;
}

function _getFilteredData() {
  var rows = _CS.data || [];
  var q = (_CS.searchQ || '').toLowerCase().trim();
  var ot = _CS.orgTypeFilter || 'ALL';
  var out = rows.filter(function(r) {
    var name = (r.org_name || r.organization || '').toLowerCase();
    if (q && !name.includes(q)) return false;
    if (ot !== 'ALL' && _resolveOrgType(r) !== ot) return false;
    return true;
  });
  return out;
}

// ─── 3. BREADCRUMB ────────────────────────────────────────────────────────────

function addBreadcrumb(label, onclick) {
  _CS.breadcrumbs.push({ label: label, onclick: onclick });
  _renderBreadcrumbs();
}

function _renderBreadcrumbs() {
  var el = document.getElementById('comp-breadcrumbs');
  if (!el) return;
  if (!_CS.breadcrumbs.length) { el.style.display = 'none'; return; }
  el.style.display = 'flex';
  el.innerHTML = _CS.breadcrumbs.map(function(b, i) {
    var isLast = i === _CS.breadcrumbs.length - 1;
    return (isLast
      ? '<span style="color:var(--text);font-size:12px">' + b.label + '</span>'
      : '<button class="comp-bc-btn" onclick="' + b.onclick + '">' + b.label + '</button><span style="color:var(--text-muted);font-size:12px;margin:0 4px">›</span>'
    );
  }).join('');
}

// ─── 4. LANDSCAPE SUB-VIEW ────────────────────────────────────────────────────

function renderCompLandscape() {
  var el = document.getElementById('comp-content');
  if (!el) return;

  _CS.view = 'landscape';
  _CS.page = 0;
  _CS.selectedOrg = null;
  _CS.panelOpen = false;

  el.innerHTML = _buildLandscapeShell();

  // Wire up search
  var searchInput = document.getElementById('comp-fb-landscape');
  if (searchInput) {
    searchInput.value = _CS.searchQ || '';
    searchInput.addEventListener('input', function() {
      _CS.searchQ = this.value;
      _CS.page = 0;
      _CS.selectedOrg = null;
      _CS.panelOpen = false;
      _refreshLandscapeTable();
    });
  }

  // Wire up xmod bar (TA / Disease mode switch)
  _bindModeButtons();

  // Wire up org type pills
  _renderOrgTypePills();

  // Load data if needed
  if (!_CS.data) {
    _loadLandscapeData();
  } else {
    _refreshLandscapeTable();
  }
}

function _buildLandscapeShell() {
  return '<div id="comp-landscape-wrap" class="comp-landscape-wrap">'
    // Breadcrumbs
    + '<div id="comp-breadcrumbs" class="comp-breadcrumbs" style="display:none;align-items:center;gap:4px;margin-bottom:12px"></div>'

    // Search + xmod island — xmod on top, compact search below
    + '<div class="comp-toolbar">'

    // Cross-module island first — populated by app.jsx refreshAllXmodBars()
    + '<div id="xmod-island-wrap-compete" class="xmod-island-wrap"></div>'

    + '<div class="diq-search-container" id="comp-fb-landscape-wrap" style="max-width:480px">'
    + '<div class="diq-search-row" id="comp-land-filter-row">'
    + '<span class="diq-search-label">Explore by</span>'
    + '<div class="diq-search-mode-tabs" id="comp-land-mode-tabs">'
    +   '<button class="diq-mode-tab comp-mode-btn' + (!_CS.diseaseMode ? ' active' : '') + '" data-mode="ta" onclick="compLandModeSwitch(\'ta\')">TA</button>'
    +   '<button class="diq-mode-tab comp-mode-btn' + (_CS.diseaseMode ? ' active' : '') + '" data-mode="disease" onclick="compLandModeSwitch(\'disease\')">Disease</button>'
    +   '<button class="diq-mode-tab comp-mode-btn" data-mode="org" onclick="compLandModeSwitch(\'org\')">Org</button>'
    + '</div>'
    + '<div class="diq-search-input-wrap" style="flex:1;min-width:0">'
    +   '<svg class="diq-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>'
    +   '<input id="comp-fb-landscape" type="text" class="diq-search-input" placeholder="' + (_CS.diseaseMode ? 'Search diseases…' : 'Search TAs…') + '" autocomplete="off"/>'
    + '</div>'
    + '<button class="diq-search-btn" type="button" onclick="_refreshLandscapeTable && _refreshLandscapeTable()">Search</button>'
    + '</div>'
    + '</div>'

    + '</div>'
    // Org type pills row
    + '<div id="comp-org-type-row" class="comp-org-type-row" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px"></div>'

    // Two-column flex: table + panel
    + '<div id="comp-layout-wrap" class="comp-layout-wrap">'
    + '<div id="comp-table-col" class="comp-table-col">'
    + '<div id="comp-landscape-tbl" class="dt-wrap"><div class="loader-center"><div class="loader"></div></div></div>'
    + '</div>'
    + '<div id="comp-org-panel" class="comp-org-panel" style="width:0;overflow:hidden;opacity:0"></div>'
    + '</div>'
    + '</div>';
}

function _bindModeButtons() {
  document.querySelectorAll('.comp-mode-btn').forEach(function(btn) {
    btn.addEventListener('click', function() {
      compLandModeSwitch(this.getAttribute('data-mode'));
    });
  });
}

function compLandModeSwitch(mode) {
  _CS.diseaseMode = (mode === 'disease');
  _CS.page = 0;
  _CS.selectedOrg = null;
  _CS.panelOpen = false;
  document.querySelectorAll('.comp-mode-btn').forEach(function(b) {
    b.classList.toggle('active', b.getAttribute('data-mode') === mode);
  });
  _closeOrgPanel();
  if (_CS.diseaseMode && !_CS.diseases.length) {
    window.DASHIQ.fetchCompeteDiseases({ limit: 2000 }).then(function(res) {
      _CS.diseases = (res && (res.results || res.diseases || res)) || [];
      _refreshLandscapeTable();
    }).catch(function() {
      _refreshLandscapeTable();
    });
  } else {
    _refreshLandscapeTable();
  }
}

function compLandSearch() {
  // Exposed for inline onclick compatibility — delegates to input listener
  var el = document.getElementById('comp-fb-landscape');
  if (el) { _CS.searchQ = el.value; _CS.page = 0; _refreshLandscapeTable(); }
}

function toggleOrgTypeFilter(type) {
  _CS.orgTypeFilter = type;
  _CS.page = 0;
  _CS.selectedOrg = null;
  _CS.panelOpen = false;
  _closeOrgPanel();
  _renderOrgTypePills();
  _refreshLandscapeTable();
}

function _renderOrgTypePills() {
  var el = document.getElementById('comp-org-type-row');
  if (!el) return;
  var counts = _CS.orgTypeCounts;
  el.innerHTML = COMP_ORG_TYPES.map(function(t) {
    var cnt = counts[t] !== undefined ? counts[t] : 0;
    var active = _CS.orgTypeFilter === t;
    return '<button class="comp-org-pill' + (active ? ' active' : '') + '" onclick="toggleOrgTypeFilter(\'' + t + '\')" style="min-height:44px;padding:6px 10px;display:inline-flex;align-items:center;gap:6px">'
      + t + (cnt > 0 ? ' <span class="comp-org-pill-count">' + cnt + '</span>' : '')
      + '</button>';
  }).join('');
}

function _loadLandscapeData() {
  var tblEl = document.getElementById('comp-landscape-tbl');
  if (tblEl) tblEl.innerHTML = '<div class="loader-center"><div class="loader"></div></div>';

  var params = { limit: 200 };
  if (_CS.diseaseMode && _CS.disease) params.disease = _CS.disease;
  // DEFECT-019 fix: forward org context when present so the rank query is
  // scoped to that org's competitive neighborhood. Backend `/v2/compete-iq/rank`
  // accepts `org` and re-ranks accordingly.
  if (_CS.org) params.org = _CS.org;

  window.DASHIQ.rankCompete(params).then(function(res) {
    var rows = (res && (res.results || res.rankings || res)) || [];
    if (!Array.isArray(rows)) rows = [];

    // Dedup rows by org_name (keep highest-scoring instance).
    // Sort score-desc first so the first occurrence in iteration is the winner,
    // then drop later duplicates via a Set of seen normalized org names.
    rows.sort(function(a, b) {
      var sa = (a && (a.score || a.compete_score)) || 0;
      var sb = (b && (b.score || b.compete_score)) || 0;
      return sb - sa;
    });
    var seen = new Set();
    rows = rows.filter(function(r) {
      var key = ((r && (r.org_name || r.organization)) || '').toLowerCase().trim();
      if (!key || seen.has(key)) return false;
      seen.add(key);
      return true;
    });

    _CS.data = rows;
    _CS.orgTypeCounts = _computeOrgTypeCounts(rows);
    _CS.filteredData = _getFilteredData();
    _renderOrgTypePills();
    _renderLandscapeTable(_CS.filteredData);
  }).catch(function(err) {
    if (tblEl) tblEl.innerHTML = _renderErrorCard('Failed to load competitive landscape data.');
  });
}

function _refreshLandscapeTable() {
  _CS.filteredData = _getFilteredData();
  _renderLandscapeTable(_CS.filteredData);
}

// Fix 6 — V1 column reconciliation. Disease mode renders Org/Score/Drug+(N)/Modality/IP Moat/Trial Vol;
// TA mode renders Org/Score/Depth/IP/Momentum (mini-bars). Mirrors V1 L22850-22968 markup.
function _renderLandscapeTable(rows) {
  var el = document.getElementById('comp-landscape-tbl');
  if (!el) return;

  if (!rows || !rows.length) {
    el.innerHTML = '<div class="empty-state">'
      + '<div class="empty-state-icon">—</div>'
      + '<div class="empty-state-title">No competitors match filters</div>'
      + '<div class="empty-state-desc">Try adjusting your therapeutic area, disease, or org type filters.</div>'
      + '</div>';
    return;
  }

  var start = _CS.page * COMP_PAGE_SIZE;
  var pageRows = rows.slice(start, start + COMP_PAGE_SIZE);
  var isDis = _CS.diseaseMode;

  // V1 L22884-22895: header columns differ per mode. No # rank column — V1 implies
  // rank from Score-desc ordering. We keep the orgs sorted score-desc (per
  // _loadLandscapeData) so visual rank reads correctly.
  var hdr = isDis
    ? '<tr>'
      + '<th style="min-width:110px;text-align:left">Organization</th>'
      + '<th style="min-width:70px;width:80px">Score</th>'
      + '<th style="min-width:120px;text-align:left">Drug</th>'
      + '<th style="min-width:75px;width:85px">Modality</th>'
      + '<th style="min-width:55px;width:65px">IP Moat</th>'
      + '<th style="min-width:55px;width:65px">Trial Vol.</th>'
      + '</tr>'
    : '<tr>'
      + '<th style="min-width:110px;text-align:left">Organization</th>'
      + '<th style="min-width:65px;width:75px">Score</th>'
      + '<th style="min-width:55px;width:65px">Depth</th>'
      + '<th style="min-width:55px;width:65px">IP</th>'
      + '<th style="min-width:55px;width:65px">Momentum</th>'
      + '</tr>';

  var tbody = pageRows.map(function(r, i) {
    var orgName = r.org_name || r.organization || '—';
    var score = r.score || r.compete_score || 0;
    var isSelected = _CS.selectedOrg && (_CS.selectedOrg.org_name || _CS.selectedOrg.organization) === orgName;

    var cols;
    if (isDis) {
      // ── Disease-filtered: competitive positioning columns (V1 L22920-22949) ──
      var ipM = _clampUnit(r.ip_strength_score || r.ip_strength || r.ip_score || 0);
      var trV = r.n_trials || 0;
      // DEFECT-029 fix: show RAW trial count (e.g. "84") instead of the
      // log-normalized 0-1 score (which read as "0.50" — confusing, looks
      // like a probability). Color is still derived from the normalized
      // bucket for relative magnitude, but the displayed number is the
      // count the user expects from a column called "Trial Vol".
      var trNorm = Math.min(1, Math.log1p(trV) / Math.log1p(500));
      var trColor = trNorm < 0.3 ? 'var(--rose)' : trNorm >= 0.5 ? 'var(--text)' : 'var(--text-muted)';
      var ipColor = ipM >= 0.5 ? 'var(--text)' : ipM < 0.1 ? 'var(--rose)' : 'var(--text-muted)';
      var ipWeight = ipM >= 0.5 ? '700' : '400';
      var trWeight = trNorm >= 0.5 ? '700' : '400';

      // Lead drug + count pill — audit bug #14: `org_drugs` is capped to top
      // 5 in DuckDB but backend ships the true total in `n_drugs` /
      // `n_drugs_actual`. Prefer those over the visible pipe-list length so
      // the (+N) pill reflects true pipeline depth instead of "+4".
      var drugList = r._drug_list || (typeof r.org_drugs === 'string' ? r.org_drugs.split('|').filter(function(s) { return s && s.trim(); }) : []);
      var drugCount = r._drug_count || r.n_drugs_actual || r.n_drugs || drugList.length || 0;
      // Coerce float (`n_drugs` ships as 60.0) to int for clean display.
      drugCount = Math.max(0, Math.floor(drugCount));
      var leadDrug = (r.lead_drug_name && r.lead_drug_name !== 'Unknown')
        ? r.lead_drug_name
        : (drugList.length > 0 ? drugList[0] : '');
      var drugHTML = '';
      if (leadDrug) {
        drugHTML = '<span style="font-weight:600;color:var(--text);font-size:11px">' + leadDrug + '</span>';
        if (drugCount > 1) {
          var tooltip = drugList.slice(0, 10).join(', ') + (drugCount > 10 ? '...' : '');
          drugHTML += ' <span class="comp-drug-count-pill" style="font-size:9px;font-family:var(--font-mono);color:var(--teal);font-weight:700;cursor:help" title="' + tooltip.replace(/"/g, '&quot;') + '">(+' + (drugCount - 1) + ')</span>';
        }
      } else {
        drugHTML = '<span style="color:var(--text-muted);font-size:10px">--</span>';
      }

      // Top modality pill
      var modLabel = r._top_modality || r.lead_modality || r.modality || (leadDrug ? assetModality(leadDrug) : '');
      var modHTML = modLabel
        ? '<span class="comp-modality-pill">' + modLabel + '</span>'
        : '<span style="color:var(--text-muted);font-size:9px">--</span>';

      // DEFECT-029 fix: render Trial Vol as the raw count + a tiny
      // normalized indicator dot, NOT the bare 0-1 normalized score.
      // The column is named "Trial Vol" — users expect a count.
      var trDisplay = trV > 0
        ? trV.toLocaleString()
        : '<span style="color:var(--text-faint)">—</span>';
      cols = '<td>' + orgXLink(orgName, 'compete') + '</td>'
        + '<td>' + scoreBarHTML(score) + '</td>'
        + '<td>' + drugHTML + '</td>'
        + '<td>' + modHTML + '</td>'
        + '<td style="font-family:var(--font-mono);font-size:11.5px;font-weight:' + ipWeight + ';color:' + ipColor + ';text-align:center">' + ipM.toFixed(2) + '</td>'
        + '<td style="font-family:var(--font-mono);font-size:11.5px;font-weight:' + trWeight + ';color:' + trColor + ';text-align:center">' + trDisplay + '</td>';

    } else {
      // ── TA-wide: competitive profile scores (V1 L22952-22968) ──
      var depthV = r.pipeline_depth_score || (r.scores && r.scores.depth) || 0;
      var ipV    = r.ip_strength_score || (r.scores && (r.scores.ip || r.scores.ip_moat)) || 0;
      var momV   = r.momentum_score || (r.scores && r.scores.momentum) || 0;

      cols = '<td>' + orgXLink(orgName, 'compete') + '</td>'
        + '<td>' + scoreBarHTML(score) + '</td>'
        + '<td>' + _miniBar(depthV) + '</td>'
        + '<td>' + _miniBar(ipV) + '</td>'
        + '<td>' + _miniBar(momV) + '</td>';
    }

    var safeOrgName = String(orgName).replace(/'/g, "\\'");
    return '<tr class="comp-row clickable result-in' + (isSelected ? ' row-hl selected' : '') + '" data-org="' + String(orgName).replace(/"/g,'&quot;') + '" style="animation-delay:' + (i * 0.015) + 's" onclick="selectCompOrg(\'' + safeOrgName + '\')">' + cols + '</tr>';
  }).join('');

  var tableHTML = '<table class="iq-table comp-landscape-table"><caption class="sr-only">CompeteIQ competitive landscape</caption><thead>' + hdr + '</thead><tbody>' + tbody + '</tbody></table>';

  // Pagination (V1 L22980-22984: "1/8" mono format + "← Prev"/"Next →" labels).
  var totalPages = Math.ceil(rows.length / COMP_PAGE_SIZE);
  var paginHTML = '';
  if (totalPages > 1) {
    var prevDisabled = _CS.page === 0;
    var nextDisabled = _CS.page >= totalPages - 1;
    paginHTML = '<div class="iq-pagination" style="margin-top:12px;display:flex;align-items:center;gap:10px;justify-content:center">'
      + '<button onclick="window._compLandPage(' + (_CS.page - 1) + ')" ' + (prevDisabled ? 'disabled' : '') + ' style="min-height:44px;min-width:44px">← Prev</button>'
      + '<span class="iq-page-indicator" style="font-family:var(--font-mono);font-size:9px;color:var(--text);font-weight:600">' + (_CS.page + 1) + '/' + totalPages + '</span>'
      + '<button onclick="window._compLandPage(' + (_CS.page + 1) + ')" ' + (nextDisabled ? 'disabled' : '') + ' style="min-height:44px;min-width:44px">Next →</button>'
      + '</div>';
  }

  el.innerHTML = tableHTML + paginHTML;
}

window._compLandPage = function(p) {
  var rows = _CS.filteredData || [];
  var totalPages = Math.ceil(rows.length / COMP_PAGE_SIZE);
  _CS.page = Math.max(0, Math.min(p, totalPages - 1));
  _renderLandscapeTable(rows);
};

// ─── 5. ORG PANEL ─────────────────────────────────────────────────────────────

function selectCompOrg(orgName) {
  var rows = _CS.filteredData || _CS.data || [];
  var org = rows.find(function(r) { return (r.org_name || r.organization) === orgName; });
  if (!org) return;
  _CS.selectedOrg = org;
  _CS.panelOpen = true;

  // Highlight selected row
  document.querySelectorAll('.comp-row').forEach(function(tr) {
    tr.classList.remove('selected');
    tr.classList.remove('row-hl');
  });
  // Re-render to apply class correctly
  _renderLandscapeTable(_CS.filteredData || rows);

  _openOrgPanel(org);
}

function _openOrgPanel(org) {
  var panel = document.getElementById('comp-org-panel');
  if (!panel) return;

  var isMobile = window.innerWidth <= 600;
  if (isMobile) {
    _renderOrgPanelInline(org);
    return;
  }

  panel.style.width = '420px';
  panel.style.opacity = '1';
  panel.style.overflow = 'visible';
  panel.innerHTML = _buildOrgPanelHTML(org);
}

function _closeOrgPanel() {
  var panel = document.getElementById('comp-org-panel');
  if (!panel) return;
  panel.style.width = '0';
  panel.style.opacity = '0';
  panel.style.overflow = 'hidden';
  panel.innerHTML = '';
  _CS.panelOpen = false;
  _CS.selectedOrg = null;
}

function _renderOrgPanelInline(org) {
  // Mobile: inline accordion below clicked row
  var existing = document.getElementById('comp-mobile-panel');
  if (existing) existing.remove();
  var orgName = org.org_name || org.organization || '';
  var panelEl = document.createElement('tr');
  panelEl.id = 'comp-mobile-panel';
  panelEl.innerHTML = '<td colspan="6" style="padding:0">' + _buildOrgPanelHTML(org) + '</td>';
  var rows = document.querySelectorAll('.comp-row');
  var targetRow = null;
  rows.forEach(function(r) {
    if (r.textContent.includes(orgName)) targetRow = r;
  });
  if (targetRow && targetRow.parentNode) {
    targetRow.parentNode.insertBefore(panelEl, targetRow.nextSibling);
  }
}

// Fix 3 (quick-pills) + Fix 4 (Score Breakdown accordion) live here.
// V1 reference: L22440-22550 of index_LG_PROD.html.
function _buildOrgPanelHTML(org) {
  var orgName = org.org_name || org.organization || '—';
  var score = org.score || org.compete_score || 0;
  var scores = org.scores || org.score_breakdown || {};
  var pipeline = org.pipeline || [];
  var pMap = _buildPMap(org);
  var orgType = _resolveOrgType(org);

  // Resolve raw scores once so the radar + Score Breakdown share identical values.
  var rawDepth   = (scores.depth) || (org.pipeline_depth_score) || 0;
  var rawBreadth = (scores.breadth) || (org.pipeline_breadth_score) || 0;
  var rawTarget  = (scores.target_affinity || scores.target) || (org.target_coverage_score) || 0;
  var rawIP      = (scores.ip_moat || scores.ip) || (org.ip_strength_score) || 0;
  var rawMom     = (scores.momentum) || (org.momentum_score) || 0;

  // Radar SVG
  var radarHTML = radarSVG({
    depth: rawDepth,
    breadth: rawBreadth,
    target: rawTarget,
    ip: rawIP,
    momentum: rawMom
  }, 140);

  // Fix 4 — Score Breakdown table. Weights MUST sum to 100 (25+15+20+20+20).
  var breakdownRows = [
    { label: 'Depth',     w: 25, raw: rawDepth },
    { label: 'Breadth',   w: 15, raw: rawBreadth },
    { label: 'Target',    w: 20, raw: rawTarget },
    { label: 'IP',        w: 20, raw: rawIP },
    { label: 'Momentum',  w: 20, raw: rawMom }
  ].map(function(row) {
    var raw = row.raw || 0;
    var wtd = raw * (row.w / 100);
    var rawColor = raw >= 0.7 ? 'var(--teal)' : raw >= 0.4 ? 'var(--amber)' : 'var(--rose)';
    return '<tr class="score-bd-row" style="font-size:11px">'
      + '<td style="padding:3px 6px;color:var(--text-muted)">' + row.label + '</td>'
      + '<td style="padding:3px 6px;font-family:var(--font-mono);text-align:right;color:' + rawColor + ';font-weight:600">' + raw.toFixed(2) + '</td>'
      + '<td style="padding:3px 6px;text-align:right;color:var(--text-muted)">×' + row.w + '%</td>'
      + '<td style="padding:3px 6px;font-family:var(--font-mono);text-align:right;color:var(--rose);font-weight:700">' + wtd.toFixed(3) + '</td>'
      + '</tr>';
  }).join('');

  // Pipeline drugs sorted by phase
  var phaseOrd = { 'EARLY_PHASE1':0,'PHASE1':1,'PHASE1/PHASE2':2,'PHASE2':3,'PHASE2/PHASE3':4,'PHASE3':5,'PHASE4':6 };
  var sortedDrugs = pipeline.slice().sort(function(a,b) {
    return (phaseOrd[a.phase] || 99) - (phaseOrd[b.phase] || 99);
  });

  var pipelineHTML = sortedDrugs.length
    ? sortedDrugs.slice(0, 8).map(function(d) {
        var phClr = _phaseColors[d.phase] || 'var(--text-muted)';
        var phLabel = String(d.phase || '').replace('EARLY_PHASE1','EP1').replace('PHASE1/PHASE2','I/II').replace('PHASE2/PHASE3','II/III').replace('PHASE','P').replace('_','');
        return '<div style="display:flex;justify-content:space-between;align-items:center;padding:4px 0;border-bottom:1px solid var(--g-border)">'
          + '<span style="font-size:12px;color:var(--text)">' + (d.drug_name || d.name || '—') + '</span>'
          + '<span style="font-size:10px;font-family:var(--font-mono);color:' + phClr + '">' + phLabel + '</span>'
          + '</div>';
      }).join('')
      + (sortedDrugs.length > 8 ? '<div style="font-size:11px;color:var(--text-muted);padding-top:4px">+' + (sortedDrugs.length - 8) + ' more</div>' : '')
    : '<div style="font-size:12px;color:var(--text-muted)">No pipeline data available.</div>';

  // Cross-module cards
  var crossCards = [
    { label: 'Whitespace', icon: '◎', onclick: 'if(typeof switchModule===\'function\')switchModule(\'opp\')' },
    { label: 'Partner',    icon: '◈', onclick: 'if(typeof switchModule===\'function\')switchModule(\'strategy\')' }
  ].map(function(c) {
    return '<button class="comp-xmod-card" onclick="' + c.onclick + '">' + c.icon + ' ' + c.label + '</button>';
  }).join('');

  // Fix 3 — Quick-pills row (V1 L22454-22479): TA / TGT / DRUG. Three semantic
  // categories with rose / teal / blue tints respectively. Each pill has a tiny
  // mono-uppercase prefix tag followed by the value. Rendered between the
  // header and the radar card per V1 layout.
  var quickTA   = org.ta || org.therapeutic_area || org._top_ta || '';
  // _all_targets is V1's pipe-split union; fall back to single-target field.
  var quickTgts = [];
  if (Array.isArray(org.targets)) {
    quickTgts = org.targets.slice();
  } else if (Array.isArray(org._all_targets)) {
    quickTgts = org._all_targets.slice();
  } else if (typeof org.targets === 'string') {
    quickTgts = org.targets.split(/\s*\|\s*/).filter(Boolean);
  }
  if (org.lead_target) quickTgts.push(org.lead_target);
  if (org.target) quickTgts.push(org.target);
  // Dedup
  var seenT = new Set();
  quickTgts = quickTgts.filter(function(t) {
    if (!t || seenT.has(t)) return false;
    seenT.add(t);
    return true;
  });
  var quickDrug = org.lead_drug_name || org._top_drug || (pipeline[0] && (pipeline[0].drug_name || pipeline[0].name)) || '';
  var quickPillsHTML = '';
  if (quickTA || quickTgts.length || quickDrug) {
    quickPillsHTML = '<div class="comp-quick-pills">';
    if (quickTA) {
      quickPillsHTML += '<span class="comp-quick-pill comp-quick-pill-ta"><span class="comp-quick-pill-tag">TA</span>' + quickTA + '</span>';
    }
    quickTgts.slice(0, 3).forEach(function(t) {
      quickPillsHTML += '<span class="comp-quick-pill comp-quick-pill-tgt"><span class="comp-quick-pill-tag">TGT</span>' + t + '</span>';
    });
    if (quickTgts.length > 3) {
      quickPillsHTML += '<span class="comp-quick-pill-more">+' + (quickTgts.length - 3) + '</span>';
    }
    if (quickDrug) {
      quickPillsHTML += '<span class="comp-quick-pill comp-quick-pill-drug"><span class="comp-quick-pill-tag">DRUG</span>' + quickDrug + '</span>';
    }
    quickPillsHTML += '</div>';
  }

  return '<div class="comp-org-panel-inner">'
    // Header + close
    + '<div class="org-panel-header">'
    + '<div>'
    + '<div style="font-size:15px;font-weight:600;color:var(--text)">' + orgXLink(orgName, 'compete') + '</div>'
    + '<div style="font-size:11px;color:var(--text-muted);margin-top:2px">' + orgType + '</div>'
    + '</div>'
    + '<button class="org-panel-close" onclick="_closeOrgPanel()" style="min-height:44px;min-width:44px;cursor:pointer;border:none;background:none;color:var(--text-muted);border-radius:6px" title="Close">'
    + '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'
    + '</button>'
    + '</div>'

    // Fix 3 — Quick-pills row (TA / TGT / DRUG)
    + quickPillsHTML

    // Score + radar
    + '<div style="display:flex;gap:12px;align-items:flex-start;margin-bottom:16px">'
    + '<div>' + radarHTML + '</div>'
    + '<div style="flex:1">'
    + '<div style="font-size:22px;font-weight:700;font-family:var(--font-mono);color:var(--teal)">' + (score * 100).toFixed(1) + '</div>'
    + '<div style="font-size:11px;color:var(--text-muted);margin-bottom:8px">Compete Score</div>'
    + scoreBarHTML(score)
    + '</div>'
    + '</div>'

    // Phase distribution bar
    + '<div style="margin-bottom:16px">'
    + '<div style="font-size:11px;color:var(--text-muted);margin-bottom:6px;font-weight:500;text-transform:uppercase;letter-spacing:0.05em">Phase Distribution</div>'
    + _phaseBarHTML(pMap)
    + '</div>'

    // Fix 4 — Score Breakdown accordion (Factor / Raw / Weight / Wtd; weights 25+15+20+20+20=100)
    + '<details class="org-panel-section score-breakdown" open>'
    + '<summary style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);cursor:pointer;padding:6px 0">Score Breakdown</summary>'
    + '<table class="comp-score-breakdown-table" style="width:100%;border-collapse:collapse;margin-top:4px">'
    + '<thead><tr style="font-size:10px;color:var(--text-muted)">'
    + '<th style="padding:3px 6px;text-align:left">Factor</th>'
    + '<th style="padding:3px 6px;text-align:right">Raw</th>'
    + '<th style="padding:3px 6px;text-align:right">Weight</th>'
    + '<th style="padding:3px 6px;text-align:right">Wtd</th>'
    + '</tr></thead>'
    + '<tbody>' + breakdownRows + '</tbody>'
    + '<tfoot><tr style="border-top:1.5px solid var(--g-border)"><td colspan="3" style="padding:4px 6px;font-weight:700;color:var(--text);font-size:9px">Score</td><td style="padding:4px 6px;text-align:right;font-weight:800;color:var(--rose);font-size:10px">' + Math.round(score * 100) + '</td></tr></tfoot>'
    + '</table>'
    + '<div style="margin-top:3px;font-size:9px;font-family:var(--font-mono);color:var(--text-muted);font-style:italic">Weights are configurable per org</div>'
    + '</details>'

    // Pipeline
    + '<details class="org-panel-section" open>'
    + '<summary style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);cursor:pointer;padding:6px 0">Pipeline Drugs</summary>'
    + '<div style="margin-top:6px">' + pipelineHTML + '</div>'
    + '</details>'

    // Drug Profile link
    + '<div style="margin-top:16px;padding-top:12px;border-top:1px solid var(--g-border)">'
    + '<button class="comp-view-profile-btn" onclick="loadDrugProfile(\'' + orgName.replace(/'/g,"\\'") + '\')">'
    + 'View Drug Profile →'
    + '</button>'
    + '</div>'

    // Cross-module cards
    + '<div style="display:flex;gap:8px;margin-top:12px">' + crossCards + '</div>'

    + '</div>';
}

// ─── 6. VIEW SWITCHER ─────────────────────────────────────────────────────────

// ── Phase 4 (2026-05-03) ──────────────────────────────────────────────────
// Backward-compat shim. Pre-Phase-4 code (inline back-buttons inside the
// drug profile body, app.jsx subview switches) calls setCompView('landscape')
// or setCompView('drug-ind'). In the unified anchor model:
//   - 'landscape' → restore the most recent SPACE anchor (TA/Disease/etc.),
//     or the default Oncology TA if no prior space anchor exists.
//   - 'drug-ind'  → no-op (drug anchor is set via _anchorOnDrug instead).
// This keeps existing call-sites working until they're migrated to the new
// anchor API directly.
function setCompView(view) {
  if (view === 'landscape') {
    var back = (_CV._lastSpaceAnchor) || _DEFAULT_ANCHOR;
    _setAnchor(back);
    return;
  }
  // Legacy drug-ind path — fall through to the original implementation
  // for anything that still depends on it.
  return _legacy_setCompView(view);
}

function _legacy_setCompView(view) {
  if (typeof window !== 'undefined' && window.STATE) {
    window.STATE.comp_view = view;
  }
  _CS.view = view;
  if (view === 'landscape') {
    _CS.breadcrumbs = [];
  }
  // Re-enter renderCompeteIQ so the sub-view tabs reflect the new active state
  // alongside the new sub-view body.
  renderCompeteIQ();
}

// ─── 7. DRUG PROFILE SUB-VIEW ─────────────────────────────────────────────────

function loadDrugProfile(orgName) {
  _CS.drugOrg = orgName || '';
  _CS.view = 'drug-ind';
  _CS.breadcrumbs = [];
  if (typeof window !== 'undefined' && window.STATE) {
    window.STATE.comp_view = 'drug-ind';
  }
  renderDrugProfileView();
  addBreadcrumb('Landscape', 'setCompView(\'landscape\')');
  if (orgName) addBreadcrumb(orgName, '');
}

// Fix 2 — V1 L21770-21777 auto-load. On first paint with no drug context,
// seed STATE.comp_drug_name + _CS.drugName to 'Pembrolizumab' (V1 default at
// line 21775) and call loadDrugProfileData. The picker still renders if the
// API returns no profile, so users can switch drugs via _buildDrugProfilePicker.
function renderDrugProfileView() {
  var el = document.getElementById('comp-content');
  if (!el) return;

  el.innerHTML = '<div id="drug-profile-body" class="drug-intel">'
    + '<div class="loader-center"><div class="loader"></div></div>'
    + '</div>';

  var breadcrumbsEl = document.getElementById('comp-breadcrumbs');
  if (!breadcrumbsEl) {
    var bc = document.createElement('div');
    bc.id = 'comp-breadcrumbs';
    bc.className = 'comp-breadcrumbs';
    bc.style.cssText = 'display:none;align-items:center;gap:4px;margin-bottom:12px';
    el.insertBefore(bc, el.firstChild);
  }

  _renderBreadcrumbs();

  // V1 default: 'Pembrolizumab'. Persist to STATE so the cross-module bar +
  // any deep-link logic that reads STATE.comp_drug_name picks up the same
  // canonical default.
  if (!_CS.drugName && !_CS.drugOrg) {
    _CS.drugName = 'Pembrolizumab';
    if (typeof window !== 'undefined' && window.STATE) {
      if (!window.STATE.comp_drug_name) window.STATE.comp_drug_name = 'Pembrolizumab';
    }
  }

  if (_CS.drugOrg) {
    loadDrugProfileData(_CS.drugOrg, _CS.drugName);
  } else if (_CS.drugName) {
    loadDrugProfileData('', _CS.drugName);
  }
}

function loadDrugProfileData(orgName, drugName) {
  _CS.drugOrg = orgName || '';
  _CS.drugName = drugName || '';
  _CS.diqPage = 0;
  _CS.diqSelectedRow = null;
  _CS.diqEvidenceOpen = false;

  var el = document.getElementById('drug-profile-body');
  if (!el) return;

  // Skeleton timeout
  var skeletonTimeout = setTimeout(function() {
    if (el) el.innerHTML = _renderErrorCard('Drug profile data is taking too long to load. Please try again.', true);
  }, 15000);

  var params = {};
  if (drugName) params.drug_name = drugName;
  if (orgName) params.org = orgName;

  window.DASHIQ.fetchDrugProfiles(params).then(function(res) {
    clearTimeout(skeletonTimeout);
    var data = (res && (res.results || res.profiles || res.data || res)) || null;
    if (Array.isArray(data)) data = data[0] || null;
    data = _normalizeDrugProfile(data);
    _CS.drugProfileData = data;
    _renderDrugProfileShell(data, orgName);
    // Hydrate competitors in parallel — only when the backend didn't embed
    // them. Audit bug #13: backend ships `data.competitors` per contract §9.
    // If those rows are present, fire-and-overwrite with a second `target=`
    // query both wastes bandwidth and triggers the boundary-SQL miss
    // (audit bug #8). Skip the hydrate when we already have data.
    var hasEmbedded = data && Array.isArray(data.competitors) && data.competitors.length > 0;
    if (!hasEmbedded && data && (data.target_names || (data.targets && data.targets.length))) {
      _hydrateCompetitors(data);
    }
  }).catch(function(err) {
    clearTimeout(skeletonTimeout);
    if (el) el.innerHTML = _renderErrorCard('Failed to load drug profile.');
  });
}

/**
 * Normalize the raw `/api/v1/drug-profiles` response into the shape the
 * render path expects. The API returns pipe-separated strings for
 * `disease_names` and `target_names`; the renderer wants `indications` and
 * `targets` arrays. We also derive `mechanism_of_action` and an
 * approximate `ip_moat_score` from `n_patents` (0-100 score).
 *
 * The renderer's existing `data.indications || data.diseases` fallback chain
 * stays compatible — we just populate the keys it expects.
 */
function _normalizeDrugProfile(data) {
  if (!data || typeof data !== 'object') return data;
  // Diseases / indications
  if (!data.indications && typeof data.disease_names === 'string') {
    data.indications = data.disease_names.split('|').map(function(s) {
      return { disease: s.trim() };
    }).filter(function(o) { return o.disease.length > 0; });
  }
  // Targets
  if (!data.targets && typeof data.target_names === 'string') {
    data.targets = data.target_names.split('|').map(function(s) { return s.trim(); }).filter(Boolean);
  }
  // Mechanism of action
  if (!data.mechanism_of_action && data.moa_name) data.mechanism_of_action = data.moa_name;
  if (!data.moa && data.moa_name) data.moa = data.moa_name;
  // IP moat score — log-scale n_patents. Audit bug #11: denominator must match
  // backend (log1p(50)) so the fallback path doesn't drift from the canonical
  // formula. Backend always ships ip_moat_score so this is rarely fired, but
  // when it is, the values must agree.
  if (data.ip_moat_score == null && typeof data.n_patents === 'number') {
    data.ip_moat_score = Math.min(1, Math.log1p(data.n_patents) / Math.log1p(50));
  }
  // Phase — backend now ships `phase` directly as a string ("APPROVED",
  // "Ph3", "Preclinical"). Older snapshots ship `max_phase` as a numeric
  // 0-4. Handle both: if max_phase is a number, prefix with "PHASE"; if
  // it's already a non-empty string, pass it through.
  if (!data.phase && data.max_phase != null) {
    data.phase = (typeof data.max_phase === 'number')
      ? ('PHASE' + data.max_phase)
      : String(data.max_phase);
  }
  // Default empty competitors (hydrated separately)
  if (!data.competitors) data.competitors = [];
  return data;
}

/**
 * Fetch competitors — drugs that share at least one target with the active
 * drug. Renders into the competitors table when results arrive.
 */
function _hydrateCompetitors(drug) {
  if (!drug || !window.DASHIQ || typeof window.DASHIQ.fetchDrugProfiles !== 'function') return;
  var firstTarget = (drug.targets && drug.targets[0]) || (drug.target_names && drug.target_names.split('|')[0].trim());
  if (!firstTarget) return;
  // The drug-profiles endpoint accepts a `target` filter and returns drugs
  // sharing that target. Cap at 25 — Page 1 of the table shows ≤10.
  window.DASHIQ.fetchDrugProfiles({ limit: 25, target: firstTarget }).then(function(res) {
    var list = (res && (res.results || res.data)) || [];
    if (!Array.isArray(list)) return;
    // Filter out the active drug itself; normalize each row for the table.
    // Audit bug #3 + #4: forward through compete_score, phase, ip_moat_score
    // and moa so the competitor table renders coloured Score, phase pill,
    // IP Strength label, and MOA tooltip — instead of "0.0 / Weak / —" for
    // every row.
    var competitors = list
      .filter(function(d) { return d && d.drug_name && d.drug_name !== drug.drug_name; })
      .map(function(d) {
        return {
          drug_name: d.drug_name,
          org_name: d.org_name || '—',
          targets: d.target_names ? d.target_names.split('|').map(function(s){return s.trim();}).slice(0, 3) : [],
          target_names: d.target_names || '',
          modality: d.modality || '—',
          phase: d.phase || d.max_phase || '—',
          max_phase: d.max_phase != null ? d.max_phase : '—',
          n_trials: d.n_trials || 0,
          n_patents: d.n_patents || 0,
          compete_score: d.compete_score || 0,
          ip_moat_score: d.ip_moat_score != null ? d.ip_moat_score : null,
          ip_score: d.ip_score != null ? d.ip_score : null,
          moa: d.moa || d.moa_name || d.mechanism_of_action || '',
          mechanism_of_action: d.mechanism_of_action || d.moa_name || d.moa || ''
        };
      });
    drug.competitors = competitors;
    if (typeof renderDiqTable === 'function') renderDiqTable(competitors);
    // Audit bug #15: auto-open the first competitor *after* hydration
    // completes. The previous setTimeout(50ms) inline in
    // _renderDrugProfileShell raced against this fetch — the drawer opened
    // empty and never reopened when results arrived. Now we trigger after
    // the table renders with real rows.
    var first = competitors && competitors[0];
    if (first && typeof openDiqDrawer === 'function') openDiqDrawer(first, 0);
  }).catch(function() {
    // Soft fail — empty competitors means the table just shows the empty-state
    drug.competitors = [];
  });
}

function _renderDrugProfileShell(data, orgName) {
  var el = document.getElementById('drug-profile-body');
  if (!el) return;

  if (!data) {
    // If no specific drug profile, show search/picker UI (silent-fallback)
    el.innerHTML = _buildDrugProfilePicker(orgName);
    _loadDrugNames();
    return;
  }

  var drugName = data.drug_name || data.name || _CS.drugName || 'Unknown Drug';
  var maxPhase = data.max_phase || data.phase || '—';
  var targets = data.targets || data.primary_target || '—';
  var modality = data.modality || assetModality(drugName);
  var moa = data.mechanism_of_action || data.moa || '—';
  var ipScore = data.ip_moat_score || data.ip_score || 0;
  var indications = data.indications || data.diseases || [];
  var competitors = data.competitors || data.competing_drugs || [];
  // Audit bug #2: defensive parser. Backend currently ships safety_profile
  // as a dict (empty {} when no data). Older snapshots / cached responses
  // may still arrive as a pipe-delimited "key: value" string ("DDI Risk:
  // HIGH (853 interactions) | CYP Inhibitor: CYP3A4:weak"). Object.keys on
  // a string yields character-index keys ("0","1","2"...) — the bug in
  // the audit. Detect string and parse to {} so safety renders correctly
  // in either shape.
  var rawSafety = data.safety_profile || data.adverse_events || {};
  var safetyProfile;
  if (typeof rawSafety === 'string') {
    safetyProfile = {};
    rawSafety.split('|').forEach(function(part) {
      var pieces = part.split(':');
      if (pieces.length >= 2) {
        var k = pieces[0].trim();
        var v = pieces.slice(1).join(':').trim();
        if (k) safetyProfile[k] = v;
      }
    });
  } else if (rawSafety && typeof rawSafety === 'object') {
    safetyProfile = rawSafety;
  } else {
    safetyProfile = {};
  }

  // Compose KPI strip (4 tiles per drugprofile.md L148: 1.4fr + 3×1fr).
  // JSX→CSS reconciliation: emit defined .diq-kpi-tile, .diq-kpi-tile-label,
  // .diq-kpi-tile-value classes (modules.css:1285-1314) instead of the
  // orphan .diq-kpi-cell / .diq-kpi-label / .diq-kpi-value names.
  var targetsDisplay = Array.isArray(targets) ? targets.join(', ') : String(targets);
  // Audit bug #1 cosmetic: use _phaseLabel so MAX PHASE tile renders
  // "Approved" / "P3" instead of the raw "APPROVED" / "PHASE 3" forms.
  var maxPhaseDisplay = maxPhase === '—' ? '—' : _phaseLabel(maxPhase);
  // UR-018 fix: clamp ip_moat to [0,1] then render as a percent. Previously
  // a saturated `ip_moat_score = 1.0` produced bare "100" with the unit
  // hidden in a tiny "/100" suffix, reading like a raw count to users.
  // Now: explicit "%" suffix (no ambiguity) and clamp guards against any
  // malformed backend value.
  var ipPctDisplay = _formatIpPct(ipScore);
  var kpiCells = [
    { label: 'MAX PHASE', value: maxPhaseDisplay, accent: true },
    { label: 'TARGETS', value: targetsDisplay },
    { label: 'MODALITY', value: modality },
    { label: 'IP MOAT', value: ipCoverageBarHTML(ipScore) + '<div style="font-size:10px;font-family:var(--font-mono);color:var(--text);margin-top:2px">' + ipPctDisplay + '</div>' }
  ].map(function(k) {
    return '<div class="diq-kpi-tile">'
      + '<div class="diq-kpi-tile-label">' + k.label + '</div>'
      + '<div class="diq-kpi-tile-value' + (k.accent ? ' accent' : '') + '">' + k.value + '</div>'
      + '</div>';
  }).join('');

  // Indications list — map raw API phase strings to .phase-pill classes so the
  // light-mode hotfix block in modules.css applies.
  var indHTML = Array.isArray(indications) && indications.length
    ? indications.slice(0, 8).map(function(ind) {
        var name = ind.disease || ind.indication || ind.name || String(ind);
        var phaseRaw = ind.phase || '';
        // Audit hygiene: use _phaseLabel for consistent short labels
        // (Approved / P3 / P2 / P1 / Preclin) instead of the literal
        // "APPROVED"-uppercase / "Ph3"-mixed forms backend ships.
        var phaseLbl = _phaseLabel(phaseRaw);
        var phaseCls = _phaseClass(phaseRaw);
        var phaseHTML = phaseRaw
          ? '<span class="phase-pill ' + phaseCls + '" style="margin-left:6px">' + phaseLbl + '</span>'
          : '';
        return '<div class="diq-ind-row" style="padding:5px 0;border-bottom:1px solid var(--g-border);font-size:12px;color:var(--text);display:flex;align-items:center;justify-content:space-between"><span class="diq-ind-name">' + name + '</span>' + phaseHTML + '</div>';
      }).join('')
      + (indications.length > 8 ? '<div style="font-size:11px;color:var(--text-muted);padding-top:4px">+' + (indications.length - 8) + ' more</div>' : '')
    : '<div style="font-size:12px;color:var(--text-muted)">No indications data.</div>';

  // Safety highlights
  var aeKeys = Object.keys(safetyProfile).slice(0, 6);
  var safetyHTML = aeKeys.length
    ? '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">'
      + aeKeys.map(function(k) {
          return '<div style="font-size:11px"><span style="color:var(--text-muted)">' + k + ':</span> <span style="color:var(--text)">' + safetyProfile[k] + '</span></div>';
        }).join('')
      + '</div>'
    : '<div style="font-size:12px;color:var(--text-muted)">No safety data available.</div>';

  el.innerHTML = ''
    // Header
    + '<div class="diq-header" style="margin-bottom:20px">'
    + '<button class="diq-back-btn" onclick="setCompView(\'landscape\')" style="min-height:44px;display:flex;align-items:center;gap:6px;margin-bottom:12px">'
    + '← Back to Landscape'
    + '</button>'
    + '<div style="font-size:22px;font-weight:700;color:var(--text)">' + drugName + '</div>'
    + '<div style="font-size:13px;color:var(--text-muted);margin-top:4px">by ' + orgXLink(orgName || _CS.drugOrg, 'compete') + '</div>'
    + '</div>'

    // KPI strip (4-up grid via .diq-kpi-strip rule in modules.css:1278)
    + '<div class="diq-kpi-strip" style="margin-bottom:24px">' + kpiCells + '</div>'

    // Mid grid (2-up via .diq-mid-grid rule in modules.css:1339)
    + '<div class="diq-mid-grid" style="margin-bottom:24px">'

    // Mechanism & Targets
    + '<div class="diq-mid-card glass-ground">'
    + '<div class="diq-card-title">Mechanism & Targets</div>'
    + '<div class="diq-field-label" style="margin-top:6px">MOA</div>'
    + '<div class="diq-field-value" style="font-size:12px;color:var(--text);margin-bottom:8px">' + moa + '</div>'
    + '<div class="diq-field-label">Targets</div>'
    + '<div class="diq-field-value" style="font-size:11px;color:var(--text-muted)">' + (Array.isArray(targets) ? targets.join(' · ') : String(targets)) + '</div>'
    + '<div class="diq-field-label" style="margin-top:6px">Modality</div>'
    + '<div class="diq-field-value" style="font-size:11px;color:var(--text)">' + modality + '</div>'
    + '</div>'

    // IP & Exclusivity
    // UR-018: keep score + unit on a single line so users can never see a
    // bare "100" without context. Also reads cleaner ("87%") than the prior
    // big-number-with-tiny-disclaimer-below stack.
    + '<div class="diq-mid-card glass-ground ip-card">'
    + '<div class="diq-card-title">IP & Exclusivity</div>'
    + '<div style="font-size:11px;color:var(--text-muted);margin-bottom:6px">IP Moat Score</div>'
    + ipCoverageBarHTML(ipScore)
    + '<div style="display:flex;align-items:baseline;gap:6px;margin-top:8px">'
    +   '<span style="font-size:20px;font-weight:700;font-family:var(--font-mono);color:var(--teal)">' + ipPctDisplay + '</span>'
    +   '<span style="font-size:11px;color:var(--text-muted)">composite (0–100%)</span>'
    + '</div>'
    + (data.patent_expiry ? '<div style="font-size:11px;color:var(--text-muted);margin-top:8px">Patent expiry: ' + data.patent_expiry + '</div>' : '')
    + '</div>'

    + '</div>'

    // Indications
    + '<div class="diq-mid-card glass-ground" style="margin-bottom:24px">'
    + '<div class="diq-card-title">Indications</div>'
    + indHTML
    + '</div>'

    // Competitors table + evidence panel (two-col)
    + '<div class="diq-bottom-grid" style="display:flex;gap:16px;align-items:flex-start">'
    + '<div style="flex:1;min-width:0">'
    + '<div style="font-size:13px;font-weight:600;color:var(--text);margin-bottom:10px">Competitor Landscape</div>'
    + '<div id="diq-competitors-tbl"></div>'
    + '</div>'
    + '<div id="diq-evidence-panel" class="diq-evidence-panel glass-elevated is-open" style="width:340px;flex-shrink:0;position:sticky;top:72px">'
    + _buildEvidencePanelEmpty()
    + '</div>'
    + '</div>'

    // Drug Intelligence & Safety accordion
    + '<details class="diq-safety-accordion" style="margin-top:24px">'
    + '<summary style="font-size:13px;font-weight:600;color:var(--text);cursor:pointer;padding:10px 0">Drug Intelligence & Safety</summary>'
    + '<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;padding-top:12px">'
    + '<div><div class="diq-card-title">Safety Profile</div>' + safetyHTML + '</div>'
    + '<div><div class="diq-card-title">Additional Context</div>'
    + '<div style="font-size:12px;color:var(--text-muted)">'
    + (data.clinical_notes || data.notes || 'No additional clinical context available.')
    + '</div></div>'
    + '</div>'
    + '</details>';

  // Render competitors table
  renderDiqTable(competitors);

  // Auto-open first competitor — audit bug #15: only fire here when we
  // already have rows (embedded competitors path). When the page is
  // hydrating asynchronously, _hydrateCompetitors will trigger the
  // auto-open after its results land. Without this gate the drawer
  // opened empty and never reopened with real data.
  if (competitors && competitors.length > 0) {
    setTimeout(function() {
      var first = competitors[0];
      if (first) openDiqDrawer(first, 0);
    }, 50);
  }
}

function _buildDrugProfilePicker(orgName) {
  return '<div style="padding:24px 0">'
    + '<button class="diq-back-btn" onclick="setCompView(\'landscape\')" style="min-height:44px;display:flex;align-items:center;gap:6px;margin-bottom:16px">'
    + '← Back to Landscape'
    + '</button>'
    + (orgName ? '<div style="font-size:16px;font-weight:600;margin-bottom:12px;color:var(--text)">Drug Profiles: ' + orgName + '</div>' : '')
    + '<div style="font-size:13px;color:var(--text-muted);margin-bottom:16px">Select a drug to view its profile.</div>'
    + '<div class="diq-search-container" style="margin-bottom:16px">'
    + '<div class="diq-search-row">'
    + '<span class="diq-search-label">Explore by</span>'
    + '<div class="diq-search-mode-tabs">'
    +   '<button class="diq-mode-tab active" data-mode="drug" onclick="compDrugModeSwitch(this)">Drug</button>'
    +   '<button class="diq-mode-tab" data-mode="target" onclick="compDrugModeSwitch(this)">Target</button>'
    + '</div>'
    + '<div class="diq-search-input-wrap">'
    +   '<svg class="diq-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>'
    +   '<input id="diq-drug-search" type="text" class="diq-search-input" placeholder="Search drugs, targets…" autocomplete="off" oninput="window._diqDrugFilter(this.value)"/>'
    + '</div>'
    + '</div>'
    + '</div>'
    + '<div id="diq-drug-list" style="max-height:400px;overflow-y:auto">'
    + '<div class="loader-center"><div class="loader"></div></div>'
    + '</div>'
    + '</div>';
}

function _loadDrugNames() {
  window.DASHIQ.fetchDrugProfileNames({ limit: 20000 }).then(function(res) {
    _CS.drugNames = (res && (res.results || res.names || res)) || [];
    if (!Array.isArray(_CS.drugNames)) _CS.drugNames = [];
    window._diqDrugFilter('');
  }).catch(function() {
    var el = document.getElementById('diq-drug-list');
    if (el) el.innerHTML = _renderErrorCard('Failed to load drug names.');
  });
}

window._diqDrugFilter = function(q) {
  var el = document.getElementById('diq-drug-list');
  if (!el) return;
  var names = _CS.drugNames;
  var filtered = q
    ? names.filter(function(n) { return String(n).toLowerCase().includes(q.toLowerCase()); })
    : names;
  filtered = filtered.slice(0, 100);
  if (!filtered.length) {
    el.innerHTML = '<div style="font-size:13px;color:var(--text-muted);padding:12px 0">No drugs match your search.</div>';
    return;
  }
  el.innerHTML = filtered.map(function(name) {
    var safe = String(name).replace(/'/g,"\\'");
    return '<button class="diq-drug-name-btn" onclick="loadDrugProfileData(\'\',\'' + safe + '\')" style="min-height:44px;text-align:left">'
      + name + '</button>';
  }).join('');
};

// ─── 8. COMPETITORS TABLE ──────────────────────────────────────────────────────

function renderDiqTable(competitors) {
  var el = document.getElementById('diq-competitors-tbl');
  if (!el) return;

  // UR-022: filter out competitor rows that have no usable info — no MoA,
  // no mechanism_of_action, AND no intelligence insights. Such rows would
  // open a drawer that just says "No mechanism data available" / "No
  // intelligence insights available", which is worse than not showing them.
  var filtered = (competitors || []).filter(function(r) {
    if (!r) return false;
    var hasMoa = !!(r.moa || r.mechanism_of_action);
    var hasInsights = Array.isArray(r.intelligence_insights) ? r.intelligence_insights.length > 0
                    : Array.isArray(r.insights) ? r.insights.length > 0
                    : false;
    var hasDrug = !!(r.drug_name || r.name);
    // Keep row if it has a drug name AND at least one of (moa | insights).
    // Drug name alone isn't enough — we'd still render empty drawer.
    return hasDrug && (hasMoa || hasInsights);
  });
  window._diqCompetitorsFiltered = filtered;

  if (!filtered.length) {
    el.innerHTML = '<div class="empty-state">'
      + '<div class="empty-state-icon">—</div>'
      + '<div class="empty-state-title">No competitor data available</div>'
      + '<div class="empty-state-desc">No competitor data available for the current disease context.</div>'
      + '</div>';
    return;
  }

  _CS.diqPage = _CS.diqPage || 0;
  var start = _CS.diqPage * COMP_PAGE_SIZE;
  var pageRows = filtered.slice(start, start + COMP_PAGE_SIZE);

  var tbody = pageRows.map(function(r, i) {
    var org = r.org_name || r.organization || '—';
    var drug = r.drug_name || r.name || '—';
    var phase = r.phase || r.max_phase || '—';
    // Audit bug #1: use _phaseLabel for clean short labels. Was
    // String(phase).replace('PHASE','P').replace('_','') which left
    // "APPROVED" untouched (no "PHASE" substring) — rendered the long
    // shouty label inside a tiny pill.
    var phaseLabel = phase === '—' ? '—' : _phaseLabel(phase);
    var phaseCls = _phaseClass(phase);
    var moa = r.moa || r.mechanism_of_action || '—';
    if (moa.length > 60) moa = moa.slice(0, 57) + '…';
    var modality = r.modality || assetModality(drug);
    // Audit bug #3: backend's _shape_competitor doesn't ship ip_strength /
    // ip_score per-competitor — so r.ip_strength was always 0 and every row
    // rendered "Weak". The contract gives us n_patents per competitor, which
    // is the same input the backend uses for the active drug's IP MOAT
    // score. Reuse the same log1p(50) formula here so the competitor pill
    // differentiates Strong / Moderate / Weak instead of always Weak.
    var ipScore = r.ip_strength;
    if (ipScore == null) ipScore = r.ip_score;
    if (ipScore == null) {
      var npat = parseInt(r.n_patents, 10) || 0;
      ipScore = npat > 0 ? Math.min(1, Math.log1p(npat) / Math.log1p(50)) : 0;
    }
    // V1 thresholds (line 24500-24501): >= 0.5 Strong, >= 0.2 Moderate, > 0 Weak
    var ipLabel = ipScore >= 0.5 ? 'Strong' : ipScore >= 0.2 ? 'Moderate' : 'Weak';
    var score = r.compete_score || r.score || 0;
    var scoreColor = score >= 0.7 ? 'var(--teal)' : score >= 0.5 ? 'var(--amber)' : score >= 0.3 ? 'var(--text-muted)' : 'var(--text-faint)';
    var scoreWeight = score >= 0.7 ? '700' : '400';
    var isSelected = _CS.diqSelectedRow && (_CS.diqSelectedRow.drug_name || _CS.diqSelectedRow.name) === drug;
    var globalIdx = start + i;

    return '<tr class="diq-comp-row clickable' + (isSelected ? ' row-hl selected' : '') + '" onclick="openDiqDrawer(window._diqCompetitors[' + globalIdx + '],' + globalIdx + ')">'
      + '<td>' + orgXLink(org, 'compete') + '</td>'
      + '<td style="font-weight:500;color:var(--text)">' + drug + '</td>'
      + '<td style="text-align:center"><span class="phase-pill ' + phaseCls + '">' + phaseLabel + '</span></td>'
      + '<td style="font-size:11px;color:var(--text-muted);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + (r.moa||r.mechanism_of_action||'').replace(/"/g,'&quot;') + '">' + moa + '</td>'
      + '<td style="font-size:11px;text-align:center"><span class="comp-modality-pill">' + modality + '</span></td>'
      + '<td>' + scoreBarHTML(ipScore) + '<span style="font-size:10px;color:var(--text-muted);margin-left:4px">' + ipLabel + '</span></td>'
      + '<td style="font-family:var(--font-mono);font-size:12px;color:' + scoreColor + ';font-weight:' + scoreWeight + ';text-align:center">' + (score * 100).toFixed(1) + '</td>'
      + '</tr>';
  }).join('');

  var totalPages = Math.ceil(competitors.length / COMP_PAGE_SIZE);
  var paginHTML = '';
  if (totalPages > 1) {
    var prevDisabled = _CS.diqPage === 0;
    var nextDisabled = _CS.diqPage >= totalPages - 1;
    paginHTML = '<div class="iq-pagination" style="margin-top:10px;display:flex;align-items:center;gap:10px;justify-content:center">'
      + '<button onclick="window._diqPage(' + (_CS.diqPage - 1) + ')" ' + (prevDisabled ? 'disabled' : '') + ' style="min-height:44px;min-width:44px">← Prev</button>'
      + '<span class="iq-page-indicator" style="font-family:var(--font-mono);font-size:9px;color:var(--text);font-weight:600">' + (_CS.diqPage + 1) + '/' + totalPages + '</span>'
      + '<button onclick="window._diqPage(' + (_CS.diqPage + 1) + ')" ' + (nextDisabled ? 'disabled' : '') + ' style="min-height:44px;min-width:44px">Next →</button>'
      + '</div>';
  }

  var tableHTML = '<table class="iq-table diq-ct" style="width:100%">'
    + '<caption class="sr-only">Competitor drugs</caption>'
    + '<thead><tr>'
    + '<th style="text-align:left">Organization</th>'
    + '<th style="text-align:left">Drug</th>'
    + '<th style="text-align:center">Phase</th>'
    + '<th style="max-width:180px;text-align:left">MOA</th>'
    + '<th style="text-align:center">Modality</th>'
    + '<th style="text-align:center">IP Strength</th>'
    + '<th style="font-family:var(--font-mono);text-align:center">Score</th>'
    + '</tr></thead>'
    + '<tbody>' + tbody + '</tbody>'
    + '</table>' + paginHTML;

  el.innerHTML = tableHTML;

  // Store reference for row click handlers — use the FILTERED set so
  // onclick handlers (which use globalIdx into _diqCompetitors) hit the
  // right row after UR-022 filter removed empty entries.
  window._diqCompetitors = filtered;
}

window._diqPage = function(p) {
  var comps = window._diqCompetitors || [];
  var totalPages = Math.ceil(comps.length / COMP_PAGE_SIZE);
  _CS.diqPage = Math.max(0, Math.min(p, totalPages - 1));
  renderDiqTable(comps);
};

// ─── 9. EVIDENCE DRAWER ───────────────────────────────────────────────────────

function openDiqDrawer(rowData, idx) {
  if (!rowData) return;
  _CS.diqSelectedRow = rowData;
  _CS.diqEvidenceOpen = true;

  // Highlight selected row
  document.querySelectorAll('.diq-comp-row').forEach(function(tr, i) {
    var hit = (i === ((idx % COMP_PAGE_SIZE)));
    tr.classList.toggle('selected', hit);
    tr.classList.toggle('row-hl', hit);
  });

  var panel = document.getElementById('diq-evidence-panel');
  if (!panel) return;
  panel.classList.add('is-open');

  var org = rowData.org_name || rowData.organization || '—';
  var drug = rowData.drug_name || rowData.name || '—';
  var moa = rowData.moa || rowData.mechanism_of_action || 'No mechanism data available.';
  var insights = rowData.intelligence_insights || rowData.insights || [];
  var sources = rowData.data_sources || rowData.sources || [];
  var ipScore = rowData.ip_strength || rowData.ip_score || 0;
  var score = rowData.compete_score || rowData.score || 0;

  var insightsHTML = Array.isArray(insights) && insights.length
    ? insights.map(function(ins) {
        var text = typeof ins === 'string' ? ins : (ins.text || ins.insight || JSON.stringify(ins));
        return '<div class="ev-insight-card">' + text + '</div>';
      }).join('')
    : '<div style="font-size:12px;color:var(--text-muted)">No intelligence insights available for this drug.</div>';

  var sourcesHTML = Array.isArray(sources) && sources.length
    ? '<div style="font-size:10px;color:var(--text-muted)">'
      + sources.map(function(s) { return typeof s === 'string' ? s : (s.name || s.url || ''); }).filter(Boolean).join(' · ')
      + '</div>'
    : '';

  panel.innerHTML = ''
    + '<div class="ev-header diq-evidence-card-header" style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px">'
    + '<div>'
    + '<div style="font-size:13px;font-weight:600;color:var(--text)">' + drug + '</div>'
    + '<div style="font-size:11px;color:var(--text-muted)">' + org + '</div>'
    + '</div>'
    + '<button class="ev-close-btn" onclick="closeEvDrawer()" style="min-height:44px;min-width:44px;border:none;background:none;cursor:pointer;color:var(--text-muted);border-radius:6px">'
    + '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'
    + '</button>'
    + '</div>'

    // Score strip
    + '<div style="display:flex;gap:12px;margin-bottom:14px">'
    + '<div style="flex:1"><div style="font-size:10px;color:var(--text-muted);margin-bottom:3px">Compete Score</div>' + scoreBarHTML(score) + '</div>'
    + '<div style="flex:1"><div style="font-size:10px;color:var(--text-muted);margin-bottom:3px">IP Strength</div>' + scoreBarHTML(ipScore) + '</div>'
    + '</div>'

    // Mechanism Comparison box
    + '<div class="ev-section" style="margin-bottom:14px">'
    + '<div class="diq-mechanism-comparison-label" style="margin-bottom:6px">MECHANISM COMPARISON</div>'
    + '<div class="ev-moa-box diq-mechanism-comparison" style="font-size:12px;color:var(--text);line-height:1.5">' + moa + '</div>'
    + '</div>'

    // Intelligence Insights
    + '<div class="ev-section" style="margin-bottom:14px">'
    + '<div style="font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);margin-bottom:6px">Intelligence Insights</div>'
    + insightsHTML
    + '</div>'

    // Data Sources
    + (sourcesHTML ? '<div class="ev-sources" style="border-top:1px solid var(--g-border);padding-top:8px">'
      + '<div style="font-size:10px;font-weight:500;color:var(--text-muted);margin-bottom:4px">Data Sources</div>'
      + sourcesHTML + '</div>' : '');

  // Mobile: panel becomes drawer overlay
  if (window.innerWidth <= 768) {
    panel.classList.add('ev-drawer');
    panel.classList.add('open');
  }
}

function closeEvDrawer() {
  _CS.diqEvidenceOpen = false;
  _CS.diqSelectedRow = null;
  var panel = document.getElementById('diq-evidence-panel');
  if (!panel) return;
  panel.classList.remove('open');
  panel.classList.remove('ev-drawer');
  panel.classList.remove('is-open');
  panel.innerHTML = _buildEvidencePanelEmpty();
  document.querySelectorAll('.diq-comp-row').forEach(function(tr) {
    tr.classList.remove('selected');
    tr.classList.remove('row-hl');
  });
}

function _buildEvidencePanelEmpty() {
  return '<div class="ev-empty-state" style="padding:24px;text-align:center;color:var(--text-muted)">'
    + '<div style="font-size:32px;margin-bottom:8px;opacity:0.4">◎</div>'
    + '<div style="font-size:12px">Select a competitor to view evidence and insights</div>'
    + '</div>';
}

// ─── 10. ERROR CARD HELPER ────────────────────────────────────────────────────

function _renderErrorCard(msg, isTimeout) {
  if (typeof ErrorCard !== 'undefined') {
    // Use primitives ErrorCard if available — render to string via div
    var tmp = document.createElement('div');
    var root = ReactDOM.createRoot(tmp);
    root.render(React.createElement(ErrorCard, { message: msg }));
    return tmp.innerHTML;
  }
  return '<div class="error-card" style="padding:24px;border-radius:12px;border:1px solid var(--rose);background:color-mix(in srgb,var(--rose) 10%,transparent)">'
    + '<div style="font-size:14px;font-weight:600;color:var(--rose);margin-bottom:6px">' + (isTimeout ? 'Request Timeout' : 'Error') + '</div>'
    + '<div style="font-size:12px;color:var(--text)">' + msg + '</div>'
    + '</div>';
}

// ─── 11. ROOT COMPONENT ───────────────────────────────────────────────────────

// ═══════════════════════════════════════════════════════════════════════════
// SPACE-ANCHORED COMPETEIQ — Phase 4 (2026-05-03)
// One unified page anchored on a SPACE (TA/Disease/Target/Modality/MOA/Drug).
// Drug-level anchor delegates to the existing renderDrugProfileView and
// related helpers, which become the deepest zoom of the unified page rather
// than a separate route. The old landscape/drug-ind tab toggle is removed.
// ═══════════════════════════════════════════════════════════════════════════

var _ANCHOR_LABELS = {
  TA:       'Therapeutic area',
  DISEASE:  'Disease',
  TARGET:   'Target',
  MODALITY: 'Modality',
  MOA:      'Mechanism',
  DRUG:     'Drug'
};

// Default landing — biggest space, most activity.
var _DEFAULT_ANCHOR = { type: 'TA', value: 'ONCOLOGY', label: 'Oncology' };

// Group-by options per anchor type. Race-track Y-axis adapts to anchor.
// Phase 4.5 (2026-05-03): 'org' grouping REMOVED. Drugs are the primary
// unit of competitive analysis — each chip is a drug. The Y-axis should
// describe WHERE the competition is happening (a space dimension:
// disease/target/modality/TA), not WHO is competing (orgs are an
// attribute of drugs, surfaced via chip eyebrow + the Org Category
// filter + the Highlight My Org overlay). Grouping by org muddles the
// race-track narrative — "Pfizer is racing" doesn't tell you which
// disease space those drugs are racing in.
var _GROUPBY_BY_ANCHOR = {
  TA:       [{k:'disease',label:'Disease'},{k:'target',label:'Target'},{k:'modality',label:'Modality'}],
  DISEASE:  [{k:'target',label:'Target'},{k:'modality',label:'Modality'},{k:'moa',label:'MOA'}],
  TARGET:   [{k:'disease',label:'Disease'},{k:'modality',label:'Modality'},{k:'moa',label:'MOA'}],
  MODALITY: [{k:'ta',label:'TA'},{k:'disease',label:'Disease'},{k:'target',label:'Target'}],
  MOA:      [{k:'disease',label:'Disease'},{k:'target',label:'Target'},{k:'modality',label:'Modality'}]
};

// Quick views — space-centric (not org-centric). Each preset composes
// filter values to surface a particular pattern in the current space.
var _QUICK_VIEWS = [
  { k:'all',         name:'All',             tag:'no filters' },
  { k:'patent-cliff',name:'Patent Cliff',    tag:'approved · expiring 24mo' },
  { k:'hot-zones',   name:'Hot Zones',       tag:'pubs ↑ · momentum ≥ 60%' },
  { k:'new-entrants',name:'New Entrants',    tag:'last 12 mo' },
  { k:'moa-converge',name:'MoA Convergence', tag:'5+ drugs · same target' },
  { k:'modality-shift',name:'Modality Shift',tag:'category migration' }
];

// Compete page-level state — single source of truth.
var _CV = {
  anchor: null,           // { type, value, label }
  view: 'all',            // quick-view preset
  viewMode: 'race',       // 'race' | 'table' | 'heatmap' — Phase 4.6
  groupBy: 'auto',
  highlightOrg: '',       // optional org to glow amber
  search: '',
  advancedOpen: false,
  filters: {
    phase: 'all',         // 'all' | 'P1+' | 'P2+' | 'P3+' | 'approved'
    orgCategory: [],
    modality: [],
    momentum: 0
  },
  rows: [],               // raw rows from the API
  loading: false
};

// Org-category color palette (no longer "you vs them" — categorical).
var _ORG_CAT_COLOR = {
  PHARMA:     { bg: 'rgba(13,148,136,0.10)',   border:'rgba(13,148,136,0.32)',   dot:'#0d9488', label:'Pharma' },
  BIOTECH:    { bg: 'rgba(124,58,237,0.10)',   border:'rgba(124,58,237,0.32)',   dot:'#7c3aed', label:'Biotech' },
  ACADEMIC:   { bg: 'rgba(180,200,220,0.06)',  border:'rgba(180,200,220,0.20)',  dot:'rgba(220,230,240,0.70)', label:'Academic' },
  GOVERNMENT: { bg: 'rgba(180,200,220,0.06)',  border:'rgba(180,200,220,0.20)',  dot:'rgba(220,230,240,0.55)', label:'Gov' },
  CRO:        { bg: 'rgba(180,200,220,0.06)',  border:'rgba(180,200,220,0.20)',  dot:'rgba(220,230,240,0.55)', label:'CRO' },
  DEVICE:     { bg: 'rgba(180,200,220,0.06)',  border:'rgba(180,200,220,0.20)',  dot:'rgba(220,230,240,0.55)', label:'Device' },
  TOOLS:      { bg: 'rgba(180,200,220,0.06)',  border:'rgba(180,200,220,0.20)',  dot:'rgba(220,230,240,0.55)', label:'Tools' },
  OTHER:      { bg: 'rgba(180,200,220,0.06)',  border:'rgba(180,200,220,0.20)',  dot:'rgba(220,230,240,0.55)', label:'Other' }
};

// Phase columns on the race track. 5 buckets matching V2 canonical phases.
// Audit Bug #1 (race-track effect): backend now ships short-form labels
// (`"Ph3"`, `"APPROVED"`, `"Preclinical"`) AND legacy forms (`"PHASE3"`,
// `"Phase 3"`, `"Approved"`). Each matcher normalizes the input to uppercase
// (sans whitespace) so all variants land in the right bucket. Keeping the
// match logic local to _RT_PHASES (vs. delegating to _phaseClass) preserves
// the chip→column mapping cleanly.
function _rtNorm(p) {
  return String(p || '').toUpperCase().replace(/\s+/g, '');
}
var _RT_PHASES = [
  // DATA-1: removed `!p ||` from the preclinical matcher. Empty / NULL
  // max_phase_name now falls through _RT_PHASES (no match) and is routed
  // to the 'unknown' bucket by _phaseBucket() below — preserving the
  // honest-degrade contract domain prescribed (NULL means metadata absent,
  // never a clinical claim of preclinical state).
  { k:'preclinical', label:'Pre-clinical', tier:'discovery',
    match: function(p){
      var u = _rtNorm(p);
      return u === 'EARLYPHASE1' || u === 'EARLY_PHASE1' || u === 'EARLYP1' ||
             u === 'PRECLINICAL' || u === 'PRECLIN' || u === 'PRECLINIC';
    } },
  { k:'p1', label:'Phase 1', tier:'first in human',
    match: function(p){
      var u = _rtNorm(p);
      return u === 'PHASE1' || u === 'PH1' || u === 'P1' ||
             u === 'PHASE1/PHASE2' || u === 'PH1/PH2' || u === 'P1/P2';
    } },
  { k:'p2', label:'Phase 2', tier:'efficacy',
    match: function(p){
      var u = _rtNorm(p);
      return u === 'PHASE2' || u === 'PH2' || u === 'P2' ||
             u === 'PHASE2/PHASE3' || u === 'PH2/PH3' || u === 'P2/P3';
    } },
  { k:'p3', label:'Phase 3', tier:'pivotal',
    match: function(p){
      var u = _rtNorm(p);
      return u === 'PHASE3' || u === 'PH3' || u === 'P3';
    } },
  { k:'approved', label:'Approved', tier:'on market',
    match: function(p){
      var u = _rtNorm(p);
      return u === 'PHASE4' || u === 'PH4' || u === 'P4' ||
             u === 'APPROVED' || u === 'MARKETED';
    } }
];

// ─── Inject race-track CSS once ─────────────────────────────────────────────
function _injectRaceTrackStyles() {
  if (document.getElementById('race-track-styles')) return;
  var s = document.createElement('style');
  s.id = 'race-track-styles';
  s.textContent = [
    /* Anchor breadcrumb */
    '.rt-anchor-bar{display:flex;align-items:center;gap:10px;padding:14px 18px;background:linear-gradient(135deg,rgba(217,119,6,0.06),rgba(7,12,23,0.55));border:1px solid rgba(217,119,6,0.18);border-radius:14px;backdrop-filter:blur(20px);margin-bottom:14px;flex-wrap:wrap}',
    '.rt-anchor-eyebrow{font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;color:var(--text-faint);margin-right:4px}',
    '.rt-anchor-pill{display:inline-flex;align-items:center;gap:7px;padding:7px 12px;background:rgba(255,255,255,0.04);border:1px solid var(--g-border);border-radius:20px;cursor:pointer;transition:all 0.18s ease}',
    '.rt-anchor-pill:hover{border-color:var(--g-border-strong);background:rgba(255,255,255,0.07)}',
    '.rt-anchor-pill.active{background:linear-gradient(135deg,rgba(217,119,6,0.16),rgba(217,119,6,0.06));border-color:rgba(217,119,6,0.42)}',
    '.rt-anchor-type{font-family:var(--font-mono);font-size:8px;font-weight:800;letter-spacing:0.16em;text-transform:uppercase;color:var(--text-faint)}',
    '.rt-anchor-pill.active .rt-anchor-type{color:rgba(217,119,6,0.85)}',
    '.rt-anchor-name{font-family:var(--font-display);font-size:13.5px;font-weight:700;color:var(--text);letter-spacing:-0.01em}',
    '.rt-anchor-pill.active .rt-anchor-name{color:var(--amber-warm,#f59e0b)}',
    '.rt-anchor-chev{font-family:var(--font-mono);font-size:11px;color:var(--text-faint)}',
    '.rt-anchor-search{flex:1;min-width:180px;display:flex;align-items:center;gap:8px;background:rgba(255,255,255,0.03);border:1px solid var(--g-border);border-radius:8px;padding:7px 12px}',
    '.rt-anchor-search:focus-within{border-color:var(--g-border-strong);background:rgba(255,255,255,0.05)}',
    '.rt-anchor-search input{flex:1;background:transparent;border:0;outline:0;color:var(--text);font-family:var(--font-display);font-size:12.5px;letter-spacing:-0.005em}',
    '.rt-anchor-search input::placeholder{color:var(--text-faint)}',
    /* Headline strip */
    '.rt-headline{padding:14px 18px;background:rgba(7,12,23,0.45);border:1px solid var(--g-border);border-radius:12px;backdrop-filter:blur(20px);margin-bottom:14px}',
    '.rt-headline-row{display:grid;grid-template-columns:1fr auto;gap:24px;align-items:center}',
    '.rt-h-line{font-family:var(--font-display);font-size:17px;font-weight:600;color:var(--text);letter-spacing:-0.012em;margin:0;line-height:1.32}',
    '.rt-h-line em{font-style:normal;color:var(--rose,#f43f5e)}',
    '.rt-h-line u{text-decoration-color:rgba(217,119,6,0.55);color:var(--amber-warm,#f59e0b);text-decoration-thickness:2px;text-underline-offset:4px}',
    '.rt-h-stats{display:flex;gap:18px}',
    '.rt-h-stat{display:flex;flex-direction:column;gap:1px;align-items:flex-end}',
    '.rt-h-stat-num{font-family:var(--font-mono);font-size:20px;font-weight:800;letter-spacing:-0.025em;color:var(--text);line-height:1}',
    '.rt-h-stat-num.warn{color:var(--rose,#f43f5e)}',
    '.rt-h-stat-num.win{color:var(--teal-bright,#14b8a6)}',
    '.rt-h-stat-label{font-family:var(--font-mono);font-size:7.5px;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--text-faint);margin-top:5px;text-align:right;line-height:1.2}',
    /* Filter stack */
    '.rt-filter-stack{margin-bottom:14px}',
    '.rt-qv-row{display:flex;gap:8px;align-items:center;padding:9px 13px;background:rgba(7,12,23,0.55);border:1px solid var(--g-border-strong);border-bottom:0;border-radius:10px 10px 0 0;backdrop-filter:blur(20px);overflow-x:auto}',
    '.rt-qv-label{font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;color:var(--text-faint);white-space:nowrap}',
    '.rt-qv-btn{display:inline-flex;flex-direction:column;align-items:flex-start;gap:1px;padding:6px 11px;background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:7px;cursor:pointer;transition:all 0.18s ease;white-space:nowrap;flex-shrink:0}',
    '.rt-qv-btn:hover{background:rgba(255,255,255,0.05);border-color:var(--g-border-strong)}',
    '.rt-qv-btn.active{background:linear-gradient(135deg,rgba(217,119,6,0.18),rgba(217,119,6,0.06));border-color:rgba(217,119,6,0.42)}',
    '.rt-qv-name{font-family:var(--font-display);font-size:11px;font-weight:600;color:var(--text);letter-spacing:-0.005em;line-height:1.1}',
    '.rt-qv-btn.active .rt-qv-name{color:var(--amber-warm,#f59e0b)}',
    '.rt-qv-tag{font-family:var(--font-mono);font-size:7px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint);line-height:1.2}',
    '.rt-qv-btn.active .rt-qv-tag{color:rgba(217,119,6,0.85)}',
    '.rt-gby-row{display:grid;grid-template-columns:auto auto 1fr auto;gap:12px;padding:9px 13px;background:rgba(7,12,23,0.45);border:1px solid var(--g-border-strong);border-top:0;border-bottom:0;align-items:center;flex-wrap:wrap}',
    '.rt-gby-row.last{border-bottom:1px solid var(--g-border-strong);border-radius:0 0 10px 10px}',
    '.rt-gby-segctrl{display:inline-flex;background:rgba(255,255,255,0.03);border:1px solid var(--g-border);border-radius:6px;padding:2px;gap:1px}',
    '.rt-gby-seg{padding:5px 9px;font-family:var(--font-mono);font-size:9px;font-weight:700;letter-spacing:0.08em;text-transform:uppercase;color:var(--text-muted);background:transparent;border:0;border-radius:4px;cursor:pointer;transition:all 0.18s ease}',
    '.rt-gby-seg:hover{color:var(--text)}',
    '.rt-gby-seg.active{background:rgba(255,255,255,0.10);color:var(--text);font-weight:800}',
    '.rt-search-wrap{display:flex;align-items:center;gap:6px;background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:7px;padding:5px 11px}',
    '.rt-search-wrap input{flex:1;min-width:160px;background:transparent;border:0;outline:0;color:var(--text);font-family:var(--font-display);font-size:12px;letter-spacing:-0.005em}',
    '.rt-search-wrap input::placeholder{color:var(--text-faint)}',
    '.rt-adv-toggle{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;font-family:var(--font-mono);font-size:9px;font-weight:800;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-muted);background:rgba(255,255,255,0.03);border:1px solid var(--g-border);border-radius:6px;cursor:pointer}',
    '.rt-adv-toggle:hover{color:var(--text)}',
    '.rt-adv-toggle.has-active{color:var(--amber-warm,#f59e0b);border-color:rgba(217,119,6,0.32);background:rgba(217,119,6,0.06)}',
    '.rt-adv-panel{padding:13px 15px;background:rgba(7,12,23,0.40);border:1px solid var(--g-border-strong);border-top:0;border-radius:0 0 10px 10px;display:grid;grid-template-columns:repeat(4,1fr);gap:13px 16px}',
    '.rt-adv-section{display:flex;flex-direction:column;gap:5px;min-width:0}',
    '.rt-adv-label{font-family:var(--font-mono);font-size:8px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;color:var(--text-faint)}',
    '.rt-adv-chips{display:flex;flex-wrap:wrap;gap:4px}',
    '.rt-adv-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:11px;font-family:var(--font-mono);font-size:9px;font-weight:700;letter-spacing:0.04em;color:var(--text-muted);cursor:pointer;transition:all 0.18s ease}',
    '.rt-adv-chip:hover{border-color:var(--g-border-strong);color:var(--text)}',
    '.rt-adv-chip.on{background:rgba(20,184,166,0.12);border-color:rgba(20,184,166,0.36);color:var(--teal-bright,#14b8a6);font-weight:800}',
    /* Track */
    '.race-track-wrap{padding:18px 22px 14px;background:rgba(7,12,23,0.45);border:1px solid var(--g-border);border-radius:14px;backdrop-filter:blur(20px) saturate(140%);-webkit-backdrop-filter:blur(20px) saturate(140%);overflow-x:auto;margin-bottom:14px;box-shadow:inset 0 1px 0 rgba(255,255,255,0.04)}',
    '.rt-track-bar{display:flex;align-items:baseline;justify-content:space-between;gap:14px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px dashed rgba(255,255,255,0.08)}',
    '.rt-track-title{font-family:var(--font-display);font-size:14px;font-weight:600;color:var(--text);letter-spacing:-0.005em}',
    '.rt-track-sub{font-family:var(--font-mono);font-size:9px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint);margin-top:3px}',
    '.rt-legend{display:flex;align-items:center;gap:13px;font-family:var(--font-mono);font-size:8.5px;font-weight:700;letter-spacing:0.06em;color:var(--text-muted);text-transform:uppercase}',
    '.rt-legend .lg{display:inline-flex;align-items:center;gap:5px}',
    '.rt-legend .ldot{width:7px;height:7px;border-radius:50%}',
    '.rt-track-head{display:grid;grid-template-columns:170px repeat(5,1fr);gap:8px;align-items:end;padding:0 0 8px;border-bottom:1px dashed rgba(255,255,255,0.08);margin-bottom:6px}',
    '.rt-th-label{font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;color:var(--text-faint);text-align:center;padding:0 6px}',
    '.rt-th-tier{font-family:var(--font-mono);font-size:7.5px;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:rgba(230,237,247,0.30);text-align:center;margin-top:3px;font-style:italic}',
    '.rt-row{display:grid;grid-template-columns:170px repeat(5,1fr);gap:8px;padding:12px 0 10px;border-bottom:1px dashed rgba(255,255,255,0.05);position:relative;min-height:88px;align-items:stretch}',
    '.rt-row:last-child{border-bottom:none}',
    '.rt-grp{display:flex;flex-direction:column;justify-content:center;padding:0 10px;gap:3px;cursor:pointer;border-radius:6px;transition:background 0.18s ease}',
    '.rt-grp:hover{background:rgba(255,255,255,0.03)}',
    '.rt-grp-name{font-family:var(--font-display);font-size:13.5px;font-weight:600;color:var(--text);letter-spacing:-0.005em;line-height:1.2}',
    '.rt-grp-meta{font-family:var(--font-mono);font-size:8.5px;font-weight:700;letter-spacing:0.06em;color:var(--text-faint);text-transform:uppercase}',
    '.rt-cell{position:relative;display:flex;flex-direction:column;justify-content:center;gap:4px;padding:4px 6px;overflow:hidden}',
    '.rt-cell::before{content:"";position:absolute;left:0;right:0;top:50%;height:1px;background:rgba(255,255,255,0.04);transform:translateY(-50%);z-index:0}',
    '.rt-stack{display:flex;flex-direction:column;gap:4px;align-items:flex-start;position:relative;z-index:1;width:100%}',
    /* Drug chip — Phase 4.1: tighter vertical padding so 2 chips fit cleanly in the cell row */
    '.rt-chip{position:relative;display:inline-flex;flex-direction:column;align-items:flex-start;padding:3px 8px 4px 7px;background:rgba(255,255,255,0.04);border:1px solid var(--g-border);border-radius:7px;font-family:var(--font-mono);letter-spacing:-0.005em;color:var(--text);max-width:100%;transition:all 0.18s ease;cursor:pointer;white-space:nowrap;overflow:hidden}',
    '.rt-chip:hover{transform:translateY(-1px);box-shadow:0 4px 14px rgba(0,0,0,0.4);border-color:var(--g-border-strong)}',
    '.rt-chip.highlight{background:linear-gradient(135deg,rgba(217,119,6,0.16),rgba(217,119,6,0.06));border-color:rgba(217,119,6,0.40);box-shadow:0 2px 10px rgba(217,119,6,0.18)}',
    '.rt-chip-row1{display:flex;align-items:center;gap:5px}',
    '.rt-chip-dot{width:5px;height:5px;border-radius:50%;background:var(--text-faint);flex-shrink:0}',
    '.rt-chip-org{font-size:7px;font-weight:800;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint)}',
    '.rt-chip.highlight .rt-chip-org{color:var(--amber-warm,#f59e0b)}',
    '.rt-chip-name{font-weight:700;font-size:9.5px}',
    '.rt-chip-evidence{display:inline-flex;gap:3px;margin-top:2px}',
    '.rt-ev{display:inline-flex;align-items:center;gap:2px;padding:0 3px;border-radius:3px;font-size:7px;font-weight:800;letter-spacing:0.04em;line-height:1.35}',
    '.rt-ev.pat{background:rgba(59,130,246,0.14);color:#3b82f6;border:1px solid rgba(59,130,246,0.20)}',
    '.rt-ev.tri{background:rgba(20,184,166,0.14);color:var(--teal-bright,#14b8a6);border:1px solid rgba(20,184,166,0.20)}',
    '.rt-ev.pub{background:rgba(124,58,237,0.14);color:#a78bfa;border:1px solid rgba(124,58,237,0.20)}',
    '.rt-cell-empty{font-family:var(--font-mono);font-size:8px;color:rgba(230,237,247,0.18);text-align:center;letter-spacing:0.10em}',
    '.rt-overflow{font-family:var(--font-mono);font-size:8px;font-weight:700;letter-spacing:0.06em;color:var(--text-faint);padding:3px 6px;background:rgba(255,255,255,0.025);border:1px dashed var(--g-border);border-radius:5px;cursor:pointer}',
    '.rt-overflow:hover{color:var(--text);border-color:var(--g-border-strong)}',
    /* Empty state */
    '.rt-empty{padding:56px 24px;text-align:center;font-family:var(--font-display);font-size:14px;color:var(--text-muted);letter-spacing:-0.005em}',
    '.rt-empty b{color:var(--text);font-weight:600}',
    /* ── Phase 4.2 · command bar · realigned to PartnerIQ visual register ──
       Was heavy on amber — every active state was amber, page felt
       isolated from PartnerIQ's neutral-glass-with-teal-accent palette.
       Swapped active accent from amber → teal-bright (matches PartnerIQ's
       hover/active treatment), kept amber only for the anchor pill (which
       IS the page's primary identity affordance per the module color
       system). Glass background tightened to match PartnerIQ surfaces. */
    '.rt-cmdbar{display:flex;align-items:center;gap:10px;padding:10px 14px;background:rgba(7,12,23,0.45);border:1px solid var(--g-border);border-radius:12px;backdrop-filter:blur(20px) saturate(140%);-webkit-backdrop-filter:blur(20px) saturate(140%);margin-bottom:14px;flex-wrap:wrap;box-shadow:inset 0 1px 0 rgba(255,255,255,0.04)}',
    '.rt-cb-div{width:1px;height:22px;background:rgba(255,255,255,0.06);flex-shrink:0}',
    '.rt-cb-anchor{display:inline-flex;flex-direction:column;align-items:flex-start;gap:1px;padding:6px 14px 7px 12px;background:rgba(255,255,255,0.04);border:1px solid var(--g-border);border-radius:9px;cursor:pointer;transition:all 0.18s ease;font-family:inherit}',
    '.rt-cb-anchor:hover{background:rgba(255,255,255,0.07);border-color:var(--g-border-strong)}',
    '.rt-cb-eyebrow{font-family:var(--font-mono);font-size:7px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;color:var(--text-faint);line-height:1.1}',
    '.rt-cb-anchor-name{font-family:var(--font-display);font-size:13px;font-weight:700;color:var(--text);letter-spacing:-0.005em;line-height:1.2;display:flex;align-items:center;gap:5px;margin-top:1px}',
    '.rt-cb-chev{font-family:var(--font-mono);font-size:9px;color:var(--text-faint);font-weight:800;line-height:1}',
    '.rt-cb-btn{display:inline-flex;align-items:center;gap:7px;padding:7px 11px;background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:8px;cursor:pointer;font-family:var(--font-display);font-size:11.5px;font-weight:600;color:var(--text-muted);letter-spacing:-0.005em;transition:all 0.18s ease}',
    '.rt-cb-btn:hover{color:var(--text);border-color:var(--g-border-strong);background:rgba(255,255,255,0.05)}',
    '.rt-cb-btn.has-active{color:var(--text);border-color:var(--g-border-strong)}',
    '.rt-cb-btn.open{border-color:rgba(20,184,166,0.42);color:var(--teal-bright,#14b8a6);background:rgba(20,184,166,0.08)}',
    '.rt-cb-btn-label{line-height:1}',
    '.rt-cb-badge{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;font-family:var(--font-mono);font-size:8.5px;font-weight:800;background:var(--teal-bright,#14b8a6);color:#070b14;border-radius:8px;letter-spacing:0}',
    '.rt-cb-gby{display:flex;align-items:center;gap:7px}',
    '.rt-cb-gby-label{font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;color:var(--text-faint);white-space:nowrap}',
    '.rt-cb-search{display:flex;align-items:center;gap:6px;background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:8px;padding:6px 11px;flex:1;min-width:200px;max-width:360px;margin-left:auto}',
    '.rt-cb-search:focus-within{border-color:var(--g-border-strong);background:rgba(255,255,255,0.05)}',
    '.rt-cb-search input{flex:1;background:transparent;border:0;outline:0;color:var(--text);font-family:var(--font-display);font-size:12px;letter-spacing:-0.005em}',
    '.rt-cb-search input::placeholder{color:var(--text-faint)}',
    /* Filter popover — neutral glass surface like PartnerIQ panels, teal accent on active */
    '.rt-filter-pop{padding:18px 20px 14px;background:rgba(7,12,23,0.55);border:1px solid var(--g-border);border-radius:12px;backdrop-filter:blur(24px) saturate(140%);-webkit-backdrop-filter:blur(24px) saturate(140%);margin-top:-8px;margin-bottom:14px;box-shadow:0 12px 36px rgba(0,0,0,0.35),inset 0 1px 0 rgba(255,255,255,0.04);position:relative;z-index:5}',
    '.rt-fp-section{display:flex;flex-direction:column;gap:6px;margin-bottom:14px}',
    '.rt-fp-label{font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.18em;text-transform:uppercase;color:var(--text-faint)}',
    '.rt-fp-presets{display:flex;gap:6px;flex-wrap:wrap}',
    '.rt-fp-preset{display:inline-flex;flex-direction:column;align-items:flex-start;gap:1px;padding:7px 12px 8px;background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:7px;cursor:pointer;transition:all 0.18s ease;text-align:left;font-family:inherit}',
    '.rt-fp-preset:hover{background:rgba(255,255,255,0.05);border-color:var(--g-border-strong)}',
    '.rt-fp-preset.active{background:rgba(20,184,166,0.10);border-color:rgba(20,184,166,0.36);box-shadow:inset 0 1px 0 rgba(255,255,255,0.05)}',
    '.rt-fp-preset-name{font-family:var(--font-display);font-size:12px;font-weight:600;color:var(--text);letter-spacing:-0.005em;line-height:1.1}',
    '.rt-fp-preset.active .rt-fp-preset-name{color:var(--teal-bright,#14b8a6)}',
    '.rt-fp-preset-tag{font-family:var(--font-mono);font-size:7.5px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint);line-height:1.2}',
    '.rt-fp-preset.active .rt-fp-preset-tag{color:rgba(20,184,166,0.85)}',
    '.rt-fp-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px 22px;padding-top:6px;margin-top:6px;border-top:1px dashed rgba(255,255,255,0.08)}',
    '.rt-fp-input{background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:6px;padding:6px 10px;color:var(--text);font-family:var(--font-display);font-size:11.5px;letter-spacing:-0.005em;outline:0}',
    '.rt-fp-input:focus{border-color:var(--g-border-strong);background:rgba(255,255,255,0.05)}',
    '.rt-fp-foot{display:flex;align-items:center;gap:10px;margin-top:8px;padding-top:11px;border-top:1px dashed rgba(255,255,255,0.08)}',
    '.rt-fp-foot-info{font-family:var(--font-mono);font-size:9px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint);flex:1}',
    '.rt-fp-reset{padding:6px 11px;font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.10em;text-transform:uppercase;color:var(--rose,#f43f5e);background:rgba(244,63,94,0.06);border:1px solid rgba(244,63,94,0.22);border-radius:6px;cursor:pointer}',
    '.rt-fp-reset:hover{background:rgba(244,63,94,0.10)}',
    '.rt-fp-close{padding:6px 14px;font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.10em;text-transform:uppercase;color:var(--text);background:rgba(255,255,255,0.06);border:1px solid var(--g-border-strong);border-radius:6px;cursor:pointer}',
    '.rt-fp-close:hover{background:rgba(255,255,255,0.10)}',
    /* Anchor picker — 2-step flow (cards → list) */
    '.rt-pp-head{display:flex;align-items:center;gap:12px;padding:14px 18px;border-bottom:1px solid var(--g-border)}',
    '.rt-pp-title{font-family:var(--font-display);font-size:15px;font-weight:600;color:var(--text);letter-spacing:-0.005em;flex:1}',
    '.rt-pp-title-row{display:flex;align-items:baseline;gap:8px;flex:1}',
    '.rt-pp-count{font-family:var(--font-mono);font-size:9px;font-weight:800;letter-spacing:0.10em;text-transform:uppercase;padding:2px 6px;background:rgba(20,184,166,0.10);color:var(--teal-bright,#14b8a6);border:1px solid rgba(20,184,166,0.30);border-radius:4px}',
    '.rt-pp-back{padding:5px 10px;font-family:var(--font-mono);font-size:9px;font-weight:800;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-muted);background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:6px;cursor:pointer;transition:all 0.18s ease}',
    '.rt-pp-back:hover{color:var(--text);border-color:var(--g-border-strong);background:rgba(255,255,255,0.05)}',
    '.rt-pp-x{background:none;border:0;color:var(--text-faint);font-size:22px;cursor:pointer;padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:all 0.18s ease;line-height:1;font-family:inherit}',
    '.rt-pp-x:hover{color:var(--text);background:rgba(255,255,255,0.06)}',
    '.rt-pp-search{display:flex;align-items:center;gap:8px;margin:14px 18px 0;padding:8px 12px;background:rgba(255,255,255,0.03);border:1px solid var(--g-border);border-radius:8px}',
    '.rt-pp-search:focus-within{border-color:var(--g-border-strong);background:rgba(255,255,255,0.05)}',
    '.rt-pp-search input{flex:1;background:transparent;border:0;outline:0;color:var(--text);font-family:var(--font-display);font-size:13px;letter-spacing:-0.005em}',
    '.rt-pp-search input::placeholder{color:var(--text-faint)}',
    '.rt-pp-body{flex:1;overflow-y:auto;padding:14px 18px 16px;min-height:0}',
    '.rt-pp-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}',
    '.rt-pp-card{display:flex;flex-direction:column;gap:5px;padding:14px 16px;text-align:left;background:rgba(255,255,255,0.025);border:1px solid var(--g-border);border-radius:10px;cursor:pointer;font-family:inherit;transition:all 0.18s ease}',
    '.rt-pp-card:hover{background:rgba(20,184,166,0.06);border-color:rgba(20,184,166,0.36);transform:translateY(-1px);box-shadow:0 4px 14px rgba(0,0,0,0.3)}',
    '.rt-pp-card-name{font-family:var(--font-display);font-size:14px;font-weight:600;color:var(--text);letter-spacing:-0.005em}',
    '.rt-pp-card-sub{font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.14em;text-transform:uppercase;color:var(--teal-bright,#14b8a6)}',
    '.rt-pp-list{display:flex;flex-direction:column;gap:3px}',
    '.rt-pp-row{display:flex;align-items:baseline;gap:10px;padding:9px 12px;background:rgba(255,255,255,0.02);border:1px solid transparent;border-radius:7px;cursor:pointer;transition:all 0.14s ease;text-align:left;font-family:inherit;width:100%}',
    '.rt-pp-row:hover{background:rgba(20,184,166,0.08);border-color:rgba(20,184,166,0.30)}',
    '.rt-pp-row-name{font-family:var(--font-display);font-size:13px;font-weight:600;color:var(--text);letter-spacing:-0.005em;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
    '.rt-pp-row-sub{font-family:var(--font-mono);font-size:8.5px;font-weight:700;letter-spacing:0.06em;text-transform:uppercase;color:var(--text-faint);flex-shrink:0;max-width:50%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
    '.rt-pp-empty{padding:36px 12px;text-align:center;font-family:var(--font-display);font-size:13px;color:var(--text-muted);letter-spacing:-0.005em}',
    '.rt-pp-empty b{color:var(--text);font-weight:600}',
    '.rt-pp-more{padding:11px 12px;text-align:center;font-family:var(--font-mono);font-size:8.5px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint)}',
    '.rt-pp-foot{padding:10px 18px;border-top:1px solid var(--g-border);font-family:var(--font-mono);font-size:9px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint);text-align:center}',
    /* ── Drug detail side panel (Phase 4.4) ─────────────────────────────
       Slide-in from the right. Race track stays visible behind a soft
       scrim. Width 460px on desktop, full-width on mobile. */
    '.rt-drug-scrim{position:fixed;inset:0;background:rgba(7,12,23,0.55);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);z-index:9000;opacity:0;transition:opacity 0.22s ease;pointer-events:none}',
    '.rt-drug-scrim.visible{opacity:1;pointer-events:auto}',
    '.rt-drug-panel{position:fixed;top:0;right:0;bottom:0;width:min(460px,92vw);background:rgba(7,12,23,0.96);border-left:1px solid var(--g-border-strong);box-shadow:-24px 0 60px rgba(0,0,0,0.55);backdrop-filter:blur(28px) saturate(160%);-webkit-backdrop-filter:blur(28px) saturate(160%);z-index:9100;display:flex;flex-direction:column;transform:translateX(100%);transition:transform 0.28s cubic-bezier(0.32,0.72,0,1);overflow:hidden}',
    '.rt-drug-panel.open{transform:translateX(0)}',
    '.rt-dp-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 18px;border-bottom:1px solid var(--g-border);flex-shrink:0;background:rgba(255,255,255,0.02)}',
    '.rt-dp-eyebrow{font-family:var(--font-mono);font-size:9px;font-weight:800;letter-spacing:0.20em;text-transform:uppercase;color:var(--teal-bright,#14b8a6)}',
    '.rt-dp-close{background:none;border:0;color:var(--text-faint);font-size:24px;cursor:pointer;padding:0;width:44px;height:44px;min-width:44px;min-height:44px;display:flex;align-items:center;justify-content:center;border-radius:6px;transition:all 0.18s ease;line-height:1;font-family:inherit}',
    '.rt-dp-close:hover{color:var(--text);background:rgba(255,255,255,0.08)}',
    '.rt-dp-body{flex:1;overflow-y:auto;padding:18px 22px 24px;min-height:0}',
    '.rt-dp-body .diq-back-btn{display:none}',
    '.rt-dp-body .drug-intel{padding:0}',
    '@media (max-width:768px){.rt-drug-panel{width:100%}}',
    /* ── Phase 4.6 · stat line ────────────────────────────────────────── */
    '.rt-statline{display:flex;align-items:center;gap:10px;padding:10px 16px;margin-bottom:14px;font-family:var(--font-mono);font-size:9.5px;font-weight:700;letter-spacing:0.04em;color:var(--text-muted);flex-wrap:wrap;background:rgba(7,12,23,0.30);border:1px solid var(--g-border);border-radius:9px}',
    '.rt-statline-cell{display:inline-flex;align-items:baseline;gap:6px}',
    '.rt-statline-num{font-family:var(--font-mono);font-size:14.5px;font-weight:800;letter-spacing:-0.02em;color:var(--text);line-height:1}',
    '.rt-statline-num.warn{color:var(--rose,#f43f5e)}',
    '.rt-statline-num.win{color:var(--teal-bright,#14b8a6)}',
    '.rt-statline-label{font-family:var(--font-mono);font-size:8.5px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint)}',
    '.rt-statline-sep{color:rgba(255,255,255,0.12);margin:0 2px}',
    /* ── Table view ───────────────────────────────────────────────────── */
    '.rt-tbl-wrap{overflow-x:auto;margin-top:6px;border:1px solid var(--g-border);border-radius:9px;background:rgba(7,12,23,0.30)}',
    '.rt-tbl{width:100%;border-collapse:collapse;font-family:var(--font-display);font-size:12.5px;letter-spacing:-0.005em}',
    '.rt-tbl thead th{font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.16em;text-transform:uppercase;color:var(--text-faint);padding:11px 14px;text-align:left;border-bottom:1px solid var(--g-border-strong);background:rgba(255,255,255,0.025);white-space:nowrap}',
    '.rt-tbl thead th.rt-tbl-num{text-align:right}',
    '.rt-tbl-row{border-bottom:1px dashed rgba(255,255,255,0.06);cursor:pointer;transition:background 0.18s ease}',
    '.rt-tbl-row:hover{background:rgba(20,184,166,0.04)}',
    '.rt-tbl-row.highlight{background:linear-gradient(90deg,rgba(217,119,6,0.07),transparent 75%);border-left:2px solid var(--amber-warm,#f59e0b)}',
    '.rt-tbl td{padding:9px 14px;color:var(--text);vertical-align:middle}',
    '.rt-tbl-drug{font-weight:600;font-size:13px;letter-spacing:-0.005em}',
    '.rt-tbl-org{font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-muted);letter-spacing:0.02em}',
    '.rt-tbl-target,.rt-tbl-modality{font-family:var(--font-mono);font-size:10.5px;color:var(--text-muted);letter-spacing:0.02em}',
    '.rt-tbl-disease{color:var(--text-muted);font-size:12px}',
    '.rt-tbl-num{text-align:right;font-family:var(--font-mono);font-size:11px;color:var(--text-muted);font-weight:700}',
    '.rt-tbl-overflow{padding:11px 16px;font-family:var(--font-mono);font-size:9px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-faint);text-align:center;border-top:1px dashed var(--g-border);background:rgba(7,12,23,0.30);border-radius:0 0 9px 9px;margin-top:-1px}',
    /* ── Heatmap view ─────────────────────────────────────────────────── */
    '.rt-hm-legend{display:flex;align-items:center;gap:12px;font-family:var(--font-mono);font-size:8.5px;font-weight:700;letter-spacing:0.06em;color:var(--text-muted);text-transform:uppercase;flex-wrap:wrap}',
    '.rt-hm-legend .lg{display:inline-flex;align-items:center;gap:5px}',
    '.rt-hm-swatch{display:inline-block;width:12px;height:12px;border-radius:3px;border:1px solid rgba(255,255,255,0.08)}',
    '.rt-hm-swatch.zero{background:rgba(255,255,255,0.025)}',
    '.rt-hm-swatch.emg{background:rgba(20,184,166,0.10);border-color:rgba(20,184,166,0.20)}',
    '.rt-hm-swatch.act{background:rgba(245,158,11,0.20);border-color:rgba(245,158,11,0.32)}',
    '.rt-hm-swatch.hot{background:rgba(244,63,94,0.30);border-color:rgba(244,63,94,0.42)}',
    '.rt-hm-wrap{overflow-x:auto;margin-top:8px;border:1px solid var(--g-border);border-radius:9px}',
    '.rt-hm{width:100%;border-collapse:separate;border-spacing:3px;padding:6px;font-family:var(--font-mono);font-size:11px;letter-spacing:-0.005em}',
    '.rt-hm thead th{font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.14em;text-transform:uppercase;color:var(--text-faint);padding:9px 6px;text-align:center}',
    '.rt-hm-rowlabel{font-family:var(--font-display);font-size:12.5px;font-weight:600;color:var(--text);letter-spacing:-0.005em;padding:10px 14px;text-align:left;cursor:pointer;background:rgba(255,255,255,0.025);border-radius:6px;border:1px solid var(--g-border);transition:all 0.18s ease;white-space:nowrap;min-width:160px;max-width:240px;overflow:hidden;text-overflow:ellipsis}',
    '.rt-hm-rowlabel:hover{background:rgba(20,184,166,0.06);border-color:rgba(20,184,166,0.32);color:var(--teal-bright,#14b8a6)}',
    '.rt-hm-cell{padding:0;text-align:center;cursor:default;border-radius:6px;transition:all 0.18s ease;min-width:60px;height:42px;vertical-align:middle}',
    '.rt-hm-cell span{display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-size:14px;font-weight:800;letter-spacing:-0.02em;color:var(--text);height:100%;width:100%}',
    '.rt-hm-zero{background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04)}',
    '.rt-hm-zero span{color:rgba(255,255,255,0.18);font-weight:400;font-size:13px}',
    '.rt-hm-emg{background:rgba(20,184,166,0.10);border:1px solid rgba(20,184,166,0.20)}',
    '.rt-hm-emg span{color:#14b8a6}',
    '.rt-hm-act{background:rgba(245,158,11,0.20);border:1px solid rgba(245,158,11,0.32)}',
    '.rt-hm-act span{color:#f59e0b}',
    '.rt-hm-hot{background:rgba(244,63,94,0.30);border:1px solid rgba(244,63,94,0.42)}',
    '.rt-hm-hot span{color:#ffffff;text-shadow:0 1px 6px rgba(244,63,94,0.45)}',
    '.rt-hm-total{padding:10px 14px;text-align:center;background:rgba(255,255,255,0.04);border:1px solid var(--g-border-strong);border-radius:6px}',
    '.rt-hm-total span{font-family:var(--font-mono);font-size:13px;font-weight:800;letter-spacing:-0.02em;color:var(--text);line-height:1}',
    /* ── Drug panel tab strip (Phase 4.6) ──────────────────────────────
       Lightweight tabs that classify the existing drug profile sections
       into R&D / Clinical / Business. Implemented as a CSS show/hide
       controlled by a data attribute on the drug profile body. */
    '.rt-dp-tabs{display:flex;gap:4px;padding:0 4px;margin-bottom:14px;border-bottom:1px solid var(--g-border)}',
    '.rt-dp-tab{padding:9px 13px;font-family:var(--font-mono);font-size:9.5px;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:var(--text-muted);background:transparent;border:0;border-bottom:2px solid transparent;cursor:pointer;transition:all 0.18s ease;margin-bottom:-1px}',
    '.rt-dp-tab:hover{color:var(--text)}',
    '.rt-dp-tab.active{color:var(--teal-bright,#14b8a6);border-bottom-color:var(--teal-bright,#14b8a6);font-weight:800}',
    /* When the panel body is in tab mode, hide sections that don\'t belong to the active tab. */
    '.rt-dp-body[data-tab="rd"] .diq-section[data-tab-cat="clinical"],',
    '.rt-dp-body[data-tab="rd"] .diq-section[data-tab-cat="biz"]{display:none}',
    '.rt-dp-body[data-tab="clinical"] .diq-section[data-tab-cat="rd"],',
    '.rt-dp-body[data-tab="clinical"] .diq-section[data-tab-cat="biz"]{display:none}',
    '.rt-dp-body[data-tab="biz"] .diq-section[data-tab-cat="rd"],',
    '.rt-dp-body[data-tab="biz"] .diq-section[data-tab-cat="clinical"]{display:none}',
    /* ─── Light-mode theme overrides — 2026-05-05 ───────────────────────────
       The block above bakes:
         - rgba(7,12,23,0.X)     → dark navy panel surfaces
         - rgba(255,255,255,0.X) → white tints (invisible on light bg)
       In light mode these all read as either solid navy bars or invisible.
       These overrides re-bind backgrounds to light-glass (rgba(255,255,255,X))
       and tints/dividers to slate (rgba(15,23,42,X)).  Dark mode is unaffected
       (lower specificity). */
    /* Anchor + headline + cmdbar — were dark navy panels */
    '[data-theme="light"] .rt-anchor-bar{background:linear-gradient(135deg,rgba(217,119,6,0.05),rgba(255,255,255,0.85))}',
    '[data-theme="light"] .rt-anchor-pill{background:rgba(15,23,42,0.04)}',
    '[data-theme="light"] .rt-anchor-pill:hover{background:rgba(15,23,42,0.07)}',
    '[data-theme="light"] .rt-anchor-search{background:rgba(15,23,42,0.03)}',
    '[data-theme="light"] .rt-anchor-search:focus-within{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-headline{background:rgba(255,255,255,0.85)}',
    /* Quick-view + group-by + filter rows — were navy strips */
    '[data-theme="light"] .rt-qv-row{background:rgba(255,255,255,0.85)}',
    '[data-theme="light"] .rt-qv-btn{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-qv-btn:hover{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-gby-row{background:rgba(255,255,255,0.85)}',
    '[data-theme="light"] .rt-gby-segctrl{background:rgba(15,23,42,0.03)}',
    '[data-theme="light"] .rt-gby-seg.active{background:rgba(15,23,42,0.10)}',
    '[data-theme="light"] .rt-search-wrap{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-adv-toggle{background:rgba(15,23,42,0.03)}',
    '[data-theme="light"] .rt-adv-panel{background:rgba(255,255,255,0.85)}',
    '[data-theme="light"] .rt-adv-chip{background:rgba(15,23,42,0.025)}',
    /* Race-track row + cells + chips */
    '[data-theme="light"] .race-track-wrap{background:rgba(255,255,255,0.85);box-shadow:inset 0 1px 0 rgba(255,255,255,0.6)}',
    '[data-theme="light"] .rt-track-bar{border-bottom:1px dashed rgba(15,23,42,0.10)}',
    '[data-theme="light"] .rt-track-head{border-bottom:1px dashed rgba(15,23,42,0.10)}',
    '[data-theme="light"] .rt-row{border-bottom:1px dashed rgba(15,23,42,0.06)}',
    '[data-theme="light"] .rt-grp:hover{background:rgba(15,23,42,0.03)}',
    '[data-theme="light"] .rt-cell::before{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-chip{background:rgba(15,23,42,0.04)}',
    '[data-theme="light"] .rt-overflow{background:rgba(15,23,42,0.025)}',
    /* Command bar + filter pop-over */
    '[data-theme="light"] .rt-cmdbar{background:rgba(255,255,255,0.85);box-shadow:inset 0 1px 0 rgba(255,255,255,0.6)}',
    '[data-theme="light"] .rt-cb-div{background:rgba(15,23,42,0.08)}',
    '[data-theme="light"] .rt-cb-anchor{background:rgba(15,23,42,0.04)}',
    '[data-theme="light"] .rt-cb-anchor:hover{background:rgba(15,23,42,0.07)}',
    '[data-theme="light"] .rt-cb-btn{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-cb-btn:hover{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-cb-search{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-cb-search:focus-within{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-filter-pop{background:rgba(255,255,255,0.96);box-shadow:0 12px 36px rgba(15,23,42,0.16),inset 0 1px 0 rgba(255,255,255,0.6)}',
    '[data-theme="light"] .rt-fp-preset{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-fp-preset:hover{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-fp-preset.active{background:rgba(20,184,166,0.10);box-shadow:inset 0 1px 0 rgba(255,255,255,0.6)}',
    '[data-theme="light"] .rt-fp-grid{border-top:1px dashed rgba(15,23,42,0.10)}',
    '[data-theme="light"] .rt-fp-input{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-fp-input:focus{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-fp-foot{border-top:1px dashed rgba(15,23,42,0.10)}',
    '[data-theme="light"] .rt-fp-close{background:rgba(15,23,42,0.06)}',
    '[data-theme="light"] .rt-fp-close:hover{background:rgba(15,23,42,0.10)}',
    '[data-theme="light"] .rt-pp-back{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-pp-back:hover{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-pp-x:hover{background:rgba(15,23,42,0.06)}',
    '[data-theme="light"] .rt-pp-search{background:rgba(15,23,42,0.03)}',
    '[data-theme="light"] .rt-pp-search:focus-within{background:rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-pp-card{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-pp-card:hover{background:rgba(20,184,166,0.06);box-shadow:0 4px 14px rgba(15,23,42,0.12)}',
    '[data-theme="light"] .rt-pp-row{background:rgba(15,23,42,0.02)}',
    /* Drug detail drawer — dark navy → white card */
    '[data-theme="light"] .rt-drug-scrim{background:rgba(15,23,42,0.30)}',
    '[data-theme="light"] .rt-drug-panel{background:rgba(255,255,255,0.97);border-left:1px solid rgba(15,23,42,0.16);box-shadow:-24px 0 60px rgba(15,23,42,0.20)}',
    '[data-theme="light"] .rt-dp-head{background:rgba(15,23,42,0.02)}',
    '[data-theme="light"] .rt-dp-close:hover{background:rgba(15,23,42,0.08)}',
    /* Statline + table */
    '[data-theme="light"] .rt-statline{background:rgba(15,23,42,0.03)}',
    '[data-theme="light"] .rt-statline-sep{color:rgba(15,23,42,0.20)}',
    '[data-theme="light"] .rt-tbl-wrap{background:rgba(255,255,255,0.85)}',
    '[data-theme="light"] .rt-tbl thead th{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-tbl-row{border-bottom:1px dashed rgba(15,23,42,0.08)}',
    '[data-theme="light"] .rt-tbl-overflow{background:rgba(15,23,42,0.025)}',
    /* Heatmap cells — keep tier tints (cool/warm/hot) but fix neutral surfaces */
    '[data-theme="light"] .rt-hm-swatch{border:1px solid rgba(15,23,42,0.10)}',
    '[data-theme="light"] .rt-hm-swatch.zero{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-hm-rowlabel{background:rgba(15,23,42,0.025)}',
    '[data-theme="light"] .rt-hm-zero{background:rgba(15,23,42,0.02);border:1px solid rgba(15,23,42,0.05)}',
    '[data-theme="light"] .rt-hm-zero span{color:rgba(15,23,42,0.30)}',
    '[data-theme="light"] .rt-hm-total{background:rgba(15,23,42,0.04)}',
    /* Highlight-org input (inline-styled — class added to enable theme swap) */
    '.rt-highlight-org-input{background:rgba(255,255,255,0.025)}',
    '[data-theme="light"] .rt-highlight-org-input{background:rgba(15,23,42,0.025)}'
  ].join('\n');
  document.head.appendChild(s);
}

// ─── Anchor breadcrumb at top ──────────────────────────────────────────────
function _buildAnchorBreadcrumb(anchor) {
  var typeLabel = _ANCHOR_LABELS[anchor.type] || anchor.type;
  return '<div class="rt-anchor-bar">' +
    '<span class="rt-anchor-eyebrow">Currently exploring</span>' +
    '<span class="rt-anchor-pill active" onclick="_openAnchorPicker()">' +
      '<span class="rt-anchor-type">' + typeLabel + '</span>' +
      '<span class="rt-anchor-name">' + (anchor.label || anchor.value) + '</span>' +
      '<span class="rt-anchor-chev">▾</span>' +
    '</span>' +
    '<div class="rt-anchor-search">' +
      '<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" style="color:var(--text-faint);flex-shrink:0"><circle cx="7" cy="7" r="5"/><line x1="14" y1="14" x2="11" y2="11" stroke-linecap="round"/></svg>' +
      '<input type="text" placeholder="Search drug · target · TA · disease · org…" oninput="_onAnchorSearchInput(event)" />' +
    '</div>' +
  '</div>';
}

// ─── Anchor picker (modal-style dropdown) ─────────────────────────────────
// Phase 4.3 (2026-05-03): rewired as a 2-step picker. Step 1 = category
// grid (TA / Disease / Target / Modality / MOA / Drug). Step 2 = scrollable
// list of distinct values pulled from a GLOBAL POOL (not _CV.rows) for the
// selected category, filtered by the search input. Click any value to set
// the anchor and close the picker. Back button returns to step 1.
//
// UR-008 fix (2026-05-05): _CV.rows is filtered to the current anchor (e.g.
// 200 Oncology rows when anchor is TA=Oncology), so reading TAs from it
// returned only 1 TA. The picker now reads from `_GLOBAL_POOL.rows` which
// is populated once at module load with an unanchored slice (limit=200,
// the backend max). Diseases come from /v2/compete-iq/diseases (2k).
// Cached for the session — re-fetched only on backend deploy.
var _PICKER_STATE = { mode: 'cards', type: null, q: '' };

// Global pool — unanchored slice for picker option enumeration.
// Populated once via _ensureGlobalPool(); subsequent calls are cached.
var _GLOBAL_POOL = {
  rows: [],            // unanchored rankCompete slice (canonical TA/target/modality/MOA/drug pool)
  diseases: [],        // /v2/compete-iq/diseases (≤ 2,000 names)
  tas: [],             // distinct TAs derived from rows (or whitespace TA endpoint as fallback)
  loaded: false,       // becomes true after first successful fetch
  loading: false       // de-dup in-flight fetches
};

function _ensureGlobalPool() {
  if (_GLOBAL_POOL.loaded || _GLOBAL_POOL.loading) return;
  if (!window.DASHIQ) return;
  _GLOBAL_POOL.loading = true;

  // Backend `/v2/compete-iq/rank` clamps limit at 200 — that's our widest
  // unanchored window. For 6k+ rows we won't see every disease this way,
  // but we WILL see every TA (12 distinct), every top modality (~6), and
  // a representative target/MOA pool. Disease list comes from the dedicated
  // /diseases endpoint which ships 2k names.
  var p1 = (typeof window.DASHIQ.rankCompete === 'function')
    ? window.DASHIQ.rankCompete({ limit: 200 })
    : Promise.resolve({ results: [] });
  var p2 = (typeof window.DASHIQ.fetchCompeteDiseases === 'function')
    ? window.DASHIQ.fetchCompeteDiseases({ limit: 2000 })
    : Promise.resolve({ results: [] });

  Promise.all([p1, p2]).then(function(both) {
    var rankRes = both[0]; var disRes = both[1];
    var rows = (rankRes && (rankRes.results || rankRes.rows || rankRes)) || [];
    if (!Array.isArray(rows)) rows = [];
    _GLOBAL_POOL.rows = rows;

    // Disease list — dedicated endpoint ships an array of strings (or
    // {disease_name}/{name} objects). Normalize to a sorted unique array.
    var disList = (disRes && (disRes.results || disRes.diseases || disRes)) || [];
    if (!Array.isArray(disList)) disList = [];
    var disSet = {};
    disList.forEach(function(d) {
      var n = (typeof d === 'string') ? d : (d && (d.disease_name || d.disease || d.name));
      if (n) disSet[n] = 1;
    });
    _GLOBAL_POOL.diseases = Object.keys(disSet).sort(function(a, b) { return a.localeCompare(b); });

    // Distinct TAs from rows (canonical 12 TAs from compete data)
    var taSet = {};
    rows.forEach(function(r) {
      var t = r.therapeutic_area || r._top_ta || r.ta;
      if (t) taSet[t] = 1;
    });
    _GLOBAL_POOL.tas = Object.keys(taSet).sort(function(a, b) { return a.localeCompare(b); });

    _GLOBAL_POOL.loaded = true;
    _GLOBAL_POOL.loading = false;

    // If the picker is currently open, re-render it so newly-loaded options
    // (and updated card counts) appear without requiring close+reopen.
    var openPicker = document.getElementById('rt-anchor-picker');
    if (openPicker) _renderPicker();
  }).catch(function() {
    _GLOBAL_POOL.loading = false; // allow retry on next open
  });
}

window._openAnchorPicker = function() {
  var existing = document.getElementById('rt-anchor-picker');
  if (existing) { existing.remove(); return; }
  // UR-008 fix: ensure the unanchored option pool is loaded so the TA /
  // Disease pickers show ALL distinct values (12 TAs / 2k diseases) rather
  // than just whatever the current anchor exposes.
  _ensureGlobalPool();
  _PICKER_STATE = { mode: 'cards', type: null, q: '' };
  var picker = document.createElement('div');
  picker.id = 'rt-anchor-picker';
  // Theme-aware picker chrome — inline cssText overrides CSS, so swap at runtime.
  var _pickerIsLight = document.documentElement.getAttribute('data-theme') === 'light';
  var _pickerBg = _pickerIsLight ? 'rgba(255,255,255,0.97)' : 'rgba(7,12,23,0.96)';
  var _pickerShadow = _pickerIsLight ? '0 24px 60px rgba(15,23,42,0.20)' : '0 24px 60px rgba(0,0,0,0.6)';
  picker.style.cssText = 'position:fixed;top:120px;left:50%;transform:translateX(-50%);width:min(620px,92vw);max-height:75vh;display:flex;flex-direction:column;background:' + _pickerBg + ';border:1px solid var(--g-border-strong);border-radius:14px;box-shadow:' + _pickerShadow + ';z-index:9999;backdrop-filter:blur(24px) saturate(160%);-webkit-backdrop-filter:blur(24px) saturate(160%)';
  document.body.appendChild(picker);
  _renderPicker();
  // Light dismiss on outside click
  setTimeout(function() {
    document.addEventListener('click', _pickerOutsideClick, { capture: true });
  }, 50);
};

function _pickerOutsideClick(e) {
  var p = document.getElementById('rt-anchor-picker');
  if (!p) { document.removeEventListener('click', _pickerOutsideClick, { capture: true }); return; }
  if (!p.contains(e.target) && !e.target.closest('.rt-cb-anchor')) {
    p.remove();
    document.removeEventListener('click', _pickerOutsideClick, { capture: true });
  }
}

function _renderPicker() {
  var p = document.getElementById('rt-anchor-picker');
  if (!p) return;
  if (_PICKER_STATE.mode === 'cards') {
    p.innerHTML = _renderPickerCards();
  } else {
    p.innerHTML = _renderPickerList(_PICKER_STATE.type);
    // focus search input
    setTimeout(function() {
      var inp = p.querySelector('.rt-pp-search input');
      if (inp) inp.focus();
    }, 50);
  }
}

function _renderPickerCards() {
  return '<div class="rt-pp-head">' +
    '<div class="rt-pp-title">Pick a space to explore</div>' +
    '<button class="rt-pp-x" onclick="document.getElementById(\'rt-anchor-picker\').remove()">×</button>' +
  '</div>' +
  '<div class="rt-pp-body">' +
    '<div class="rt-pp-grid">' +
      _anchorPickerCard('TA',       'Therapeutic area', _pickerCount('TA')      + ' TAs in scope') +
      _anchorPickerCard('DISEASE',  'Disease',          _pickerCount('DISEASE') + ' diseases') +
      _anchorPickerCard('TARGET',   'Target',           _pickerCount('TARGET')  + ' targets') +
      _anchorPickerCard('MODALITY', 'Modality',         _pickerCount('MODALITY')+ ' modalities') +
      _anchorPickerCard('MOA',      'Mechanism',        _pickerCount('MOA')     + ' MOAs') +
      _anchorPickerCard('DRUG',     'Drug',             _pickerCount('DRUG')    + ' drugs') +
    '</div>' +
  '</div>' +
  '<div class="rt-pp-foot">Pick a category to see options · counts reflect the global compete dataset</div>';
}

// Distinct-value extractor — reads from the GLOBAL POOL (unanchored slice).
// UR-008 fix: previously read from _CV.rows which is filtered by the active
// anchor, so the TA picker only ever showed 1 TA (the anchored one). Now
// reads from `_GLOBAL_POOL.rows` (rankCompete unanchored, limit 200) for
// TA/Target/Modality/MOA/Drug, and from `_GLOBAL_POOL.diseases` (the
// dedicated /diseases endpoint, up to 2,000) for the Disease picker.
//
// `_CV.rows` remains the right source for the in-space race-track render —
// it's anchor-scoped on purpose. The picker is the only callsite that
// needs the global pool.
function _pickerOptions(type) {
  var pool = _GLOBAL_POOL && _GLOBAL_POOL.loaded ? _GLOBAL_POOL : null;
  var rows = pool ? pool.rows : (_CV.rows || []);
  var seen = {};
  var out = [];
  function add(label, value, sub) {
    if (!label || seen[label]) return;
    seen[label] = 1;
    out.push({ label: label, value: value || label, sub: sub || '' });
  }

  // Disease picker — prefer the dedicated /diseases endpoint (≤ 2,000 names)
  // when available. Falls back to row-derived disease names.
  if (type === 'DISEASE') {
    if (pool && pool.diseases && pool.diseases.length) {
      pool.diseases.forEach(function(name) { add(name, name, ''); });
    }
    // Augment with row-level disease_name in case some appear in rank but
    // not in the dedicated endpoint snapshot (rare but possible).
    rows.forEach(function(r) {
      if (r.disease_name) add(r.disease_name, r.disease_name, r.therapeutic_area);
    });
    return out.sort(function(a, b) { return a.label.localeCompare(b.label); });
  }

  // TA picker — always derive from the global pool's distinct TA set.
  // (12 TAs in the canonical compete dataset.)
  // CTK 2026-05-07: render labels in Title Case ("Infectious Disease",
  // "Rare Disease") instead of storage form ("INFECTIOUSDISEASE").
  // Value remains storage form (used as filter key downstream).
  if (type === 'TA') {
    if (pool && pool.tas && pool.tas.length) {
      pool.tas.forEach(function(t) { add(_formatTaLabel(t), t, ''); });
    } else {
      rows.forEach(function(r) {
        var t = r.therapeutic_area || r._top_ta || r.ta;
        if (t) add(_formatTaLabel(t), t, '');
      });
    }
    return out.sort(function(a, b) { return a.label.localeCompare(b.label); });
  }

  // Target / Modality / MOA / Drug — derive from the global pool's row set.
  rows.forEach(function(r) {
    if (type === 'TARGET' && r.lead_target) {
      String(r.lead_target).split(',').forEach(function(t) { add(t.trim()); });
    }
    if (type === 'MODALITY' && r.lead_modality) add(r.lead_modality);
    if (type === 'MOA' && r.lead_moa) add(r.lead_moa);
    if (type === 'DRUG' && r.lead_drug_name) add(r.lead_drug_name, r.lead_drug_name, r.org_name);
  });
  return out.sort(function(a, b) { return a.label.localeCompare(b.label); });
}

function _pickerCount(type) {
  return _pickerOptions(type).length;
}

function _renderPickerList(type) {
  var typeLabel = _ANCHOR_LABELS[type] || type;
  var opts = _pickerOptions(type);
  var q = (_PICKER_STATE.q || '').toLowerCase();
  if (q) opts = opts.filter(function(o) {
    return o.label.toLowerCase().indexOf(q) >= 0 ||
           (o.sub || '').toLowerCase().indexOf(q) >= 0;
  });

  var listHTML;
  if (opts.length === 0) {
    listHTML = '<div class="rt-pp-empty"><b>No matches.</b><br/>Try a different search or pick another category.</div>';
  } else {
    listHTML = '<div class="rt-pp-list">' + opts.slice(0, 200).map(function(o) {
      var safeLabel = String(o.label).replace(/'/g, "\\'");
      var safeValue = String(o.value).replace(/'/g, "\\'");
      var safeSub = String(o.sub || '').replace(/'/g, "\\'");
      return '<button class="rt-pp-row" onclick="_pickAnchorValue(\'' + type + '\',\'' + safeValue + '\',\'' + safeLabel + '\',\'' + safeSub + '\')">' +
        '<span class="rt-pp-row-name">' + o.label + '</span>' +
        (o.sub ? '<span class="rt-pp-row-sub">' + o.sub + '</span>' : '') +
      '</button>';
    }).join('') + '</div>';
    if (opts.length > 200) {
      listHTML += '<div class="rt-pp-more">+ ' + (opts.length - 200) + ' more · refine the search</div>';
    }
  }

  return '<div class="rt-pp-head">' +
    '<button class="rt-pp-back" onclick="_pickerBack()">← Back</button>' +
    '<div class="rt-pp-title-row">' +
      '<div class="rt-pp-title">' + typeLabel + '</div>' +
      '<span class="rt-pp-count">' + opts.length + (_PICKER_STATE.q ? ' / ' + _pickerOptions(type).length : '') + '</span>' +
    '</div>' +
    '<button class="rt-pp-x" onclick="document.getElementById(\'rt-anchor-picker\').remove()">×</button>' +
  '</div>' +
  '<div class="rt-pp-search">' +
    '<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" style="color:var(--text-faint);flex-shrink:0"><circle cx="7" cy="7" r="5"/><line x1="14" y1="14" x2="11" y2="11" stroke-linecap="round"/></svg>' +
    '<input type="text" placeholder="Filter ' + typeLabel.toLowerCase() + 's…" oninput="_pickerSearch(event)" value="' + (_PICKER_STATE.q || '') + '" />' +
  '</div>' +
  '<div class="rt-pp-body">' + listHTML + '</div>';
}

function _anchorPickerCard(type, label, sub) {
  return '<button class="rt-pp-card" onclick="_pickerOpenList(\'' + type + '\')">' +
    '<div class="rt-pp-card-name">' + label + '</div>' +
    '<div class="rt-pp-card-sub">' + sub + '</div>' +
  '</button>';
}

window._pickerOpenList = function(type) {
  _PICKER_STATE = { mode: 'list', type: type, q: '' };
  _renderPicker();
};

window._pickerBack = function() {
  _PICKER_STATE = { mode: 'cards', type: null, q: '' };
  _renderPicker();
};

window._pickerSearch = function(evt) {
  _PICKER_STATE.q = evt.target.value;
  // Re-render only the list (preserve focus on input)
  var p = document.getElementById('rt-anchor-picker');
  if (!p) return;
  var bodyEl = p.querySelector('.rt-pp-body');
  var countEl = p.querySelector('.rt-pp-count');
  if (!bodyEl || !_PICKER_STATE.type) return;
  var type = _PICKER_STATE.type;
  var opts = _pickerOptions(type);
  var q = (_PICKER_STATE.q || '').toLowerCase();
  var filtered = q
    ? opts.filter(function(o) { return o.label.toLowerCase().indexOf(q) >= 0 || (o.sub || '').toLowerCase().indexOf(q) >= 0; })
    : opts;
  if (countEl) countEl.textContent = filtered.length + (q ? ' / ' + opts.length : '');
  bodyEl.innerHTML = filtered.length === 0
    ? '<div class="rt-pp-empty"><b>No matches.</b><br/>Try a different search.</div>'
    : '<div class="rt-pp-list">' + filtered.slice(0, 200).map(function(o) {
        var sl = String(o.label).replace(/'/g, "\\'");
        var sv = String(o.value).replace(/'/g, "\\'");
        var ss = String(o.sub || '').replace(/'/g, "\\'");
        return '<button class="rt-pp-row" onclick="_pickAnchorValue(\'' + type + '\',\'' + sv + '\',\'' + sl + '\',\'' + ss + '\')">' +
          '<span class="rt-pp-row-name">' + o.label + '</span>' +
          (o.sub ? '<span class="rt-pp-row-sub">' + o.sub + '</span>' : '') +
        '</button>';
      }).join('') + '</div>'
        + (filtered.length > 200 ? '<div class="rt-pp-more">+ ' + (filtered.length - 200) + ' more · refine search</div>' : '');
};

// New unified pick — works for any anchor type with a real selected value.
// Special-case DRUG: don't re-anchor the page; open the side panel instead.
window._pickAnchorValue = function(type, value, label, sub) {
  var picker = document.getElementById('rt-anchor-picker');
  if (picker) picker.remove();
  document.removeEventListener('click', _pickerOutsideClick, { capture: true });
  if (type === 'DRUG') {
    _openDrugPanel(sub || '', value);
    return;
  }
  _setAnchor({ type: type, value: value, label: label || value });
};

// Legacy shim — old code still calls _pickAnchorType for the 6 hardcoded demos.
// Now it just opens the per-type list instead of jumping straight to a hardcoded value.
window._pickAnchorType = function(type) {
  _pickerOpenList(type);
};

window._onAnchorSearchInput = function(evt) {
  _CV.search = evt.target.value;
};

function _setAnchor(anchor) {
  // Stash the most recent non-drug anchor so "Back to Landscape" buttons
  // inside the drug profile can restore it.
  if (_CV.anchor && _CV.anchor.type && _CV.anchor.type !== 'DRUG') {
    _CV._lastSpaceAnchor = _CV.anchor;
  }
  _CV.anchor = anchor;
  if (window.STATE) window.STATE.compete_anchor = anchor;
  if (typeof addBreadcrumb === 'function') {
    addBreadcrumb((_ANCHOR_LABELS[anchor.type] || '') + ' · ' + anchor.label, '');
  }
  renderCompeteIQ();
}
window._setAnchor = _setAnchor;

// ─── Headline strip ────────────────────────────────────────────────────────
function _buildHeadline(anchor, rows) {
  var nDrugs = rows.length;
  var nOrgs = (function(){
    var s = {}; rows.forEach(function(r){ if (r.org_name) s[r.org_name]=1; });
    return Object.keys(s).length;
  })();
  var nApproved = rows.filter(function(r){ return _RT_PHASES[4].match(r.max_phase_name); }).length;
  var nP3 = rows.filter(function(r){ return _RT_PHASES[3].match(r.max_phase_name); }).length;
  var nNew = Math.min(nDrugs, Math.round(nDrugs * 0.08)); // demo proxy

  var typeLabel = _ANCHOR_LABELS[anchor.type] || anchor.type;
  return '<div class="rt-headline">' +
    '<div class="rt-headline-row">' +
      '<div>' +
        '<div style="font-family:var(--font-mono);font-size:8.5px;font-weight:800;letter-spacing:0.20em;text-transform:uppercase;color:var(--amber-warm,#f59e0b);margin-bottom:4px">' + typeLabel + ' · ' + anchor.label + '</div>' +
        '<h1 class="rt-h-line"><b>' + nDrugs + ' drugs</b> across <b>' + nOrgs + ' orgs</b> are competing in this space — <em>' + nP3 + ' in Phase 3</em>, ' + nApproved + ' approved.</h1>' +
      '</div>' +
      '<div class="rt-h-stats">' +
        '<div class="rt-h-stat"><div class="rt-h-stat-num">' + nDrugs + '</div><div class="rt-h-stat-label">Drugs<br/>in race</div></div>' +
        '<div class="rt-h-stat"><div class="rt-h-stat-num">' + nOrgs + '</div><div class="rt-h-stat-label">Active<br/>orgs</div></div>' +
        '<div class="rt-h-stat"><div class="rt-h-stat-num warn">' + nP3 + '</div><div class="rt-h-stat-label">Phase 3<br/>imminent</div></div>' +
        '<div class="rt-h-stat"><div class="rt-h-stat-num win">' + nApproved + '</div><div class="rt-h-stat-label">Approved<br/>on market</div></div>' +
      '</div>' +
    '</div>' +
  '</div>';
}

// ─── Filter stack: Quick Views + Group By + Advanced ───────────────────────
function _buildFilterStack(anchor) {
  // Quick views row
  var qvHTML = '<div class="rt-qv-row">' +
    '<span class="rt-qv-label">View</span>' +
    _QUICK_VIEWS.map(function(qv) {
      var act = (_CV.view === qv.k) ? ' active' : '';
      return '<button class="rt-qv-btn' + act + '" onclick="_setQuickView(\'' + qv.k + '\')">' +
        '<span class="rt-qv-name">' + qv.name + '</span>' +
        '<span class="rt-qv-tag">' + qv.tag + '</span>' +
      '</button>';
    }).join('') +
  '</div>';

  // Group by + Search + Advanced toggle
  var gbyOpts = _GROUPBY_BY_ANCHOR[anchor.type] || [];
  var resolvedGroupBy = _CV.groupBy === 'auto' && gbyOpts.length ? gbyOpts[0].k : _CV.groupBy;
  var gbyHTML = '<div class="rt-gby-row' + (_CV.advancedOpen ? '' : ' last') + '">' +
    '<div style="display:flex;align-items:center;gap:8px">' +
      '<span class="rt-qv-label">Group by</span>' +
      '<div class="rt-gby-segctrl">' +
        gbyOpts.map(function(o) {
          var act = (resolvedGroupBy === o.k) ? ' active' : '';
          return '<button class="rt-gby-seg' + act + '" onclick="_setGroupBy(\'' + o.k + '\')">' + o.label + '</button>';
        }).join('') +
      '</div>' +
    '</div>' +
    '<div></div>' +
    '<div class="rt-search-wrap">' +
      '<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" style="color:var(--text-faint);flex-shrink:0"><circle cx="7" cy="7" r="5"/><line x1="14" y1="14" x2="11" y2="11" stroke-linecap="round"/></svg>' +
      '<input type="text" placeholder="Filter by drug, target, org…" oninput="_onTrackSearchInput(event)" value="' + (_CV.search || '') + '" />' +
    '</div>' +
    '<div style="display:flex;gap:7px;align-items:center">' +
      (_activeFilterCount() > 0
        ? '<button class="rt-adv-toggle has-active" style="border-color:rgba(244,63,94,0.32);color:var(--rose,#f43f5e);background:rgba(244,63,94,0.06)" onclick="_resetFilters()">↺ Reset</button>'
        : '') +
      '<button class="rt-adv-toggle' + (_activeFilterCount() > 0 ? ' has-active' : '') + (_CV.advancedOpen ? ' open' : '') + '" onclick="_toggleAdvanced()">Advanced' +
        (_activeFilterCount() > 0 ? ' <span style="background:var(--amber-warm,#f59e0b);color:#070b14;padding:1px 5px;border-radius:8px;font-size:8.5px;font-weight:800">' + _activeFilterCount() + '</span>' : '') +
        ' ' + (_CV.advancedOpen ? '▴' : '▾') + '</button>' +
    '</div>' +
  '</div>';

  // Advanced filter panel
  var advHTML = _CV.advancedOpen ? _buildAdvancedPanel() : '';

  return '<div class="rt-filter-stack">' + qvHTML + gbyHTML + advHTML + '</div>';
}

function _buildAdvancedPanel() {
  var f = _CV.filters;
  function chipFor(label, key, val, count) {
    var on = (f[key] && (Array.isArray(f[key]) ? f[key].indexOf(val) >= 0 : f[key] === val));
    var ct = count != null ? '<span style="opacity:0.55;font-size:8.5px"> ' + count + '</span>' : '';
    return '<span class="rt-adv-chip' + (on ? ' on' : '') + '" onclick="_toggleAdvFilter(\'' + key + '\',\'' + val + '\')">' + label + ct + '</span>';
  }
  return '<div class="rt-adv-panel">' +
    // Phase
    '<div class="rt-adv-section"><span class="rt-adv-label">Phase</span><div class="rt-adv-chips">' +
      chipFor('All','phase','all',null) +
      chipFor('P1+','phase','P1+',null) +
      chipFor('P2+','phase','P2+',null) +
      chipFor('P3+','phase','P3+',null) +
      chipFor('Approved','phase','approved',null) +
    '</div></div>' +
    // Org category
    '<div class="rt-adv-section"><span class="rt-adv-label">Org category</span><div class="rt-adv-chips">' +
      chipFor('Pharma','orgCategory','PHARMA',null) +
      chipFor('Biotech','orgCategory','BIOTECH',null) +
      chipFor('Academic','orgCategory','ACADEMIC',null) +
      chipFor('Other','orgCategory','OTHER',null) +
    '</div></div>' +
    // Modality
    '<div class="rt-adv-section"><span class="rt-adv-label">Modality</span><div class="rt-adv-chips">' +
      chipFor('SM','modality','Small Molecule',null) +
      chipFor('mAb','modality','mAb',null) +
      chipFor('ADC','modality','ADC',null) +
      chipFor('CAR-T','modality','Cell Therapy',null) +
      chipFor('Gene','modality','Gene Therapy',null) +
    '</div></div>' +
    // Highlight my org
    '<div class="rt-adv-section"><span class="rt-adv-label">Highlight my org</span>' +
      '<input class="rt-highlight-org-input" type="text" placeholder="e.g. Sanofi" value="' + (_CV.highlightOrg || '') + '" oninput="_setHighlightOrg(event.target.value)" style="border:1px solid var(--g-border);border-radius:6px;padding:5px 9px;color:var(--text);font-family:var(--font-display);font-size:11px;letter-spacing:-0.005em;outline:0" />' +
      '<span style="font-family:var(--font-mono);font-size:7.5px;letter-spacing:0.10em;color:var(--text-faint);text-transform:uppercase;margin-top:3px">Glow this org\'s drugs in amber</span>' +
    '</div>' +
  '</div>';
}

function _activeFilterCount() {
  var n = 0;
  var f = _CV.filters;
  if (f.phase && f.phase !== 'all') n++;
  if (f.orgCategory && f.orgCategory.length) n++;
  if (f.modality && f.modality.length) n++;
  if (_CV.search) n++;
  if (_CV.highlightOrg) n++;
  return n;
}

window._setQuickView = function(k) { _CV.view = k; renderCompeteIQ(); };
window._setGroupBy = function(k) { _CV.groupBy = k; renderCompeteIQ(); };
window._toggleAdvanced = function() { _CV.advancedOpen = !_CV.advancedOpen; renderCompeteIQ(); };
window._setHighlightOrg = function(v) { _CV.highlightOrg = v; renderCompeteIQ(); };
window._resetFilters = function() {
  _CV.view = 'all';
  _CV.search = '';
  _CV.highlightOrg = '';
  _CV.filters = { phase:'all', orgCategory:[], modality:[], momentum:0 };
  renderCompeteIQ();
};
window._toggleAdvFilter = function(key, val) {
  var f = _CV.filters;
  if (key === 'phase') { f.phase = val; }
  else if (Array.isArray(f[key])) {
    var i = f[key].indexOf(val);
    if (i >= 0) f[key].splice(i, 1); else f[key].push(val);
  }
  renderCompeteIQ();
};
var _searchDebounceTimer = null;
window._onTrackSearchInput = function(evt) {
  _CV.search = evt.target.value;
  clearTimeout(_searchDebounceTimer);
  _searchDebounceTimer = setTimeout(function() { renderCompeteIQ(); }, 220);
};

// ─── Apply filters to rows ─────────────────────────────────────────────────
function _applyFilters(rows) {
  var f = _CV.filters;
  var q = (_CV.search || '').toLowerCase().trim();
  return rows.filter(function(r) {
    if (q) {
      var hay = [(r.lead_drug_name||''), (r.lead_target||''), (r.org_name||''), (r.disease_name||'')].join(' ').toLowerCase();
      if (hay.indexOf(q) < 0) return false;
    }
    if (f.phase !== 'all') {
      var p = r.max_phase_name;
      var pIdx = -1;
      _RT_PHASES.forEach(function(ph, i){ if (ph.match(p)) pIdx = i; });
      if (f.phase === 'P1+' && pIdx < 1) return false;
      if (f.phase === 'P2+' && pIdx < 2) return false;
      if (f.phase === 'P3+' && pIdx < 3) return false;
      if (f.phase === 'approved' && pIdx < 4) return false;
    }
    if (f.orgCategory.length) {
      var cat = r.org_category || _inferOrgType(r.org_name);
      if (f.orgCategory.indexOf(cat) < 0) return false;
    }
    if (f.modality.length) {
      var mod = r.lead_modality || assetModality(r.lead_drug_name);
      if (f.modality.indexOf(mod) < 0) return false;
    }
    // Quick-view presets
    if (_CV.view === 'patent-cliff') {
      if (!_RT_PHASES[4].match(r.max_phase_name)) return false;
    } else if (_CV.view === 'hot-zones') {
      if ((r.momentum_score || 0) < 0.6) return false;
    } else if (_CV.view === 'new-entrants') {
      // proxy: low evidence_tier or high momentum
      if ((r.momentum_score || 0) < 0.7 && r.evidence_tier !== 'BRONZE') return false;
    }
    return true;
  });
}

// ─── Group rows by current group-by key ────────────────────────────────────
function _groupRows(rows, groupKey) {
  var groups = {};
  rows.forEach(function(r) {
    var k;
    if (groupKey === 'org') k = r.org_name || '—';
    else if (groupKey === 'disease') k = r.disease_name || '—';
    else if (groupKey === 'target') k = (r.lead_target || '').split(',')[0].trim() || '—';
    else if (groupKey === 'modality') k = r.lead_modality || assetModality(r.lead_drug_name) || '—';
    else if (groupKey === 'ta') k = r.therapeutic_area || '—';
    else k = r.org_name || '—';
    if (!groups[k]) groups[k] = [];
    groups[k].push(r);
  });
  // Sort keys by count desc, cap to top 8 rows for legibility
  var keys = Object.keys(groups).sort(function(a, b) { return groups[b].length - groups[a].length; });
  return keys.slice(0, 8).map(function(k) { return { key: k, rows: groups[k] }; });
}

// ─── Build a single drug chip ──────────────────────────────────────────────
function _buildChip(r) {
  var hi = (_CV.highlightOrg && r.org_name &&
            r.org_name.toLowerCase().indexOf(String(_CV.highlightOrg).toLowerCase()) >= 0);
  var safeOrg = _safeOrg(r.org_name || '');
  var safeDrug = _safeOrg(r.lead_drug_name || '');
  var ipCount = parseInt(r.n_patents, 10) || 0;
  var momentum = r.momentum_score || 0;
  var momTxt = momentum > 0.7 ? '▶▶' : (momentum > 0.4 ? '▶' : '·');
  var nameDisp = r.lead_drug_name || (r.org_name + ' (no lead)');
  return '<div class="rt-chip' + (hi ? ' highlight' : '') + '" onclick="_anchorOnDrug(\'' + safeOrg + '\',\'' + safeDrug + '\')">' +
    '<div class="rt-chip-row1">' +
      '<span class="rt-chip-dot"></span>' +
      '<span class="rt-chip-org">' + (r.org_name || '').slice(0, 14) + '</span>' +
      '<span class="rt-chip-name">' + String(nameDisp).slice(0, 16) + '</span>' +
    '</div>' +
    '<div class="rt-chip-evidence">' +
      _phasePillForChip(r.max_phase_name) +
      (ipCount ? '<span class="rt-ev pat">' + ipCount + ' IP</span>' : '') +
      '<span class="rt-ev tri">' + momTxt + '</span>' +
    '</div>' +
  '</div>';
}

// Phase 4.4 (2026-05-03): clicking a drug chip no longer re-anchors to a
// DRUG type (which replaced the whole page). Instead it opens a side panel
// over the race track so the user keeps their navigation context.
window._anchorOnDrug = function(orgName, drugName) {
  if (!drugName) return;
  _openDrugPanel(orgName, drugName);
};

// ─── Phase 4.6 (2026-05-03) — view modes + stat line + table + heatmap ─────
window._setViewMode = function(m) {
  _CV.viewMode = m;
  renderCompeteIQ();
};

// Resolve a phase string to one of: 'unknown' | 'preclinical' | 'p1' | 'p2' | 'p3' | 'approved'.
// DATA-1: NULL/empty max_phase_name returns 'unknown' (data absent), NOT
// 'preclinical' (a clinical claim). Domain decision artifact at
// _AUDIT/2026-05-08/DATA-1/domain_decision.json. Empirical 8% NULL rate at
// /v2/compete-iq/rank?limit=200 (16 of 200) — affected drugs include
// Tenofovir / Isoniazid / Erythromycin / Cycloserine / Rifampin which were
// silently mislabeled "Pre-clinical" before this fix.
function _phaseBucket(p) {
  if (!p) return 'unknown';
  for (var i = 0; i < _RT_PHASES.length; i++) {
    if (_RT_PHASES[i].match(p)) return _RT_PHASES[i].k;
  }
  return 'preclinical';
}

function _phasePillForChip(p) {
  var k = _phaseBucket(p);
  // DATA-1: 'unknown' renders as em-dash in neutral grey — distinct from
  // both colored clinical pills and from generic '—' missing-data tokens.
  var labels = { unknown:'—', preclinical:'Pre', p1:'P1', p2:'P2', p3:'P3', approved:'Appr' };
  var styles = {
    unknown:     'background:rgba(148,163,184,0.14);color:#94a3b8;border:1px solid rgba(148,163,184,0.30)',
    preclinical: 'background:rgba(124,58,237,0.14);color:#a78bfa;border:1px solid rgba(124,58,237,0.30)',
    p1:          'background:rgba(59,130,246,0.14);color:#3b82f6;border:1px solid rgba(59,130,246,0.30)',
    p2:          'background:rgba(20,184,166,0.14);color:#14b8a6;border:1px solid rgba(20,184,166,0.30)',
    p3:          'background:rgba(245,158,11,0.16);color:#f59e0b;border:1px solid rgba(245,158,11,0.32)',
    approved:    'background:rgba(244,63,94,0.14);color:#f43f5e;border:1px solid rgba(244,63,94,0.30)'
  };
  return '<span class="rt-ev rt-ev-phase" style="' + (styles[k] || styles.unknown) + ';font-weight:800">' + labels[k] + '</span>';
}

// ─── Stat line ──────────────────────────────────────────────────────────────
function _buildStatLine(rows) {
  if (!rows || !rows.length) return '';
  var nDrugs = rows.length;
  var nP3plus = rows.filter(function(r){
    var k = _phaseBucket(r.max_phase_name);
    return k === 'p3' || k === 'approved';
  }).length;
  var nApproved = rows.filter(function(r){ return _phaseBucket(r.max_phase_name) === 'approved'; }).length;
  var nHot = rows.filter(function(r){ return (r.momentum_score || 0) >= 0.6; }).length;
  function pair(label, num, cls) {
    return '<span class="rt-statline-cell"><span class="rt-statline-num' + (cls ? ' ' + cls : '') + '">' + num + '</span><span class="rt-statline-label">' + label + '</span></span>';
  }
  return '<div class="rt-statline">' +
    pair('Drugs in race', nDrugs) +
    '<span class="rt-statline-sep">·</span>' +
    pair('P3+ imminent', nP3plus, 'warn') +
    '<span class="rt-statline-sep">·</span>' +
    pair('Approved', nApproved, 'win') +
    '<span class="rt-statline-sep">·</span>' +
    pair('Hot zones', nHot) +
  '</div>';
}

// ─── Table view ─────────────────────────────────────────────────────────────
function _buildTrackTable(anchor, rows) {
  var filtered = _applyFilters(rows);
  if (filtered.length === 0) {
    return '<div class="race-track-wrap"><div class="rt-empty">No drugs match the current filters.<br/><b>Try resetting filters or picking a different space.</b></div></div>';
  }
  // Sort by phase (descending — approved first), then momentum desc.
  var phaseOrder = { approved: 5, p3: 4, p2: 3, p1: 2, preclinical: 1 };
  var sorted = filtered.slice().sort(function(a, b) {
    var pa = phaseOrder[_phaseBucket(a.max_phase_name)] || 0;
    var pb = phaseOrder[_phaseBucket(b.max_phase_name)] || 0;
    if (pa !== pb) return pb - pa;
    return (b.momentum_score || 0) - (a.momentum_score || 0);
  });
  var visible = sorted.slice(0, 200);
  var overflow = sorted.length - visible.length;

  var rowsHTML = visible.map(function(r) {
    var hi = (_CV.highlightOrg && r.org_name &&
              r.org_name.toLowerCase().indexOf(String(_CV.highlightOrg).toLowerCase()) >= 0);
    var safeOrg = _safeOrg(r.org_name || '');
    var safeDrug = _safeOrg(r.lead_drug_name || '');
    var k = _phaseBucket(r.max_phase_name);
    var phaseLabels = { preclinical:'Pre-clinical', p1:'Phase 1', p2:'Phase 2', p3:'Phase 3', approved:'Approved' };
    var ipCount = parseInt(r.n_patents, 10) || 0;
    var momentum = r.momentum_score || 0;
    var momTxt = momentum > 0.7 ? '▶▶' : (momentum > 0.4 ? '▶' : '·');
    return '<tr class="rt-tbl-row' + (hi ? ' highlight' : '') + '" onclick="_anchorOnDrug(\'' + safeOrg + '\',\'' + safeDrug + '\')">' +
      '<td class="rt-tbl-drug">' + (r.lead_drug_name || '—') + '</td>' +
      '<td class="rt-tbl-org">' + (r.org_name || '—') + '</td>' +
      '<td class="rt-tbl-target">' + ((r.lead_target || '').split(',')[0] || '—') + '</td>' +
      '<td class="rt-tbl-modality">' + (r.lead_modality || '—') + '</td>' +
      '<td class="rt-tbl-phase">' + _phasePillForChip(r.max_phase_name).replace('Pre', phaseLabels[k]).replace('P1','Phase 1').replace('P2','Phase 2').replace('P3','Phase 3').replace('Appr','Approved') + '</td>' +
      '<td class="rt-tbl-disease">' + (r.disease_name || '—') + '</td>' +
      '<td class="rt-tbl-num">' + ipCount + '</td>' +
      '<td class="rt-tbl-num">' + (momentum > 0 ? momTxt + ' ' + (momentum * 100).toFixed(0) + '%' : '—') + '</td>' +
    '</tr>';
  }).join('');

  var html = '<div class="race-track-wrap">';
  html += '<div class="rt-track-bar">' +
            '<div>' +
              '<div class="rt-track-title">' + _ANCHOR_LABELS[anchor.type] + ' · ' + anchor.label + ' · table view</div>' +
              '<div class="rt-track-sub">' + filtered.length + ' drug' + (filtered.length === 1 ? '' : 's') + (overflow > 0 ? ' · showing top 200' : '') + '</div>' +
            '</div>' +
            '<div class="rt-legend">' +
              '<span class="lg">Click row → drug detail</span>' +
            '</div>' +
          '</div>';
  html += '<div class="rt-tbl-wrap"><table class="rt-tbl">';
  html +=   '<thead><tr>' +
              '<th>Drug</th>' +
              '<th>Sponsor</th>' +
              '<th>Target</th>' +
              '<th>Modality</th>' +
              '<th>Phase</th>' +
              '<th>Top indication</th>' +
              '<th class="rt-tbl-num">IP</th>' +
              '<th class="rt-tbl-num">Momentum</th>' +
            '</tr></thead>';
  html +=   '<tbody>' + rowsHTML + '</tbody>';
  html += '</table></div>';
  if (overflow > 0) {
    html += '<div class="rt-tbl-overflow">+ ' + overflow + ' more · refine the search or filters</div>';
  }
  html += '</div>';
  return html;
}

// ─── Heatmap view ──────────────────────────────────────────────────────────
function _buildHeatmap(anchor, rows) {
  var groupKey = _CV.groupBy === 'auto' ? (_GROUPBY_BY_ANCHOR[anchor.type] || [{k:'disease'}])[0].k : _CV.groupBy;
  var filtered = _applyFilters(rows);
  if (filtered.length === 0) {
    return '<div class="race-track-wrap"><div class="rt-empty">No drugs match the current filters.</div></div>';
  }
  // Build (group × phase) count matrix.
  var groups = {};
  filtered.forEach(function(r) {
    var k;
    if (groupKey === 'org') k = r.org_name || '—';
    else if (groupKey === 'disease') k = r.disease_name || '—';
    else if (groupKey === 'target') k = (r.lead_target || '').split(',')[0].trim() || '—';
    else if (groupKey === 'modality') k = r.lead_modality || assetModality(r.lead_drug_name) || '—';
    else if (groupKey === 'ta') k = r.therapeutic_area || '—';
    else if (groupKey === 'moa') k = r.lead_moa || '—';
    else k = '—';
    if (!groups[k]) groups[k] = { unknown:0, preclinical:0, p1:0, p2:0, p3:0, approved:0, _total:0 };
    var bucket = _phaseBucket(r.max_phase_name);
    groups[k][bucket]++;
    groups[k]._total++;
  });
  var groupKeys = Object.keys(groups).sort(function(a, b) { return groups[b]._total - groups[a]._total; }).slice(0, 12);

  function cellHTML(count) {
    if (!count) return '<td class="rt-hm-cell rt-hm-zero"><span>—</span></td>';
    // Intensity tiers: 1-3 emerging, 4-12 active, >12 hotspot
    var tier = count >= 13 ? 'hot' : (count >= 4 ? 'act' : 'emg');
    return '<td class="rt-hm-cell rt-hm-' + tier + '"><span>' + count + '</span></td>';
  }

  var html = '<div class="race-track-wrap">';
  html += '<div class="rt-track-bar">' +
            '<div>' +
              '<div class="rt-track-title">' + _ANCHOR_LABELS[anchor.type] + ' · ' + anchor.label + ' · heat-map · grouped by ' + (groupKey.charAt(0).toUpperCase() + groupKey.slice(1)) + '</div>' +
              '<div class="rt-track-sub">' + filtered.length + ' drugs · top ' + groupKeys.length + ' ' + groupKey + ' rows</div>' +
            '</div>' +
            '<div class="rt-hm-legend">' +
              '<span class="lg"><span class="rt-hm-swatch zero"></span>Whitespace</span>' +
              '<span class="lg"><span class="rt-hm-swatch emg"></span>Emerging (1-3)</span>' +
              '<span class="lg"><span class="rt-hm-swatch act"></span>Active (4-12)</span>' +
              '<span class="lg"><span class="rt-hm-swatch hot"></span>Hotspot (13+)</span>' +
            '</div>' +
          '</div>';
  html += '<div class="rt-hm-wrap"><table class="rt-hm">';
  html += '<thead><tr><th></th>';
  _RT_PHASES.forEach(function(ph) { html += '<th>' + ph.label + '</th>'; });
  html += '<th class="rt-hm-total">Total</th>';
  html += '</tr></thead><tbody>';
  groupKeys.forEach(function(gk) {
    var g = groups[gk];
    html += '<tr>' +
              '<th class="rt-hm-rowlabel" onclick="_anchorOnGroup(\'' + groupKey + '\',\'' + _safeOrg(gk) + '\')">' + gk + '</th>' +
              cellHTML(g.preclinical) +
              cellHTML(g.p1) +
              cellHTML(g.p2) +
              cellHTML(g.p3) +
              cellHTML(g.approved) +
              '<td class="rt-hm-total"><span>' + g._total + '</span></td>' +
            '</tr>';
  });
  html += '</tbody></table></div>';
  html += '</div>';
  return html;
}

// ─── Drug detail side panel ─────────────────────────────────────────────────
// Slide-in panel from the right. Race track stays visible beneath it.
// Closes on X / Esc / outside-click. Populates by delegating to the existing
// loadDrugProfileData flow, which fills #drug-profile-body.
function _openDrugPanel(orgName, drugName) {
  // Stash on state for later use (back-navigation, breadcrumb, etc.).
  if (window.STATE) {
    window.STATE.comp_drug_name = drugName;
    if (orgName) window.STATE.comp_drug_org = orgName;
  }
  _CS.drugName = drugName;
  if (orgName) _CS.drugOrg = orgName;

  var existing = document.getElementById('rt-drug-panel');
  if (existing) existing.remove();
  var existingScrim = document.getElementById('rt-drug-scrim');
  if (existingScrim) existingScrim.remove();

  // Scrim — semi-transparent backdrop that dims the race track.
  var scrim = document.createElement('div');
  scrim.id = 'rt-drug-scrim';
  scrim.className = 'rt-drug-scrim';
  scrim.onclick = _closeDrugPanel;
  document.body.appendChild(scrim);

  // Panel — fixed right, slides in.
  var panel = document.createElement('aside');
  panel.id = 'rt-drug-panel';
  panel.className = 'rt-drug-panel';
  panel.setAttribute('role', 'dialog');
  panel.setAttribute('aria-label', 'Drug detail · ' + drugName);
  panel.innerHTML =
    '<div class="rt-dp-head">' +
      '<div class="rt-dp-eyebrow">Drug detail</div>' +
      '<button class="rt-dp-close" onclick="_closeDrugPanel()" aria-label="Close drug panel">×</button>' +
    '</div>' +
    '<div class="rt-dp-tabs">' +
      '<button class="rt-dp-tab active" data-tab="rd"       onclick="_setDrugPanelTab(\'rd\')">R&amp;D</button>' +
      '<button class="rt-dp-tab"        data-tab="clinical" onclick="_setDrugPanelTab(\'clinical\')">Clinical Development</button>' +
      '<button class="rt-dp-tab"        data-tab="biz"      onclick="_setDrugPanelTab(\'biz\')">Business / Commercial</button>' +
    '</div>' +
    '<div class="rt-dp-body" id="drug-profile-body" data-tab="rd">' +
      '<div class="loader-center" style="padding:60px 0"><div class="loader"></div></div>' +
    '</div>';
  document.body.appendChild(panel);

  // Tag drug-profile sections into tab categories once the existing
  // renderer populates #drug-profile-body. Heuristic: section header text
  // → category. Runs after load completes.
  _scheduleDrugTabTagging();

  // Trigger slide-in transition on next frame.
  requestAnimationFrame(function() {
    scrim.classList.add('visible');
    panel.classList.add('open');
  });

  // Esc to close.
  document.addEventListener('keydown', _onDrugPanelEsc);

  // Populate using the existing drug-profile renderer (writes into #drug-profile-body).
  if (typeof loadDrugProfileData === 'function') {
    loadDrugProfileData(orgName || '', drugName);
  }
}
window._openDrugPanel = _openDrugPanel;

function _closeDrugPanel() {
  var panel = document.getElementById('rt-drug-panel');
  var scrim = document.getElementById('rt-drug-scrim');
  if (panel) panel.classList.remove('open');
  if (scrim) scrim.classList.remove('visible');
  document.removeEventListener('keydown', _onDrugPanelEsc);
  setTimeout(function() {
    if (panel) panel.remove();
    if (scrim) scrim.remove();
  }, 280);
}
window._closeDrugPanel = _closeDrugPanel;

function _onDrugPanelEsc(e) {
  if (e.key === 'Escape') _closeDrugPanel();
}

window._setDrugPanelTab = function(tab) {
  var body = document.getElementById('drug-profile-body');
  if (body) body.setAttribute('data-tab', tab);
  var tabs = document.querySelectorAll('.rt-dp-tab');
  tabs.forEach(function(t) {
    if (t.getAttribute('data-tab') === tab) t.classList.add('active');
    else t.classList.remove('active');
  });
};

// Tag sections inside the drug profile body so the tab strip can show/hide
// them. Existing renderer doesn't emit data-tab-cat, so we classify by
// matching section header text → category.
//
// UR-019 fix (2026-05-05): the original section selector only matched
// `.diq-card / .diq-section / section / .drug-intel-card` but the real
// drug-profile renderer uses `.diq-mid-card`, `.diq-bottom-grid`,
// `.diq-safety-accordion`. None matched, no sections got `data-tab-cat`,
// so clicking "Clinical Development" or "Business / Commercial" tabs
// did nothing visible. Selector now matches the real classes; the
// Competitor Landscape inline div (no card class) is also tagged via a
// header-text fallback so it lands in the Clinical tab.
function _scheduleDrugTabTagging() {
  var attempts = 0;
  function tryTag() {
    attempts++;
    var body = document.getElementById('drug-profile-body');
    if (!body) return;
    var tagged = body.querySelector('[data-tab-cat]');
    if (tagged) return; // already done

    // Match every container shape the renderer uses: legacy .diq-card +
    // .diq-section + .drug-intel-card, plus current .diq-mid-card,
    // .diq-bottom-grid, .diq-safety-accordion, and the KPI strip.
    var sections = body.querySelectorAll(
      '.diq-card, .diq-section, section, .drug-intel-card,' +
      '.diq-mid-card, .diq-bottom-grid, .diq-safety-accordion,' +
      '.diq-kpi-strip'
    );
    var classified = 0;

    function classifyText(t) {
      // Order matters: clinical wins over biz wins over rd because some
      // headings ("Indications" + "exclusivity") could match multiple.
      if (/clinical|trial|indication|competitor|safety|adverse/.test(t)) return 'clinical';
      if (/sponsor|market|approval|business|commercial|brand|sales|exclusivity|patent expiry/.test(t)) return 'biz';
      if (/mechanism|target|moa|patent|ip moat|ip strength|ip\b|modality|pipeline|chemistry|kpi|max phase/.test(t)) return 'rd';
      return 'rd'; // default to R&D so unknown sections always land somewhere
    }

    sections.forEach(function(s) {
      var hdrEl = s.querySelector('h2,h3,h4,.diq-card-title,.diq-section-title,.diq-kpi-tile-label,summary');
      var hdr = hdrEl ? (hdrEl.textContent || '') : (s.textContent || '').slice(0, 80);
      var cat = classifyText(hdr.toLowerCase());
      s.classList.add('diq-section');
      s.setAttribute('data-tab-cat', cat);
      classified++;
    });

    // Header-only fallback: the Competitor Landscape title sits in a bare
    // <div> not wrapped in any card class. Find it by text and tag the
    // wrapping flex parent (.diq-bottom-grid) so the table travels with it.
    if (classified === 0 && attempts < 20) {
      setTimeout(tryTag, 250);
    }
  }
  setTimeout(tryTag, 400);
}

// ─── Build the race track for the current anchor ──────────────────────────
function _buildRaceTrack(anchor, rows) {
  var groupKey = _CV.groupBy === 'auto' ? (_GROUPBY_BY_ANCHOR[anchor.type] || [{k:'org'}])[0].k : _CV.groupBy;
  var filtered = _applyFilters(rows);
  var grouped = _groupRows(filtered, groupKey);

  if (filtered.length === 0) {
    return '<div class="race-track-wrap"><div class="rt-empty">No drugs match the current filters.<br/><b>Try resetting filters or picking a different space.</b></div></div>';
  }

  var html = '<div class="race-track-wrap">';
  // Header bar
  html += '<div class="rt-track-bar">' +
            '<div>' +
              '<div class="rt-track-title">' + _ANCHOR_LABELS[anchor.type] + ' · ' + anchor.label + ' · grouped by ' + (groupKey.charAt(0).toUpperCase() + groupKey.slice(1)) + '</div>' +
              '<div class="rt-track-sub">' + filtered.length + ' drugs · top ' + grouped.length + ' ' + groupKey + ' rows shown</div>' +
            '</div>' +
            '<div class="rt-legend">' +
              (_CV.highlightOrg ? '<span class="lg"><span class="ldot" style="background:var(--amber-warm,#f59e0b)"></span>' + _CV.highlightOrg + '</span>' : '') +
              '<span class="lg"><span class="ldot" style="background:#0d9488"></span>Pharma</span>' +
              '<span class="lg"><span class="ldot" style="background:#7c3aed"></span>Biotech</span>' +
              '<span class="lg"><span class="ldot" style="background:rgba(220,230,240,0.6)"></span>Other</span>' +
            '</div>' +
          '</div>';

  // Phase column headers
  html += '<div class="rt-track-head">' +
            '<div></div>' +
            _RT_PHASES.map(function(ph) {
              return '<div><div class="rt-th-label">' + ph.label + '</div><div class="rt-th-tier">' + ph.tier + '</div></div>';
            }).join('') +
          '</div>';

  // Rows
  html += grouped.map(function(g) {
    return _buildTrackRow(g, groupKey);
  }).join('');

  html += '</div>';
  return html;
}

function _buildTrackRow(group, groupKey) {
  // Bucket the group's rows into the 5 phase columns
  var buckets = _RT_PHASES.map(function(){ return []; });
  group.rows.forEach(function(r) {
    var p = r.max_phase_name;
    for (var i = 0; i < _RT_PHASES.length; i++) {
      if (_RT_PHASES[i].match(p)) { buckets[i].push(r); break; }
    }
  });

  // Cap chips per cell to 2 visible + overflow indicator. With chip vertical
  // padding + gap, 2 chips ≈ 70px which fits the row min-height (90px). Cap
  // of 4 caused stacked overlap (Round 1 screenshot).
  var CAP = 2;
  var cellsHTML = buckets.map(function(rs) {
    if (!rs.length) return '<div class="rt-cell"></div>';
    // Sort by momentum desc, cap.
    rs = rs.slice().sort(function(a, b) { return (b.momentum_score||0) - (a.momentum_score||0); });
    var visible = rs.slice(0, CAP);
    var overflow = rs.length - visible.length;
    var stack = '<div class="rt-stack">' + visible.map(_buildChip).join('');
    if (overflow > 0) stack += '<span class="rt-overflow">+ ' + overflow + ' more</span>';
    stack += '</div>';
    return '<div class="rt-cell">' + stack + '</div>';
  }).join('');

  return '<div class="rt-row">' +
    '<div class="rt-grp" onclick="_anchorOnGroup(\'' + groupKey + '\',\'' + _safeOrg(group.key) + '\')">' +
      '<div class="rt-grp-name">' + group.key + '</div>' +
      '<div class="rt-grp-meta">' + group.rows.length + ' drug' + (group.rows.length === 1 ? '' : 's') + '</div>' +
    '</div>' +
    cellsHTML +
  '</div>';
}

window._anchorOnGroup = function(groupKey, value) {
  // Clicking a row label re-anchors to that level. Org grouping was
  // removed in Phase 4.5 — orgs aren't a navigation axis on this page.
  var typeMap = { disease:'DISEASE', target:'TARGET', modality:'MODALITY', moa:'MOA', ta:'TA' };
  var t = typeMap[groupKey];
  if (!t) return;
  _setAnchor({ type: t, value: value, label: value });
};

// ─── Load data for the anchored space ──────────────────────────────────────
function _loadSpaceData(anchor) {
  if (!window.DASHIQ || typeof window.DASHIQ.rankCompete !== 'function') {
    _CV.rows = [];
    _renderSpace();
    return;
  }
  var params = { limit: 200 };
  if (anchor.type === 'TA') params.ta = anchor.value;
  else if (anchor.type === 'DISEASE') params.disease = anchor.value;
  // Target / Modality / MOA — backend may not filter directly; we filter client-side.

  _CV.loading = true;
  _renderSpace();

  window.DASHIQ.rankCompete(params).then(function(res) {
    var rows = (res && (res.results || res.rows || res)) || [];
    // Client-side filter for non-server-supported anchor types.
    // Audit bug #7: V2 backend's /rank signature ONLY accepts org + disease;
    // params.ta is silently dropped, so we defensively re-filter here.
    // Without this, anchoring on "Oncology" returns all 6,578 orgs across
    // every TA. Match is case-insensitive substring against
    // therapeutic_area / _top_ta / ta to handle the various aliases backend
    // may emit (canonical "ONCOLOGY", or raw "ANTINEOPLASTIC AND
    // IMMUNOMODULATING AGENTS" — see audit bug #19).
    if (anchor.type === 'TA' && anchor.value) {
      var taQ = String(anchor.value).toLowerCase();
      rows = rows.filter(function(r) {
        var hay = (
          (r.therapeutic_area || '') + ' ' +
          (r._top_ta || '') + ' ' +
          (r.ta || '')
        ).toLowerCase();
        return hay.indexOf(taQ) >= 0;
      });
    } else if (anchor.type === 'TARGET') {
      rows = rows.filter(function(r){ return (r.lead_target||'').toLowerCase().indexOf(anchor.value.toLowerCase()) >= 0; });
    } else if (anchor.type === 'MODALITY') {
      rows = rows.filter(function(r){ return (r.lead_modality||assetModality(r.lead_drug_name)) === anchor.value; });
    } else if (anchor.type === 'MOA') {
      rows = rows.filter(function(r){ return (r.lead_moa||'').toLowerCase().indexOf(anchor.value.toLowerCase()) >= 0; });
    }
    _CV.rows = rows;
    _CV.loading = false;
    _renderSpace();
  }).catch(function() {
    _CV.rows = [];
    _CV.loading = false;
    _renderSpace();
  });
}

// ─── Render the space (TA/Disease/Target/Modality/MOA anchor) ──────────────
// Phase 4.1 (2026-05-03): collapsed top into ONE command bar. Was 3 bands
// (breadcrumb + headline+stats + View row + Group/Search/Advanced row) which
// read as multiple separate searches. Now: single command bar with anchor
// pill on the left, Filter button (Quick Views + Advanced inside a popover),
// Group By segmented control, search box, optional Highlight my org.
// Counts that lived in the headline now sit inside the track header bar.
function _renderSpace() {
  var anchor = _CV.anchor;
  if (!anchor) return;
  var el = document.getElementById('comp-content');
  if (!el) return;

  // Cross-intelligence module bar — mounts at top, auto-populated by
  // app.jsx refreshAllXmodBars() looking for #xmod-island-wrap-compete.
  // Added back in Phase 4.2 (2026-05-03) — was lost in the unified rewrite.
  var xmodMount = '<div id="xmod-island-wrap-compete" class="xmod-island-wrap" style="margin-bottom:14px"></div>';

  if (_CV.loading) {
    el.innerHTML = xmodMount + _buildCommandBar(anchor) + '<div class="loader-center" style="padding:80px"><div class="loader"></div></div>';
    if (typeof window.refreshAllXmodBars === 'function') window.refreshAllXmodBars();
    return;
  }

  // Dispatch on view mode. Race = chip grid (default), Table = flat rows,
  // Heatmap = (group × phase) intensity matrix.
  var bodyHTML;
  if (_CV.viewMode === 'table') {
    bodyHTML = _buildTrackTable(anchor, _CV.rows);
  } else if (_CV.viewMode === 'heatmap') {
    bodyHTML = _buildHeatmap(anchor, _CV.rows);
  } else {
    bodyHTML = _buildRaceTrack(anchor, _CV.rows);
  }

  el.innerHTML = xmodMount +
    _buildCommandBar(anchor) +
    _buildStatLine(_CV.rows) +
    bodyHTML;

  // Re-populate the xmod bar after innerHTML wipe.
  if (typeof window.refreshAllXmodBars === 'function') window.refreshAllXmodBars();
}

// ─── Single consolidated command bar ──────────────────────────────────────
function _buildCommandBar(anchor) {
  var typeLabel = _ANCHOR_LABELS[anchor.type] || anchor.type;
  var gbyOpts = _GROUPBY_BY_ANCHOR[anchor.type] || [];
  var resolvedGroupBy = _CV.groupBy === 'auto' && gbyOpts.length ? gbyOpts[0].k : _CV.groupBy;
  var nFilters = _activeFilterCount();
  var viewLabel = (_QUICK_VIEWS.find ? _QUICK_VIEWS.find(function(v){return v.k===_CV.view;}) : null);
  if (!viewLabel) {
    for (var i = 0; i < _QUICK_VIEWS.length; i++) {
      if (_QUICK_VIEWS[i].k === _CV.view) { viewLabel = _QUICK_VIEWS[i]; break; }
    }
  }

  var html = '<div class="rt-cmdbar">';
  // Anchor pill (primary affordance)
  html += '<button class="rt-cb-anchor" onclick="_openAnchorPicker()" title="Re-anchor on a different space">' +
            '<span class="rt-cb-eyebrow">' + typeLabel + '</span>' +
            '<span class="rt-cb-anchor-name">' + (anchor.label || anchor.value) + '</span>' +
            '<span class="rt-cb-chev">▾</span>' +
          '</button>';

  // Vertical divider
  html += '<span class="rt-cb-div"></span>';

  // Filter button (composes Quick Views + Advanced)
  html += '<button class="rt-cb-btn' + (_CV.filterOpen ? ' open' : '') + (nFilters > 0 || _CV.view !== 'all' ? ' has-active' : '') + '" onclick="_toggleFilter()">' +
            '<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M2 4h12M4 8h8M6 12h4" stroke-linecap="round"/></svg>' +
            '<span class="rt-cb-btn-label">' + (viewLabel ? viewLabel.name : 'Filter') + '</span>' +
            (nFilters > 0 ? '<span class="rt-cb-badge">' + nFilters + '</span>' : '') +
            '<span class="rt-cb-chev">▾</span>' +
          '</button>';

  // View mode toggle — Race / Table / Heatmap (Phase 4.6)
  html += '<span class="rt-cb-div"></span>';
  html += '<div class="rt-cb-gby">' +
            '<span class="rt-cb-gby-label">View</span>' +
            '<div class="rt-gby-segctrl">' +
              ['race','table','heatmap'].map(function(m) {
                var act = (_CV.viewMode === m) ? ' active' : '';
                var label = m === 'race' ? 'Race' : (m === 'table' ? 'Table' : 'Heat');
                return '<button class="rt-gby-seg' + act + '" onclick="_setViewMode(\'' + m + '\')">' + label + '</button>';
              }).join('') +
            '</div>' +
          '</div>';

  // Group by segmented control (only if multiple options + applicable mode)
  if (gbyOpts.length > 1) {
    html += '<span class="rt-cb-div"></span>';
    html += '<div class="rt-cb-gby">' +
              '<span class="rt-cb-gby-label">Group</span>' +
              '<div class="rt-gby-segctrl">' +
                gbyOpts.map(function(o) {
                  var act = (resolvedGroupBy === o.k) ? ' active' : '';
                  return '<button class="rt-gby-seg' + act + '" onclick="_setGroupBy(\'' + o.k + '\')">' + o.label + '</button>';
                }).join('') +
              '</div>' +
            '</div>';
  }

  // Search (right-flushed)
  html += '<div class="rt-cb-search">' +
            '<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" style="color:var(--text-faint);flex-shrink:0"><circle cx="7" cy="7" r="5"/><line x1="14" y1="14" x2="11" y2="11" stroke-linecap="round"/></svg>' +
            '<input type="text" placeholder="Filter by drug · target · org…" oninput="_onTrackSearchInput(event)" value="' + (_CV.search || '') + '" />' +
          '</div>';

  html += '</div>'; // /rt-cmdbar

  // Filter popover — single panel containing Quick Views + Advanced filters
  if (_CV.filterOpen) {
    html += _buildFilterPopover();
  }

  return html;
}

// ─── Single filter popover (Quick Views + Advanced filters together) ─────
function _buildFilterPopover() {
  var f = _CV.filters;
  function chipFor(label, key, val) {
    var on = (f[key] && (Array.isArray(f[key]) ? f[key].indexOf(val) >= 0 : f[key] === val));
    return '<span class="rt-adv-chip' + (on ? ' on' : '') + '" onclick="_toggleAdvFilter(\'' + key + '\',\'' + val + '\')">' + label + '</span>';
  }

  var html = '<div class="rt-filter-pop">';

  // Quick views section (radio-style)
  html += '<div class="rt-fp-section">' +
            '<div class="rt-fp-label">Preset view</div>' +
            '<div class="rt-fp-presets">';
  _QUICK_VIEWS.forEach(function(qv) {
    var act = (_CV.view === qv.k) ? ' active' : '';
    html += '<button class="rt-fp-preset' + act + '" onclick="_setQuickView(\'' + qv.k + '\')">' +
              '<span class="rt-fp-preset-name">' + qv.name + '</span>' +
              '<span class="rt-fp-preset-tag">' + qv.tag + '</span>' +
            '</button>';
  });
  html += '</div></div>';

  // Advanced filters in a 2-column grid
  html += '<div class="rt-fp-grid">';
  // Phase
  html += '<div class="rt-fp-section"><div class="rt-fp-label">Phase</div><div class="rt-adv-chips">' +
            chipFor('All','phase','all') +
            chipFor('P1+','phase','P1+') +
            chipFor('P2+','phase','P2+') +
            chipFor('P3+','phase','P3+') +
            chipFor('Approved','phase','approved') +
          '</div></div>';
  // Org category
  html += '<div class="rt-fp-section"><div class="rt-fp-label">Org category</div><div class="rt-adv-chips">' +
            chipFor('Pharma','orgCategory','PHARMA') +
            chipFor('Biotech','orgCategory','BIOTECH') +
            chipFor('Academic','orgCategory','ACADEMIC') +
            chipFor('Other','orgCategory','OTHER') +
          '</div></div>';
  // Modality
  html += '<div class="rt-fp-section"><div class="rt-fp-label">Modality</div><div class="rt-adv-chips">' +
            chipFor('SM','modality','Small Molecule') +
            chipFor('mAb','modality','mAb') +
            chipFor('ADC','modality','ADC') +
            chipFor('CAR-T','modality','Cell Therapy') +
            chipFor('Gene','modality','Gene Therapy') +
          '</div></div>';
  // Highlight my org
  html += '<div class="rt-fp-section"><div class="rt-fp-label">Highlight my org</div>' +
            '<input type="text" placeholder="e.g. Sanofi" value="' + (_CV.highlightOrg || '') + '" oninput="_setHighlightOrg(event.target.value)" class="rt-fp-input" />' +
          '</div>';
  html += '</div>'; // /rt-fp-grid

  // Footer with reset
  html += '<div class="rt-fp-foot">' +
            '<span class="rt-fp-foot-info">' + (_activeFilterCount() > 0 ? _activeFilterCount() + ' active filter' + (_activeFilterCount()===1?'':'s') : 'No filters applied') + '</span>' +
            '<button class="rt-fp-reset" onclick="_resetFilters()">Reset all</button>' +
            '<button class="rt-fp-close" onclick="_toggleFilter()">Done</button>' +
          '</div>';

  html += '</div>';
  return html;
}

window._toggleFilter = function() { _CV.filterOpen = !_CV.filterOpen; renderCompeteIQ(); };

// ─── Drug-anchor mode — delegate to existing drug profile renderer ─────────
function _renderDrugAnchor(anchor) {
  var el = document.getElementById('comp-content');
  if (!el) return;
  // Render anchor breadcrumb + delegate to renderDrugProfileView for the body.
  var headHTML = _buildAnchorBreadcrumb(anchor);
  el.innerHTML = headHTML + '<div id="drug-profile-body"><div class="loader-center"><div class="loader"></div></div></div>';
  // Set drug name in state and call the existing drug profile loader.
  if (window.STATE) {
    window.STATE.comp_drug_name = anchor.value;
    if (anchor._org) window.STATE.comp_drug_org = anchor._org;
  }
  _CS.drugName = anchor.value;
  if (anchor._org) _CS.drugOrg = anchor._org;
  if (typeof loadDrugProfileData === 'function') {
    loadDrugProfileData(anchor._org || '', anchor.value);
  }
}

// ─── Entry point — single unified renderer ────────────────────────────────
function renderCompeteIQ() {
  var panel = document.getElementById('panel-compete');
  if (!panel) return;

  _injectRaceTrackStyles();

  // UR-008 fix: kick off the unanchored option pool fetch on first render
  // so TA / Disease pickers have all 12 TAs / 2k diseases ready by the time
  // the user clicks the anchor pill. Idempotent — re-calls return early.
  _ensureGlobalPool();

  // Remove any leftover legacy subview tabs from earlier versions.
  var oldTabs = document.getElementById('comp-subview-tabs');
  if (oldTabs) oldTabs.remove();

  // Bootstrap content area.
  // Phase 4.2 (2026-05-03): top padding bumped from 16px → 28px so the
  // command bar clears the chrome nav band. Was rendering INTO the header
  // chrome — first row of UI appeared inside the nav strip.
  var el = document.getElementById('comp-content');
  if (!el) {
    el = document.createElement('div');
    el.id = 'comp-content';
    el.style.padding = '28px 22px 24px';
    panel.appendChild(el);
  } else {
    el.style.padding = '28px 22px 24px';
  }

  // Resolve anchor — STATE > _CV > default.
  var anchor = (window.STATE && window.STATE.compete_anchor) || _CV.anchor || _DEFAULT_ANCHOR;
  _CV.anchor = anchor;

  // Phase 4.4: DRUG anchor dropped as a page-level state. Drug detail now
  // opens as a slide-in side panel (_openDrugPanel) over the current space
  // anchor. If a legacy caller passes type:'DRUG', open the panel and keep
  // the underlying space anchor unchanged.
  if (anchor.type === 'DRUG') {
    var spaceBack = _CV._lastSpaceAnchor || _DEFAULT_ANCHOR;
    _CV.anchor = spaceBack;
    if (window.STATE) window.STATE.compete_anchor = spaceBack;
    _openDrugPanel(anchor._org || '', anchor.value);
    var needsLoadSpace = !_CV.rows.length ||
      (_CV._lastAnchorKey !== (spaceBack.type + ':' + spaceBack.value));
    _CV._lastAnchorKey = spaceBack.type + ':' + spaceBack.value;
    if (needsLoadSpace) _loadSpaceData(spaceBack);
    else _renderSpace();
    return;
  }
  // Non-drug anchors: load if rows are stale, else just rerender shell.
  var needsLoad = !_CV.rows.length ||
    (_CV._lastAnchorKey !== (anchor.type + ':' + anchor.value));
  _CV._lastAnchorKey = anchor.type + ':' + anchor.value;
  if (needsLoad) {
    _loadSpaceData(anchor);
  } else {
    _renderSpace();
  }
}

// ─── 12. WINDOW EXPORTS ───────────────────────────────────────────────────────

window.renderCompeteIQ      = renderCompeteIQ;
window.setCompView          = setCompView;
window.loadDrugProfile      = loadDrugProfile;
window.loadDrugProfileData  = loadDrugProfileData;
window.renderDiqTable       = renderDiqTable;
window.openDiqDrawer        = openDiqDrawer;
window.closeEvDrawer        = closeEvDrawer;
window.selectCompOrg        = selectCompOrg;
window.compLandSearch       = compLandSearch;
window.compLandModeSwitch   = compLandModeSwitch;
// Drug Profile picker mode switch (Drug vs Target). Active-class only —
// real filtering happens through the existing input handler.
window.compDrugModeSwitch = function(btn) {
  if (!btn) return;
  var tabs = btn.closest('.diq-search-mode-tabs');
  if (!tabs) return;
  tabs.querySelectorAll('.diq-mode-tab').forEach(function(t) { t.classList.remove('active'); });
  btn.classList.add('active');
  var mode = btn.getAttribute('data-mode');
  var input = btn.closest('.diq-search-row').querySelector('.diq-search-input');
  if (input) {
    input.placeholder = (mode === 'target') ? 'Search targets, genes…' : 'Search drugs, targets…';
    input.value = '';
    input.focus();
  }
};
window.toggleOrgTypeFilter  = toggleOrgTypeFilter;
window.addBreadcrumb        = addBreadcrumb;
window.renderCompLandscape  = renderCompLandscape;
window._closeOrgPanel       = _closeOrgPanel;
