// Recursive MLS — Member Insights (membership BI workspace).
// Admin-facing, customizable dashboards (build/save widgets), member explorer,
// member profile, and office insights. Ported into the canvas as a native screen.
// Reuses window.Icon (Chrome) + charts; data from window.RC_INSIGHTS (insightsData.js).
const { useState: _useStateMI } = React;

// parse a CSS string into a React style object (lets us reuse inline style strings)
function st(s) {
  if (!s) return undefined;
  if (typeof s === "object") return s;
  const o = {};
  s.split(";").forEach((p) => {
    const i = p.indexOf(":"); if (i < 0) return;
    const k = p.slice(0, i).trim(); if (!k) return;
    o[k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = p.slice(i + 1).trim();
  });
  return o;
}
const F = React.Fragment;

class MemberInsights extends React.Component {
  constructor(props) {
    super(props);
    this._KEY = "rc_mi_dashboards_v2";
    this.state = {
      view: "overview", months: 12, memberKey: null, officeKey: null,
      exportOpen: false, statPickerOpen: false, officeMetric: "members",
      ai: { q: "", a: null, loading: false },
      filters: { q: "", offices: [], status: [], tenure: [], prod: [], features: {}, stats: [] },
      sort: { col: "closedSides", dir: "desc" },
      dashboards: this.seedDashboards(), activeDash: "dash-exec", editMode: false,
      builder: { open: false, id: null, step: "type", draft: {} },
    };
  }
  componentDidMount() {
    try { const raw = localStorage.getItem(this._KEY); if (raw) { const p = JSON.parse(raw); if (p && p.dashboards && p.dashboards.length) this.setState({ dashboards: p.dashboards, activeDash: p.activeDash || p.dashboards[0].id }); } } catch (e) {}
  }
  D() { return window.RC_INSIGHTS || {}; }
  persist() { try { localStorage.setItem(this._KEY, JSON.stringify({ dashboards: this.state.dashboards, activeDash: this.state.activeDash })); } catch (e) {} }
  mkId(p) { return p + Math.random().toString(36).slice(2, 8); }

  seedDashboards() {
    const w = (type, o) => Object.assign({ id: "w" + Math.random().toString(36).slice(2, 8), type }, o);
    return [
      { id: "dash-exec", name: "Executive overview", widgets: [
        w("kpi", { metric: "members_active", compare: true, span: 2 }),
        w("kpi", { metric: "closed_per_member", compare: true, span: 2 }),
        w("kpi", { metric: "engaged30", compare: true, span: 2 }),
        w("line", { trend: "new_members", span: 4 }),
        w("gauge", { metric: "crm_adoption", span: 2 }),
        w("bar", { metric: "closedSides", dim: "office", topN: 8, compare: false, chart: "column", span: 4 }),
        w("bar", { metric: "members", dim: "status", chart: "donut", span: 2 }),
        w("line", { trend: "membership_total", span: 3 }),
        w("distribution", { span: 3 }),
        w("adoption", { span: 3 }),
        w("netflow", { span: 3 }),
        w("leaderboard", { subject: "members", metric: "closedSides", topN: 6, span: 3 }),
        w("moves", { span: 3 }),
      ] },
      { id: "dash-prod", name: "Production & performance", widgets: [
        w("kpi", { metric: "total_sides", compare: true, span: 2 }),
        w("kpi", { metric: "avg_listings", compare: true, span: 2 }),
        w("kpi", { metric: "showings_per_member", compare: true, span: 2 }),
        w("bar", { metric: "closedSides", dim: "office", topN: 10, compare: true, chart: "column", span: 4 }),
        w("distribution", { span: 2 }),
        w("bar", { metric: "activeListings", dim: "office", topN: 8, chart: "column", span: 3 }),
        w("bar", { metric: "members", dim: "productionBand", chart: "donut", span: 3 }),
        w("gauge", { metric: "cma_adoption", span: 3 }),
        w("leaderboard", { subject: "members", metric: "closedSides", topN: 8, span: 3 }),
      ] },
      { id: "dash-ret", name: "Retention & recruiting", widgets: [
        w("kpi", { metric: "office_moves30", compare: true, span: 2 }),
        w("kpi", { metric: "engaged30", compare: false, span: 2 }),
        w("kpi", { metric: "licensed_not_member", compare: false, span: 2 }),
        w("netflow", { span: 4 }),
        w("gauge", { metric: "engaged_share", span: 2 }),
        w("bar", { metric: "members", dim: "tenure", chart: "donut", span: 3 }),
        w("moves", { span: 3 }),
        w("corridors", { span: 3 }),
        w("expiring", { span: 3 }),
      ] },
    ];
  }

  // ---------- icons / charts ----------
  ic(name, size, color, stroke) {
    size = size || 16; color = color || "#8a8e88"; stroke = stroke || 2;
    const L = window.lucide && window.lucide.icons;
    const pascal = name.split("-").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
    const node = (L && L[pascal]) || [];
    const kids = node.map((c, i) => { const [tag, attrs] = c; const p = { key: i }; for (const k in attrs) p[k.replace(/-([a-z])/g, (_, ch) => ch.toUpperCase())] = attrs[k]; return React.createElement(tag, p); });
    return React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", style: { display: "inline-block", flex: "none", verticalAlign: "middle" } }, kids);
  }
  spark(values, color, w, h) {
    w = w || 84; h = h || 34; const mn = Math.min(...values), mx = Math.max(...values), sp = (mx - mn) || 1;
    const X = (i) => 1 + (w - 2) * (values.length <= 1 ? 0 : i / (values.length - 1)); const Y = (v) => h - 3 - (h - 6) * ((v - mn) / sp);
    const pts = values.map((v, i) => [X(i), Y(v)]); const d = pts.map((p, i) => (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" ");
    const area = d + " L " + pts[pts.length - 1][0].toFixed(1) + " " + h + " L " + pts[0][0].toFixed(1) + " " + h + " Z"; const id = "sp" + Math.random().toString(36).slice(2, 7); const e = React.createElement;
    return e("svg", { width: w, height: h, viewBox: `0 0 ${w} ${h}`, style: { display: "block" } }, e("defs", null, e("linearGradient", { id, x1: "0", y1: "0", x2: "0", y2: "1" }, e("stop", { offset: "0", stopColor: color, stopOpacity: "0.18" }), e("stop", { offset: "1", stopColor: color, stopOpacity: "0" }))), e("path", { d: area, fill: `url(#${id})` }), e("path", { d, fill: "none", stroke: color, strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }), e("circle", { cx: pts[pts.length - 1][0], cy: pts[pts.length - 1][1], r: "2.4", fill: color }));
  }
  area(series, labels, color, opts) {
    opts = opts || {}; const h = opts.h || 200, w = 640, pl = 46, pr = 16, pt = 14, pb = 28;
    const yfmt = opts.yfmt || ((v) => Math.round(v).toLocaleString()); const warmFrom = opts.warmFrom == null ? 0 : opts.warmFrom;
    const real = series.filter((v, i) => i >= warmFrom); const mn0 = Math.min(...real), mx0 = Math.max(...real), sp = (mx0 - mn0) || mx0 || 1; const mn = mn0 - sp * 0.08, mx = mx0 + sp * 0.14;
    const X = (i) => pl + (w - pl - pr) * (series.length <= 1 ? 0 : i / (series.length - 1)); const Y = (v) => h - pb - (h - pt - pb) * ((v - mn) / ((mx - mn) || 1));
    const pts = []; for (let i = warmFrom; i < series.length; i++) pts.push([X(i), Y(series[i])]);
    const d = pts.map((p, i) => (i ? "L" : "M") + p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" ");
    const areaD = pts.length ? d + " L " + pts[pts.length - 1][0].toFixed(1) + " " + (h - pb) + " L " + pts[0][0].toFixed(1) + " " + (h - pb) + " Z" : "";
    const id = "ar" + Math.random().toString(36).slice(2, 7), hp = "hp" + Math.random().toString(36).slice(2, 7); const e = React.createElement, GRID = "#ece9e0", AXIS = "#a8aca6", ticks = 4, labStep = Math.ceil(labels.length / 7);
    const kids = [e("defs", { key: "d" }, e("linearGradient", { id, x1: "0", y1: "0", x2: "0", y2: "1" }, e("stop", { offset: "0", stopColor: color, stopOpacity: "0.16" }), e("stop", { offset: "1", stopColor: color, stopOpacity: "0" })), e("pattern", { id: hp, width: "7", height: "7", patternUnits: "userSpaceOnUse", patternTransform: "rotate(45)" }, e("rect", { width: "7", height: "7", fill: "#faf9f5" }), e("line", { x1: "0", y1: "0", x2: "0", y2: "7", stroke: "#dcdad2", strokeWidth: "1.5" })))];
    for (let i = 0; i <= ticks; i++) { const v = mn + (mx - mn) * (i / ticks); const yy = Y(v); kids.push(e("line", { key: "g" + i, x1: pl, y1: yy, x2: w - pr, y2: yy, stroke: GRID, strokeWidth: "1" })); kids.push(e("text", { key: "t" + i, x: pl - 8, y: yy + 3, textAnchor: "end", fontSize: "9.5", fill: AXIS, fontFamily: "'Space Mono',monospace" }, yfmt(v))); }
    if (warmFrom > 0) { const wx = X(warmFrom); kids.push(e("rect", { key: "w", x: pl, y: pt, width: Math.max(0, wx - pl), height: h - pt - pb, fill: `url(#${hp})` })); kids.push(e("text", { key: "wt", x: (pl + wx) / 2, y: (pt + h - pb) / 2, textAnchor: "middle", fontSize: "10", fill: "#b0b4ad" }, "Collecting…")); }
    if (opts.area !== false && areaD) kids.push(e("path", { key: "a", d: areaD, fill: `url(#${id})` }));
    if (d) kids.push(e("path", { key: "l", d, fill: "none", stroke: color, strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }));
    if (pts.length) kids.push(e("circle", { key: "c", cx: pts[pts.length - 1][0], cy: pts[pts.length - 1][1], r: "3.2", fill: color, stroke: "#fff", strokeWidth: "1.5" }));
    labels.forEach((l, i) => { if (i % labStep === 0) kids.push(e("text", { key: "x" + i, x: X(i), y: h - 9, textAnchor: "middle", fontSize: "9.5", fill: AXIS, fontFamily: "'Space Mono',monospace" }, l)); });
    return e("svg", { width: "100%", viewBox: `0 0 ${w} ${h}`, style: { display: "block" } }, kids);
  }
  gbar(groups, labels, colors, opts) {
    opts = opts || {}; const h = opts.h || 200, w = 640, pl = 40, pr = 16, pt = 14, pb = 28; const yfmt = opts.yfmt || ((v) => (v >= 1000 ? (v / 1000).toFixed(0) + "k" : Math.round(v)));
    const flat = groups.flatMap((g) => g.values); const mx = Math.max(...flat) * 1.12 || 1; const n = labels.length, bw = (w - pl - pr) / n, inner = bw * 0.58, gap = 3, each = (inner - gap * (groups.length - 1)) / groups.length;
    const e = React.createElement, GRID = "#ece9e0", AXIS = "#a8aca6", ticks = 4, kids = [];
    for (let i = 0; i <= ticks; i++) { const v = (mx / ticks) * i; const yy = h - pb - (h - pt - pb) * (v / mx); kids.push(e("line", { key: "g" + i, x1: pl, y1: yy, x2: w - pr, y2: yy, stroke: GRID, strokeWidth: "1" })); kids.push(e("text", { key: "t" + i, x: pl - 8, y: yy + 3, textAnchor: "end", fontSize: "9.5", fill: AXIS, fontFamily: "'Space Mono',monospace" }, yfmt(v))); }
    labels.forEach((l, i) => { const gx = pl + bw * i + (bw - inner) / 2; groups.forEach((g, gi) => { const bh = (h - pt - pb) * (g.values[i] / mx); kids.push(e("rect", { key: "b" + i + "-" + gi, x: gx + gi * (each + gap), y: h - pb - bh, width: each, height: bh, rx: "1.5", fill: colors[gi] })); }); kids.push(e("text", { key: "x" + i, x: pl + bw * i + bw / 2, y: h - 9, textAnchor: "middle", fontSize: "9.5", fill: AXIS, fontFamily: "'Space Mono',monospace" }, l)); });
    return e("svg", { width: "100%", viewBox: `0 0 ${w} ${h}`, style: { display: "block" } }, kids);
  }
  donut(rows, opts) {
    opts = opts || {}; const e = React.createElement; const size = opts.size || 132, sw = opts.sw || 24, r = (size - sw) / 2, cx = size / 2, cy = size / 2, C = 2 * Math.PI * r;
    const total = rows.reduce((a, b) => a + b.value, 0) || 1; let acc = 0;
    const rings = rows.map((row, i) => { const frac = row.value / total; const seg = e("circle", { key: i, cx, cy, r, fill: "none", stroke: row.color, strokeWidth: sw, strokeDasharray: `${(frac * C).toFixed(2)} ${C.toFixed(2)}`, strokeDashoffset: (-acc * C).toFixed(2), transform: `rotate(-90 ${cx} ${cy})`, style: { cursor: row.pivot ? "pointer" : "default", transition: "opacity .12s" }, onClick: row.pivot ? () => this.pivotTo(row.pivot) : null }); acc += frac; return seg; });
    const svg = e("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, style: { flex: "none" } }, e("circle", { cx, cy, r, fill: "none", stroke: "#f1f0ea", strokeWidth: sw }), rings, e("text", { x: cx, y: cy - 1, textAnchor: "middle", fontSize: 21, fontWeight: 700, fontFamily: "'Space Mono',monospace", fill: "#1d241f" }, opts.centerVal != null ? String(opts.centerVal) : total.toLocaleString()), opts.centerLabel ? e("text", { x: cx, y: cy + 15, textAnchor: "middle", fontSize: 9.5, fill: "#a8aca6" }, opts.centerLabel) : null);
    const legend = e("div", { style: { display: "flex", flexDirection: "column", gap: 6, flex: 1, minWidth: 0 } }, rows.map((row, i) => e("div", { key: i, className: "pv-row", onClick: row.pivot ? () => this.pivotTo(row.pivot) : null, style: { display: "flex", alignItems: "center", gap: 8, cursor: row.pivot ? "pointer" : "default", padding: "3px 4px", borderRadius: 5 } }, e("span", { style: { width: 9, height: 9, borderRadius: 2, background: row.color, flex: "none" } }), e("span", { style: { flex: 1, fontSize: 12, color: "#4a504a", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, row.label), e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 12, fontWeight: 700, color: "#1d241f" } }, (row.value / total * 100).toFixed(0) + "%"))));
    return e("div", { style: { display: "flex", alignItems: "center", gap: 18, flex: 1, justifyContent: "center" } }, svg, legend);
  }
  columnChart(rows, color, opts) {
    opts = opts || {}; const e = React.createElement; const h = opts.h || 195, w = 640, pl = 40, pr = 12, pt = 16, pb = 46;
    const vals = rows.flatMap((r) => [r.value, opts.compare ? (r.prior || 0) : 0]); const mx = Math.max(...vals) * 1.16 || 1; const n = rows.length, bw = (w - pl - pr) / n, cw = Math.min(bw * 0.6, 54);
    const GRID = "#ece9e0", AXIS = "#a8aca6", kids = [];
    for (let i = 0; i <= 4; i++) { const v = mx / 4 * i; const yy = h - pb - (h - pt - pb) * (v / mx); kids.push(e("line", { key: "g" + i, x1: pl, y1: yy, x2: w - pr, y2: yy, stroke: GRID, strokeWidth: 1 })); kids.push(e("text", { key: "t" + i, x: pl - 6, y: yy + 3, textAnchor: "end", fontSize: 9.5, fill: AXIS, fontFamily: "'Space Mono',monospace" }, v >= 1000 ? (v / 1000).toFixed(0) + "k" : Math.round(v))); }
    rows.forEach((r, i) => { const cx0 = pl + bw * i + bw / 2; const bh = (h - pt - pb) * (r.value / mx); const x = cx0 - cw / 2;
      if (opts.compare && r.prior) { const ph = (h - pt - pb) * (r.prior / mx); kids.push(e("rect", { key: "p" + i, x: x - 3, y: h - pb - ph, width: cw, height: ph, rx: 2, fill: "#e2e7e3" })); }
      kids.push(e("rect", { key: "b" + i, x: opts.compare && r.prior ? x + 3 : x, y: h - pb - bh, width: cw, height: bh, rx: 2, fill: r.color || color, style: { cursor: r.pivot ? "pointer" : "default" }, onClick: r.pivot ? () => this.pivotTo(r.pivot) : null }));
      kids.push(e("text", { key: "v" + i, x: cx0, y: h - pb - bh - 5, textAnchor: "middle", fontSize: 9.5, fontWeight: 700, fill: "#6b706a", fontFamily: "'Space Mono',monospace" }, r.value >= 1000 ? (r.value / 1000).toFixed(1) + "k" : r.value));
      const lab = r.label.length > 13 ? r.label.slice(0, 12) + "…" : r.label; kids.push(e("text", { key: "x" + i, x: cx0, y: h - pb + 15, textAnchor: "end", fontSize: 9, fill: AXIS, transform: `rotate(-32 ${cx0} ${h - pb + 15})` }, lab)); });
    return e("svg", { width: "100%", viewBox: `0 0 ${w} ${h}`, style: { display: "block" } }, kids);
  }
  gaugeChart(value, opts) {
    opts = opts || {}; const e = React.createElement; const min = opts.min || 0, max = opts.max || 100, h = opts.h || 150, w = 240, cx = w / 2, cy = h - 14, r = 90;
    const frac = Math.max(0, Math.min(1, (value - min) / (max - min))); const polar = (a) => [cx + r * Math.cos(a), cy - r * Math.sin(a)]; const [sx, sy] = polar(Math.PI), [ex, ey] = polar(0), [px, py] = polar(Math.PI * (1 - frac)); const color = opts.color || "#006747";
    return e("svg", { width: "100%", viewBox: `0 0 ${w} ${h}`, style: { display: "block", maxWidth: 240, margin: "0 auto" } }, e("path", { d: `M ${sx} ${sy} A ${r} ${r} 0 0 1 ${ex} ${ey}`, fill: "none", stroke: "#ece9e0", strokeWidth: 14, strokeLinecap: "round" }), e("path", { d: `M ${sx} ${sy} A ${r} ${r} 0 0 1 ${px} ${py}`, fill: "none", stroke: color, strokeWidth: 14, strokeLinecap: "round" }), e("circle", { cx: px, cy: py, r: 6, fill: "#fff", stroke: color, strokeWidth: 3 }), e("text", { x: cx, y: cy - 24, textAnchor: "middle", fontSize: 30, fontWeight: 700, fill: "#1d241f", fontFamily: "'Space Mono',monospace", letterSpacing: "-1" }, opts.label != null ? opts.label : value), e("text", { x: cx, y: cy - 4, textAnchor: "middle", fontSize: 10.5, fill: "#a8aca6" }, opts.sub || ""));
  }
  kpiSpark(m) { let seed = 0; for (const c of m.key) seed += c.charCodeAt(0); const down = m.good === false; const arr = []; for (let i = 0; i < 9; i++) { const t = i / 8; arr.push((down ? 1 - 0.35 * t : 0.6 + 0.4 * t) + Math.sin((i + seed) * 1.1) * 0.05); } const col = m.warming ? "#9a6a12" : m.warn ? "#c0612f" : (m.good === false ? "#c0612f" : "#1f8a5b"); return this.spark(arr, col, 96, 36); }
  gaugeMetrics() { return [{ key: "crm_adoption", label: "CRM adoption", sub: "share of members", val: 71, min: 0, max: 100, color: "#006747", note: "71% actively use CRM", pivot: { features: { crm: "has" } } }, { key: "cma_adoption", label: "CMA adoption", sub: "share of members", val: 44, min: 0, max: 100, color: "#2a6f8a", note: "44% have run a CMA", pivot: { features: { cma: "has" } } }, { key: "showings_adoption", label: "Showings adoption", sub: "share of members", val: 64, min: 0, max: 100, color: "#006747", note: "64% use Showings", pivot: { features: { showings: "has" } } }, { key: "engaged_share", label: "Engaged share", sub: "signed in · 30d", val: 69, min: 0, max: 100, color: "#e0a020", note: "warming — approximate", warming: true }]; }

  // ---------- navigation ----------
  go(view) { this.setState({ view, exportOpen: false, statPickerOpen: false }); }
  goOverview() { this.go("overview"); }
  goMembers() { this.go("members"); }
  openMember(key) { this.setState({ view: "profile", memberKey: key }); }
  openOffice(key) { this.setState({ view: "offices", officeKey: key }); this.setFilter("offices", [key]); }
  pivotTo(patch) { this.setState((s) => ({ view: "members", filters: { q: "", offices: [], status: [], tenure: [], prod: [], features: {}, stats: [], ...patch } })); }
  setMonths(m) { this.setState({ months: m }); }

  // ---------- filters ----------
  setFilter(field, val) { this.setState((s) => ({ filters: { ...s.filters, [field]: val } })); }
  toggleArr(field, val) { this.setState((s) => { const cur = s.filters[field]; const nx = cur.includes(val) ? cur.filter((x) => x !== val) : [...cur, val]; return { filters: { ...s.filters, [field]: nx } }; }); }
  setFeature(key, mode) { this.setState((s) => { const f = { ...s.filters.features }; if (f[key] === mode) delete f[key]; else f[key] = mode; return { filters: { ...s.filters, features: f } }; }); }
  addStat(col, label) { this.setState((s) => ({ statPickerOpen: false, filters: { ...s.filters, stats: [...s.filters.stats, { col, label, min: "", max: "" }] } })); }
  setStat(i, field, v) { this.setState((s) => { const st2 = s.filters.stats.slice(); st2[i] = { ...st2[i], [field]: v }; return { filters: { ...s.filters, stats: st2 } }; }); }
  removeStat(i) { this.setState((s) => ({ filters: { ...s.filters, stats: s.filters.stats.filter((_, j) => j !== i) } })); }
  clearFilters() { this.setState({ filters: { q: "", offices: [], status: [], tenure: [], prod: [], features: {}, stats: [] } }); }
  applySaved(patch) { this.setState((s) => ({ view: "members", filters: { q: "", offices: [], status: [], tenure: [], prod: [], features: {}, stats: [], ...patch } })); }
  setSort(col) { this.setState((s) => ({ sort: { col, dir: s.sort.col === col && s.sort.dir === "desc" ? "asc" : "desc" } })); }
  setOfficeMetric(m) { this.setState({ officeMetric: m }); }
  openExport() { this.setState({ exportOpen: true }); }
  closeExport() { this.setState({ exportOpen: false }); }
  toggleStatPicker() { this.setState((s) => ({ statPickerOpen: !s.statPickerOpen })); }

  // ---------- dashboard ops ----------
  activeDashObj() { return this.state.dashboards.find((d) => d.id === this.state.activeDash) || this.state.dashboards[0]; }
  updateActive(fn) { this.setState((s) => ({ dashboards: s.dashboards.map((d) => (d.id === s.activeDash ? fn(d) : d)) }), () => this.persist()); }
  setActiveDash(id) { this.setState({ activeDash: id, editMode: false }, () => this.persist()); }
  toggleEdit() { this.setState((s) => ({ editMode: !s.editMode })); }
  addDashboard() { const id = this.mkId("dash-"); this.setState((s) => ({ dashboards: [...s.dashboards, { id, name: "New dashboard", widgets: [] }], activeDash: id, editMode: true }), () => this.persist()); }
  duplicateDashboard() { const cur = this.activeDashObj(); const id = this.mkId("dash-"); const copy = { id, name: cur.name + " (copy)", widgets: cur.widgets.map((w) => ({ ...w, id: this.mkId("w") })) }; this.setState((s) => ({ dashboards: [...s.dashboards, copy], activeDash: id }), () => this.persist()); }
  deleteDashboard() { this.setState((s) => { if (s.dashboards.length <= 1) return {}; const nx = s.dashboards.filter((d) => d.id !== s.activeDash); return { dashboards: nx, activeDash: nx[0].id }; }, () => this.persist()); }
  renameDashboard(v) { this.updateActive((d) => ({ ...d, name: v })); }
  addWidget(spec) { this.updateActive((d) => ({ ...d, widgets: [...d.widgets, { ...spec, id: this.mkId("w") }] })); }
  updateWidget(id, spec) { this.updateActive((d) => ({ ...d, widgets: d.widgets.map((w) => (w.id === id ? { ...spec, id } : w)) })); }
  removeWidget(id) { this.updateActive((d) => ({ ...d, widgets: d.widgets.filter((w) => w.id !== id) })); }
  duplicateWidget(id) { this.updateActive((d) => { const i = d.widgets.findIndex((w) => w.id === id); if (i < 0) return d; const copy = { ...d.widgets[i], id: this.mkId("w") }; const nx = d.widgets.slice(); nx.splice(i + 1, 0, copy); return { ...d, widgets: nx }; }); }
  moveWidget(id, dir) { this.updateActive((d) => { const i = d.widgets.findIndex((w) => w.id === id); const j = i + dir; if (i < 0 || j < 0 || j >= d.widgets.length) return d; const nx = d.widgets.slice(); const t = nx[i]; nx[i] = nx[j]; nx[j] = t; return { ...d, widgets: nx }; }); }
  resizeWidget(id) { const order = [2, 3, 4, 6]; this.updateActive((d) => ({ ...d, widgets: d.widgets.map((w) => { if (w.id !== id) return w; const idx = order.indexOf(w.span || 3); return { ...w, span: order[(idx + 1) % order.length] }; }) })); }

  // ---------- widget builder ----------
  openBuilderNew() { this.setState({ editMode: true, builder: { open: true, id: null, step: "type", draft: { span: 3 } } }); }
  openEditWidget(id) { const w = this.activeDashObj().widgets.find((x) => x.id === id); if (w) this.setState({ builder: { open: true, id, step: "config", draft: { ...w } } }); }
  cancelBuilder() { this.setState({ builder: { open: false, id: null, step: "type", draft: {} } }); }
  backToType() { this.setState((s) => ({ builder: { ...s.builder, step: "type" } })); }
  chooseType(type) {
    const def = { kpi: { metric: "members_active", compare: true }, bar: { metric: "closedSides", dim: "office", topN: 8, compare: false, chart: "column" }, line: { trend: "new_members" }, leaderboard: { subject: "members", metric: "closedSides", topN: 6 }, distribution: {}, gauge: { metric: "crm_adoption" }, adoption: {}, netflow: {}, moves: {}, corridors: {}, licensing: {}, expiring: {} }[type] || {};
    const spanDef = (type === "kpi") ? 2 : 3;
    this.setState((s) => ({ builder: { ...s.builder, step: "config", draft: { type, span: spanDef, ...def } } }));
  }
  setDraft(field, v) { this.setState((s) => ({ builder: { ...s.builder, draft: { ...s.builder.draft, [field]: v } } })); }
  saveBuilder() { const d = this.state.builder.draft; const spec = { ...d, title: d.title || this.titleFor(d) }; if (this.state.builder.id) this.updateWidget(this.state.builder.id, spec); else this.addWidget(spec); this.cancelBuilder(); }

  metricCatalog() {
    return [
      { key: "members_active", label: "Active members", fmt: "int", val: 46204, delta: "+2.4% YoY", good: true, window: "current", group: "Membership", pivot: { status: ["active"] } },
      { key: "members_new30", label: "New members", fmt: "int", val: 312, delta: "+18 vs prior", good: true, window: "30d", group: "Membership", pivot: { tenure: ["new"] } },
      { key: "engaged30", label: "Engaged members", fmt: "int", val: 31880, delta: "warming", warming: true, window: "30d", group: "Membership", pivot: {} },
      { key: "office_moves30", label: "Office moves", fmt: "int", val: 214, delta: "warming", warming: true, window: "30d", group: "Membership", pivot: {} },
      { key: "closed_per_member", label: "Closed sides / member", fmt: "dec", val: 6.8, delta: "+0.3 YoY", good: true, window: "12m", group: "Production", pivot: { prod: ["b4", "b5"] } },
      { key: "showings_per_member", label: "Showings / member", fmt: "dec", val: 4.2, delta: "+0.6 QoQ", good: true, window: "30d", group: "Production", pivot: { features: { showings: "has" } } },
      { key: "avg_listings", label: "Avg listings / member", fmt: "dec", val: 4.1, delta: "+0.4 vs prior 12m", good: true, window: "12m", group: "Production", pivot: {} },
      { key: "total_sides", label: "Total closed sides", fmt: "int", val: 314200, delta: "+6.1% YoY", good: true, window: "12m", group: "Production", pivot: {} },
      { key: "total_listings", label: "Active listings", fmt: "int", val: 24380, delta: "+3.2%", good: true, window: "current", group: "Production", pivot: {} },
      { key: "total_showings", label: "Showings held", fmt: "int", val: 124600, delta: "+5.4%", good: true, window: "30d", group: "Production", pivot: {} },
      { key: "total_offers", label: "Offers submitted", fmt: "int", val: 38600, delta: "+2.1%", good: true, window: "30d", group: "Production", pivot: {} },
      { key: "cma_adoption", label: "CMA adoption", fmt: "pct", val: 44, delta: "+3 pts", good: true, window: "current", group: "Engagement", pivot: { features: { cma: "has" } } },
      { key: "expiring90", label: "Licenses expiring 90d", fmt: "int", val: 2860, delta: "attention", warn: true, window: "90d", group: "Licensing", pivot: {} },
      { key: "licensed_not_member", label: "Licensed · not member", fmt: "int", val: 1810, delta: "pipeline", window: "current", group: "Licensing", pivot: {} },
    ];
  }
  fmtVal(m) { const v = m.val; return m.fmt === "int" ? v.toLocaleString() : m.fmt === "dec" ? v.toFixed(1) : m.fmt === "pct" ? v + "%" : v; }
  dims() { return [{ key: "office", label: "Office" }, { key: "tenure", label: "Tenure band" }, { key: "status", label: "Status" }, { key: "productionBand", label: "Production band" }, { key: "license", label: "License status" }]; }
  barMetrics() { return [{ key: "members", label: "Members (count)" }, { key: "closedSides", label: "Closed sides" }, { key: "activeListings", label: "Active listings" }, { key: "showings", label: "Showings" }]; }
  trendCat() { const D = this.D(), CC = D.CHART_COLORS, S = D.series; return [{ key: "new_members", label: "New members / month", series: S.newMembersMonthly, color: CC.GREEN }, { key: "membership_total", label: "Membership total", series: S.membershipTotal, color: CC.TEAL, warmFrom: 10 }, { key: "showings", label: "Showings held / month", series: S.showingsHeld, color: CC.GREEN }, { key: "offers", label: "Offers / month", series: S.offersMonthly, color: CC.ORANGE }]; }
  lbMetrics(subject) { return subject === "offices" ? [{ key: "members", label: "Members" }, { key: "closedSides", label: "Closed sides" }, { key: "activeListings", label: "Active listings" }, { key: "showings", label: "Showings" }] : [{ key: "closedSides", label: "Closed sides 12m" }, { key: "activeListings", label: "Active listings" }, { key: "showingsHosted", label: "Showings hosted" }, { key: "offers", label: "Offers 12m" }]; }

  breakdown(metricKey, dimKey) {
    const D = this.D(); let rows = [];
    if (dimKey === "office") { const f = ["members", "closedSides", "activeListings", "showings"].includes(metricKey) ? metricKey : "members"; rows = D.OFFICES.map((o) => ({ label: o.name, value: o[f], pivot: { offices: [o.key] } })); }
    else if (dimKey === "productionBand") rows = D.closedDistribution.map((d) => ({ label: d.band, value: d.count, pivot: { prod: [d.key] } }));
    else if (dimKey === "license") rows = D.licenseStatus.map((l) => ({ label: l.label, value: l.count, color: l.color, pivot: {} }));
    else if (dimKey === "tenure") rows = [{ label: "New (<1 yr)", value: 8240, pivot: { tenure: ["new"] } }, { label: "Established (1–5 yr)", value: 22380, pivot: { tenure: ["established"] } }, { label: "Veteran (5 yr+)", value: 17610, pivot: { tenure: ["veteran"] } }];
    else if (dimKey === "status") rows = [{ label: "Active", value: 43120, pivot: { status: ["active"] } }, { label: "Pending", value: 2480, pivot: { status: ["pending"] } }, { label: "Inactive", value: 2210, pivot: { status: ["inactive"] } }, { label: "Suspended", value: 420, pivot: { status: ["suspended"] } }];
    return rows.slice().sort((a, b) => b.value - a.value);
  }
  titleFor(d) {
    const cat = this.metricCatalog(); const dl = (k) => (this.dims().find((x) => x.key === k) || {}).label || k; const bm = (k) => (this.barMetrics().find((x) => x.key === k) || {}).label || k;
    switch (d.type) {
      case "kpi": return (cat.find((m) => m.key === d.metric) || {}).label || "Metric";
      case "bar": return bm(d.metric) + " by " + dl(d.dim);
      case "line": return (this.trendCat().find((t) => t.key === d.trend) || {}).label || "Trend";
      case "leaderboard": { const m = (this.lbMetrics(d.subject).find((x) => x.key === d.metric) || {}).label || ""; return "Top " + (d.topN || 6) + " " + (d.subject === "offices" ? "offices" : "members") + " · " + m; }
      case "gauge": return (this.gaugeMetrics().find((m) => m.key === d.metric) || {}).label || "Gauge";
      case "distribution": return "Closed-sides distribution";
      case "adoption": return "Feature adoption";
      case "netflow": return "Office net flow";
      case "moves": return "Recent moves";
      case "corridors": return "Top corridors";
      case "licensing": return "License status";
      case "expiring": return "Licenses expiring";
      default: return "Widget";
    }
  }
  statusStyle(status) { const tones = { active: ["#e8f4ee", "#1f8a5b"], pending: ["#fbf2dc", "#9a6a12"], inactive: ["#f0efe9", "#6b706a"], suspended: ["#faece3", "#c0612f"] }; const t = tones[status] || tones.inactive; return `display:inline-flex;align-items:center;font-size:10px;font-weight:600;letter-spacing:.2px;padding:2px 7px;border-radius:999px;background:${t[0]};color:${t[1]};text-transform:capitalize`; }
  filtered() {
    const D = this.D(); const f = this.state.filters;
    let rows = (D.MEMBERS || []).filter((m) => {
      if (f.q) { const q = f.q.toLowerCase(); if (!(m.name.toLowerCase().includes(q) || m.mls.toLowerCase().includes(q))) return false; }
      if (f.offices.length && !f.offices.includes(m.officeKey)) return false;
      if (f.status.length && !f.status.includes(m.status)) return false;
      if (f.tenure.length && !f.tenure.includes(m.tenure)) return false;
      if (f.prod.length) { const band = D.PRODUCTION_BANDS.find((b) => m.closedSides >= b.lo && m.closedSides <= b.hi); if (!band || !f.prod.includes(band.key)) return false; }
      for (const k in f.features) { const md = f.features[k]; if (md === "has" && !m.adoption.includes(k)) return false; if (md === "hasnt" && m.adoption.includes(k)) return false; }
      for (const st2 of f.stats) { const v = m[st2.col]; if (st2.min !== "" && st2.min != null && v < Number(st2.min)) return false; if (st2.max !== "" && st2.max != null && v > Number(st2.max)) return false; }
      return true;
    });
    const { col, dir } = this.state.sort; const mul = dir === "asc" ? 1 : -1; const key = col === "tenure" ? "joined" : col === "lastSeen" ? "closedSides" : col;
    rows = rows.slice().sort((a, b) => { const av = a[key], bv = b[key]; if (typeof av === "string") return av.localeCompare(bv) * mul; return (av - bv) * mul; });
    return rows;
  }
  async askAI(q) {
    const D = this.D(); const ques = (q || this.state.ai.q || "").trim(); if (!ques) return;
    this.setState({ ai: { q: ques, a: null, loading: true } });
    const fb = (D.aiSamples.find((s) => s.q === ques) || D.aiSamples[0]).a;
    try {
      if (window.claude && window.claude.complete) { const txt = await window.claude.complete(`You are a membership analytics assistant for an MLS executive. The Miami MLS has ${D.TOTAL_MEMBERS} members. Answer in 2-4 concise sentences with specific, realistic numbers. Question: ${ques}`); this.setState({ ai: { q: ques, a: txt || fb, loading: false } }); }
      else { await new Promise((r) => setTimeout(r, 700)); this.setState({ ai: { q: ques, a: fb, loading: false } }); }
    } catch (e) { this.setState({ ai: { q: ques, a: fb, loading: false } }); }
  }
  resetAI() { this.setState({ ai: { q: "", a: null, loading: false } }); }

  // ---------- widget renderer ----------
  renderWidget(w) {
    const D = this.D(), CC = D.CHART_COLORS, e = React.createElement; const nfmt = (n) => n.toLocaleString();
    const spanCols = w.span || 3; const kpiCard = w.type === "kpi";
    const cardStyle = `background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,${this.state.editMode ? ".12" : ".07"});padding:15px 18px 16px;grid-column:span ${spanCols};display:flex;flex-direction:column;min-height:${kpiCard ? 120 : 210}px${this.state.editMode ? ";outline:1px dashed #cfe0d7;outline-offset:-1px" : ""}`;
    const base = { id: w.id, span: spanCols, cardStyle, title: w.title || this.titleFor(w), subtitle: "", warming: false,
      onEdit: () => this.openEditWidget(w.id), onDup: () => this.duplicateWidget(w.id), onRemove: () => this.removeWidget(w.id), onResize: () => this.resizeWidget(w.id), onLeft: () => this.moveWidget(w.id, -1), onRight: () => this.moveWidget(w.id, 1) };
    const barList = (rows, opts) => { opts = opts || {}; const mx = Math.max(...rows.map((r) => r.value)) || 1; return e("div", { style: { display: "flex", flexDirection: "column", gap: 9, flex: 1, justifyContent: "center" } }, rows.map((r, i) => e("div", { key: i, className: "pv-row", onClick: r.pivot ? () => this.pivotTo(r.pivot) : null, style: { display: "flex", alignItems: "center", gap: 10, padding: "3px 4px", borderRadius: 6, cursor: r.pivot ? "pointer" : "default" } },
      e("span", { style: { width: 116, flex: "none", fontSize: 12, color: "#4a504a", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, r.label),
      e("div", { style: { flex: 1, position: "relative", height: 16, background: "#f1f0ea", borderRadius: 4, overflow: "hidden" } },
        opts.compare && r.prior ? e("div", { style: { position: "absolute", inset: 0, width: (r.prior / mx * 100) + "%", background: "#dfe7e2", borderRadius: 4 } }) : null,
        e("div", { style: { position: "absolute", inset: 0, width: (r.value / mx * 100) + "%", background: r.color || CC.GREEN, borderRadius: 4 } })),
      e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 12.5, fontWeight: 700, width: 58, textAlign: "right", flex: "none" } }, nfmt(r.value))))); };

    if (w.type === "kpi") {
      const m = this.metricCatalog().find((x) => x.key === w.metric) || this.metricCatalog()[0]; base.warming = !!m.warming;
      const good = m.good === true; const dcolor = m.warming ? "#9a6a12" : m.warn ? "#c0612f" : (good ? "#1f8a5b" : "#8a8e88");
      const deltaEl = w.compare ? e("div", { style: { fontSize: 12, fontWeight: 600, marginTop: 8, color: dcolor, display: "inline-flex", alignItems: "center", gap: 3 } }, good ? this.ic("arrow-up-right", 12, dcolor) : null, m.delta, e("span", { style: { color: "#bcbfba", fontWeight: 400, marginLeft: 3 } }, m.window)) : e("div", { style: { fontSize: 11, color: "#bcbfba", marginTop: 8 } }, m.window);
      base.node = e("div", { className: "pivot", tabIndex: 0, onClick: () => this.pivotTo(m.pivot || {}), style: { flex: 1, display: "flex", flexDirection: "column", justifyContent: "center", cursor: "pointer", margin: "-4px", padding: "4px", borderRadius: 6 } },
        e("div", { style: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 10 } },
          e("div", { style: { minWidth: 0 } }, e("div", { style: { fontFamily: "'Space Mono',monospace", fontSize: 28, fontWeight: 700, letterSpacing: "-1px", color: "#1d241f", lineHeight: 1 } }, this.fmtVal(m)), deltaEl),
          e("div", { style: { flex: "none" } }, this.kpiSpark(m))),
        e("div", { style: { fontSize: 11, fontWeight: 600, color: "#006747", marginTop: 12, display: "flex", alignItems: "center", gap: 4 } }, "View members ", this.ic("arrow-right", 12, "#006747")));
      return base;
    }
    if (w.type === "bar") {
      const palette = ["#006747", "#2a6f8a", "#c0612f", "#1f8a5b", "#3f8ba3", "#e0a020", "#8ab3a1", "#9a6a12", "#4d9377", "#d08a58"];
      const rows = this.breakdown(w.metric, w.dim).slice(0, w.topN || 8).map((r, i) => ({ ...r, color: r.color || palette[i % palette.length], prior: Math.round(r.value * (0.86 + (i % 4) * 0.035)) }));
      const ml = (this.barMetrics().find((x) => x.key === w.metric) || {}).label; const dl = ((this.dims().find((x) => x.key === w.dim) || {}).label || "").toLowerCase(); const chart = w.chart || "column";
      base.subtitle = ml + " · by " + dl + (w.compare && chart !== "donut" ? " · vs prior" : "");
      if (chart === "donut") { const total = rows.reduce((a, b) => a + b.value, 0); base.node = this.donut(rows, { centerVal: total >= 1000 ? (total / 1000).toFixed(1) + "k" : total, centerLabel: ml }); }
      else if (chart === "bar") base.node = barList(rows, { compare: w.compare });
      else base.node = e("div", { style: { flex: 1, display: "flex", alignItems: "center" } }, this.columnChart(rows, "#006747", { h: kpiCard ? 120 : 195, compare: w.compare }));
      return base;
    }
    if (w.type === "gauge") {
      const gm = this.gaugeMetrics().find((x) => x.key === w.metric) || this.gaugeMetrics()[0]; base.warming = !!gm.warming; base.subtitle = gm.sub;
      base.node = e("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 2 } }, this.gaugeChart(gm.val, { min: gm.min, max: gm.max, label: gm.val + "%", sub: gm.label, color: gm.color }), e("div", { style: { fontSize: 12, color: "#8a8e88", marginTop: -6 } }, gm.note));
      return base;
    }
    if (w.type === "line") {
      const t = this.trendCat().find((x) => x.key === w.trend) || this.trendCat()[0]; const take = Math.min(this.state.months, 12); const start = 12 - take; const labels = D.MONTHS.slice(start); const ser = t.series.slice(start);
      const localWarm = t.warmFrom != null ? Math.max(0, t.warmFrom - start) : 0; base.warming = t.warmFrom != null; base.subtitle = "trailing " + this.state.months + " mo";
      base.node = e("div", { style: { flex: 1, display: "flex", alignItems: "center" } }, this.area(ser, labels, t.color, { h: kpiCard ? 120 : 180, warmFrom: localWarm, yfmt: (v) => (v >= 1000 ? (v / 1000).toFixed(1) + "k" : Math.round(v)) }));
      return base;
    }
    if (w.type === "leaderboard") {
      base.subtitle = "by " + ((this.lbMetrics(w.subject).find((x) => x.key === w.metric) || {}).label || "");
      if (w.subject === "offices") { const arr = D.OFFICES.slice().sort((a, b) => b[w.metric] - a[w.metric]).slice(0, w.topN || 8); const mx = arr[0][w.metric]; base.node = e("div", { style: { display: "flex", flexDirection: "column", gap: 8, flex: 1, justifyContent: "center" } }, arr.map((o, i) => e("div", { key: i, className: "pv-row", onClick: () => this.openOffice(o.key), style: { display: "flex", alignItems: "center", gap: 10, padding: "5px 4px", borderRadius: 6, cursor: "pointer" } }, e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 12, fontWeight: 700, color: "#c0c3bd", width: 16 } }, i + 1), e("span", { style: { flex: 1, fontSize: 12.5, fontWeight: 500, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, o.name), e("div", { style: { width: 70, height: 6, background: "#eef0ec", borderRadius: 3, overflow: "hidden" } }, e("div", { style: { height: "100%", width: (o[w.metric] / mx * 100) + "%", background: CC.GREEN, borderRadius: 3 } })), e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 12.5, fontWeight: 700, width: 52, textAlign: "right" } }, nfmt(o[w.metric]))))); }
      else { const arr = D.MEMBERS.slice().sort((a, b) => b[w.metric] - a[w.metric]).slice(0, w.topN || 6); const mx = arr[0][w.metric] || 1; base.node = e("div", { style: { display: "flex", flexDirection: "column", gap: 6, flex: 1, justifyContent: "center" } }, arr.map((m, i) => e("div", { key: i, className: "pv-row", onClick: () => this.openMember(m.key), style: { display: "flex", alignItems: "center", gap: 11, padding: "6px 4px", borderRadius: 7, cursor: "pointer" } }, e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 12, fontWeight: 700, color: "#c0c3bd", width: 16 } }, i + 1), e("div", { style: { width: 28, height: 28, borderRadius: "50%", background: "#006747", color: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 600, flex: "none" } }, m.initials), e("div", { style: { flex: 1, minWidth: 0 } }, e("div", { style: { fontSize: 13, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, m.name), e("div", { style: { fontSize: 11, color: "#a8aca6", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, m.office)), e("div", { style: { width: 64, height: 6, background: "#eef0ec", borderRadius: 3, overflow: "hidden", flex: "none" } }, e("div", { style: { height: "100%", width: (m[w.metric] / mx * 100) + "%", background: CC.GREEN, borderRadius: 3 } })), e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 13, fontWeight: 700, width: 28, textAlign: "right" } }, m[w.metric])))); }
      return base;
    }
    if (w.type === "distribution") {
      base.subtitle = "members by closed sides (12m)"; const distMax = Math.max(...D.closedDistribution.map((d) => d.count)); const colors = ["#c9c6bc", "#8ab3a1", "#4d9377", "#1f8a5b", "#0d7550", "#006747"];
      base.node = e("div", { style: { display: "flex", gap: 8, alignItems: "flex-end", flex: 1, minHeight: 150 } }, D.closedDistribution.map((d, i) => e("button", { key: i, className: "pivot", onClick: () => this.pivotTo({ prod: [d.key] }), style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-end", background: "transparent", border: 0, cursor: "pointer", fontFamily: "inherit", borderRadius: 4 } }, e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 11, fontWeight: 700, color: "#6b706a", marginBottom: 4 } }, (d.count / 1000).toFixed(1) + "k"), e("div", { style: { width: "100%", height: Math.round(24 + (d.count / distMax) * 118), borderRadius: "3px 3px 0 0", background: colors[i] } }), e("div", { style: { fontSize: 10.5, fontWeight: 600, color: "#6b706a", marginTop: 7 } }, d.band))));
      return base;
    }
    if (w.type === "adoption") { base.subtitle = "% using each feature — click to drill"; const colors = ["#006747", "#0d7550", "#2a6f8a", "#3f8ba3", "#c0612f", "#d08a58"]; base.node = e("div", { style: { display: "flex", flexDirection: "column", gap: 10, flex: 1, justifyContent: "center" } }, D.adoptionRows.map((a, i) => e("div", { key: i, className: "pv-row", onClick: () => this.pivotTo({ features: { [a.key]: "hasnt" } }), style: { display: "flex", alignItems: "center", gap: 12, padding: "3px 4px", borderRadius: 6, cursor: "pointer" } }, e("span", { style: { width: 100, flex: "none", fontSize: 12.5, color: "#4a504a", fontWeight: 500 } }, a.label), e("div", { style: { flex: 1, height: 16, background: "#f1f0ea", borderRadius: 4, overflow: "hidden" } }, e("div", { style: { height: "100%", width: a.pct + "%", background: colors[i % colors.length], borderRadius: 4 } })), e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 13, fontWeight: 700, width: 38, textAlign: "right" } }, a.pct + "%")))); return base; }
    if (w.type === "netflow") {
      base.warming = true; base.subtitle = "gains vs losses · 30d"; const nfMax = Math.max(...D.netFlow.map((n) => Math.max(n.gains, n.losses))) || 1;
      base.node = e("div", { style: { display: "flex", flexDirection: "column", gap: 7, flex: 1, justifyContent: "center" } }, D.netFlow.map((n, i) => e("div", { key: i, style: { display: "flex", alignItems: "center", gap: 8 } }, e("span", { style: { width: 116, flex: "none", fontSize: 11.5, color: "#4a504a", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, n.name), e("div", { style: { flex: 1, display: "flex", alignItems: "center", height: 15 } }, e("div", { style: { flex: 1, display: "flex", justifyContent: "flex-end" } }, e("div", { onClick: () => this.openOffice(n.key), style: { height: 13, width: (n.losses / nfMax * 100) + "%", background: "#c0612f", borderRadius: "3px 0 0 3px", cursor: "pointer" } })), e("div", { style: { width: 1, height: 17, background: "#d6d4cb", flex: "none" } }), e("div", { style: { flex: 1 } }, e("div", { onClick: () => this.openOffice(n.key), style: { height: 13, width: (n.gains / nfMax * 100) + "%", background: "#1f8a5b", borderRadius: "0 3px 3px 0", cursor: "pointer" } }))), e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 12, fontWeight: 700, color: n.net > 0 ? "#1f8a5b" : n.net < 0 ? "#c0612f" : "#8a8e88", width: 32, textAlign: "right", flex: "none" } }, (n.net >= 0 ? "+" : "") + n.net)))); return base;
    }
    if (w.type === "moves") { base.warming = true; base.subtitle = "member office changes"; base.node = e("div", { style: { display: "flex", flexDirection: "column", flex: 1 } }, D.movesFeed.slice(0, w.span >= 4 ? 8 : 6).map((m, i) => e("div", { key: i, className: "rc-row", style: { display: "flex", alignItems: "center", gap: 10, padding: "8px 4px", borderTop: i ? "1px solid #f4f3ee" : "none" } }, e("div", { style: { width: 26, height: 26, borderRadius: "50%", background: "#e7e1d4", color: "#006747", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10, fontWeight: 600, flex: "none" } }, m.initials), e("div", { style: { flex: 1, minWidth: 0, fontSize: 12.5, color: "#4a504a", lineHeight: 1.35 } }, e("a", { href: "#", onClick: (ev) => { ev.preventDefault(); this.openMember(m.memberKey); }, style: { fontWeight: 600, color: "#1d241f" } }, m.member), " · ", e("span", { style: { color: "#c0612f" } }, m.from), " → ", e("span", { style: { color: "#1f8a5b" } }, m.to)), e("span", { style: { fontSize: 11, color: "#a8aca6", flex: "none" } }, m.date)))); return base; }
    if (w.type === "corridors") { base.subtitle = "where movers go · 12m"; base.node = e("div", { style: { display: "flex", flexDirection: "column", flex: 1 } }, D.corridors.map((c, i) => e("div", { key: i, className: "rc-row", style: { display: "flex", alignItems: "center", gap: 8, padding: "9px 4px", borderTop: i ? "1px solid #f4f3ee" : "none", fontSize: 12.5 } }, e("a", { href: "#", onClick: (ev) => { ev.preventDefault(); this.openOffice(c.from); }, style: { color: "#c0612f", fontWeight: 600 } }, c.fromName), e("span", { style: { color: "#bcbfba" } }, "→"), e("a", { href: "#", onClick: (ev) => { ev.preventDefault(); this.openOffice(c.to); }, style: { color: "#1f8a5b", fontWeight: 600, flex: 1 } }, c.toName), e("span", { style: { fontFamily: "'Space Mono',monospace", fontWeight: 700 } }, c.n), e("span", { style: { color: "#a8aca6", fontSize: 11 } }, "members")))); return base; }
    if (w.type === "licensing") { base.subtitle = "active membership"; const licTotal = D.licenseStatus.reduce((a, b) => a + b.count, 0); base.node = e("div", { style: { display: "flex", flexDirection: "column", gap: 10, flex: 1, justifyContent: "center" } }, D.licenseStatus.map((l, i) => e("div", { key: i, className: "pv-row", onClick: () => this.pivotTo({}), style: { display: "flex", alignItems: "center", gap: 10, padding: "2px 3px", borderRadius: 6, cursor: "pointer" } }, e("span", { style: { width: 78, flex: "none", fontSize: 12, color: "#4a504a" } }, l.label), e("div", { style: { flex: 1, height: 14, background: "#f1f0ea", borderRadius: 4, overflow: "hidden" } }, e("div", { style: { height: "100%", width: (l.count / licTotal * 100) + "%", background: l.color, borderRadius: 4 } })), e("span", { style: { fontFamily: "'Space Mono',monospace", fontSize: 12, fontWeight: 700, width: 52, textAlign: "right" } }, nfmt(l.count))))); return base; }
    if (w.type === "expiring") { base.subtitle = "renewal risk windows"; const K = D.meta.kpis; const trio = [["30d", K.expiring30], ["60d", K.expiring60], ["90d", K.expiring90]]; base.node = e("div", { style: { display: "flex", gap: 10, flex: 1, alignItems: "center" } }, trio.map(([lab, c], i) => e("button", { key: i, className: "pivot", onClick: () => this.pivotTo({}), style: { flex: 1, background: "#faf7f0", border: 0, borderRadius: 8, padding: "16px 8px", textAlign: "center", cursor: "pointer", fontFamily: "inherit" } }, e("div", { style: { fontFamily: "'Space Mono',monospace", fontSize: 24, fontWeight: 700, color: "#9a6a12" } }, nfmt(c)), e("div", { style: { fontSize: 11, color: "#a8aca6", marginTop: 4 } }, "within " + lab)))); return base; }
    base.node = e("div", { style: { color: "#a8aca6", fontSize: 12 } }, "Unknown widget"); return base;
  }
  builderPreview(d) {
    if (!d || !d.type) return null;
    const r = this.renderWidget({ ...d, id: "preview" });
    return React.createElement("div", { style: { background: "#fff", borderRadius: 3, boxShadow: "inset 0 0 0 1px rgba(0,0,0,.07)", padding: "13px 15px" } }, React.createElement("div", { style: { fontSize: 13, fontWeight: 600, marginBottom: 8 } }, r.title), r.node);
  }

  // build the vals object (mirror of the DC renderVals — data prep only) --------
  vals() {
    const D = this.D(); if (!D.MEMBERS) return {}; const s = this.state; const CC = D.CHART_COLORS || {}; const meta = D.meta, K = meta.kpis, nfmt = (n) => n.toLocaleString(); const cap = (v) => s.view === v; const link = (fn) => (ev) => { if (ev && ev.preventDefault) ev.preventDefault(); fn(); };
    const icons = {
      download: this.ic("download", 15, "#006747"), downloadSm: this.ic("download", 14, "#fff"), downloadBig: this.ic("download", 18, "#006747"),
      calendarClock: this.ic("calendar-clock", 15, "#006747"), sparkles: this.ic("sparkles", 15, "#006747"), refresh: this.ic("refresh-cw", 12, "#bcbfba"),
      cornerDown: this.ic("corner-down-right", 13, "#8aa99a"), loaderMute: this.ic("loader", 14, "#8a8e88", 2), rotate: this.ic("rotate-ccw", 13, "#006747"),
      crumbChev: this.ic("chevron-right", 13, "#c0c3bd"), searchSm: this.ic("search", 14, "#a8aca6"), lockSm: this.ic("lock", 11, "#c2c0b7"),
      plusSm: this.ic("plus", 13, "#8a8e88"), plusWhite: this.ic("plus", 14, "#fff"), plusBig: this.ic("plus-circle", 22, "#8a8e88"),
      xSm: this.ic("x", 13, "#a8aca6"), xTiny: this.ic("x", 12, "#006747"), xClose: this.ic("x", 18, "#8a8e88"), saveSm: this.ic("bookmark", 13, "#006747"),
      building: this.ic("building-2", 14, "#8a8e88"), building2: this.ic("building-2", 14, "#006747"), mail: this.ic("mail", 14, "#8a8e88"),
      phone: this.ic("phone", 14, "#8a8e88"), calSm: this.ic("calendar", 14, "#8a8e88"), idCard: this.ic("badge-check", 14, "#a8aca6"),
      msgWhite: this.ic("message-square", 14, "#fff"), shield: this.ic("shield-check", 14, "#bcbfba"), checkGreen: this.ic("check-circle-2", 15, "#1f8a5b"),
      copy: this.ic("copy", 14, "#6b706a"), trash: this.ic("trash-2", 14, "#6b706a"), pencil: this.ic("pencil", 14, "#6b706a"), resize: this.ic("maximize-2", 13, "#6b706a"),
      arrowLeftSm: this.ic("chevron-left", 15, "#6b706a"), arrowRightCtl: this.ic("chevron-right", 15, "#6b706a"), sliders: this.ic("sliders-horizontal", 16, "#006747"), widget: this.ic("layout-grid", 18, "#006747"),
    };
    const monthOpts = [3, 6, 12, 24].map((m) => ({ label: m + "mo", onClick: () => this.setMonths(m), style: `font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;border:0;border-radius:6px;padding:6px 12px;background:${s.months === m ? "#006747" : "transparent"};color:${s.months === m ? "#fff" : "#6b706a"}` }));
    const monthsLabel = s.months + " mo";
    const dashTabs = s.dashboards.map((d) => ({ name: d.name, count: d.widgets.length, onClick: () => this.setActiveDash(d.id), style: `font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;background:none;border:0;border-bottom:2px solid ${d.id === s.activeDash ? "#006747" : "transparent"};color:${d.id === s.activeDash ? "#1d241f" : "#8a8e88"};padding:9px 12px 8px;margin-bottom:-1px` }));
    const active = this.activeDashObj();
    const widgets = active.widgets.map((w) => this.renderWidget(w));
    const editBtnStyle = `display:inline-flex;align-items:center;gap:6px;font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;padding:7px 13px;border-radius:7px;border:1px solid ${s.editMode ? "#006747" : "#d7d4c9"};background:${s.editMode ? "#006747" : "#fff"};color:${s.editMode ? "#fff" : "#006747"}`;

    const b = s.builder, d = b.draft || {};
    const blockDefs = [
      { key: "kpi", label: "Metric (KPI)", desc: "A single number with an optional comparison.", icon: this.ic("hash", 17, "#006747") },
      { key: "bar", label: "Breakdown", desc: "Compare a metric across offices or segments.", icon: this.ic("bar-chart-3", 17, "#006747") },
      { key: "line", label: "Trend", desc: "A metric over time, with a warming state.", icon: this.ic("line-chart", 17, "#006747") },
      { key: "leaderboard", label: "Leaderboard", desc: "Top members or offices by a metric.", icon: this.ic("list-ordered", 17, "#006747") },
      { key: "distribution", label: "Distribution", desc: "Members grouped by closed-side bands.", icon: this.ic("align-end-horizontal", 17, "#006747") },
      { key: "gauge", label: "Gauge", desc: "A single rate as a dial — adoption, engagement.", icon: this.ic("gauge", 17, "#006747") },
    ];
    const prebuiltDefs = [
      { key: "adoption", label: "Feature adoption", icon: this.ic("toggle-right", 15, "#006747") },
      { key: "netflow", label: "Office net flow", icon: this.ic("arrow-left-right", 15, "#006747") },
      { key: "moves", label: "Recent moves", icon: this.ic("route", 15, "#006747") },
      { key: "corridors", label: "Top corridors", icon: this.ic("git-branch", 15, "#006747") },
      { key: "licensing", label: "License status", icon: this.ic("badge-check", 15, "#006747") },
      { key: "expiring", label: "Expiring licenses", icon: this.ic("clock", 15, "#006747") },
    ];
    const allDefs = [...blockDefs, ...prebuiltDefs]; const curType = allDefs.find((t) => t.key === d.type) || {};
    const chip = (on, label, onClick) => ({ label, onClick, style: `font-family:inherit;font-size:12px;font-weight:600;padding:6px 12px;border-radius:999px;cursor:pointer;border:1px solid ${on ? "#006747" : "#e2e0d8"};background:${on ? "#eef4f1" : "#fff"};color:${on ? "#006747" : "#6b706a"}` });
    const spanField = { label: "Widget width", isChips: true, chips: [[2, "Small"], [3, "Medium"], [4, "Large"], [6, "Full"]].map(([v, l]) => chip((d.span || 3) === v, l, () => this.setDraft("span", v))) };
    let fields = [];
    if (d.type === "kpi") { const opts = this.metricCatalog().map((m) => ({ value: m.key, label: m.group + " · " + m.label })); fields = [{ label: "Metric", isSelect: true, value: d.metric, options: opts, onChange: (ev) => this.setDraft("metric", ev.target.value) }, { label: "Comparison", isToggle: true, on: !!d.compare, toggleLabel: d.compare ? "Show change vs prior period" : "No comparison", onToggle: () => this.setDraft("compare", !d.compare), toggleTrack: d.compare ? "#006747" : "#cfccc2", knobLeft: d.compare ? "14px" : "2px", toggleStyle: "display:inline-flex;align-items:center;gap:9px;font-family:inherit;font-size:12.5px;font-weight:500;color:#4a504a;background:none;border:0;cursor:pointer;padding:0" }, spanField]; }
    else if (d.type === "bar") { fields = [{ label: "Metric", isSelect: true, value: d.metric, options: this.barMetrics().map((m) => ({ value: m.key, label: m.label })), onChange: (ev) => this.setDraft("metric", ev.target.value) }, { label: "Break down by", isSelect: true, value: d.dim, options: this.dims().map((m) => ({ value: m.key, label: m.label })), onChange: (ev) => this.setDraft("dim", ev.target.value) }, { label: "Show top", isSelect: true, value: String(d.topN || 8), options: [5, 8, 10].map((n) => ({ value: String(n), label: "Top " + n })), onChange: (ev) => this.setDraft("topN", Number(ev.target.value)) }, { label: "Chart style", isChips: true, chips: [["column", "Columns"], ["bar", "Bars"], ["donut", "Donut"]].map(([v, l]) => chip((d.chart || "column") === v, l, () => this.setDraft("chart", v))) }, { label: "Comparison", isToggle: true, on: !!d.compare, toggleLabel: d.compare ? "Show prior-period ghost bars" : "No comparison", onToggle: () => this.setDraft("compare", !d.compare), toggleTrack: d.compare ? "#006747" : "#cfccc2", knobLeft: d.compare ? "14px" : "2px", toggleStyle: "display:inline-flex;align-items:center;gap:9px;font-family:inherit;font-size:12.5px;font-weight:500;color:#4a504a;background:none;border:0;cursor:pointer;padding:0" }, spanField]; }
    else if (d.type === "line") { fields = [{ label: "Series", isSelect: true, value: d.trend, options: this.trendCat().map((t) => ({ value: t.key, label: t.label })), onChange: (ev) => this.setDraft("trend", ev.target.value) }, spanField]; }
    else if (d.type === "leaderboard") { fields = [{ label: "Rank", isSelect: true, value: d.subject, options: [{ value: "members", label: "Members" }, { value: "offices", label: "Offices" }], onChange: (ev) => this.setDraft("subject", ev.target.value) }, { label: "By metric", isSelect: true, value: d.metric, options: this.lbMetrics(d.subject).map((m) => ({ value: m.key, label: m.label })), onChange: (ev) => this.setDraft("metric", ev.target.value) }, { label: "Show top", isSelect: true, value: String(d.topN || 6), options: [5, 6, 8, 10].map((n) => ({ value: String(n), label: "Top " + n })), onChange: (ev) => this.setDraft("topN", Number(ev.target.value)) }, spanField]; }
    else if (d.type === "gauge") { fields = [{ label: "Metric", isSelect: true, value: d.metric, options: this.gaugeMetrics().map((m) => ({ value: m.key, label: m.label })), onChange: (ev) => this.setDraft("metric", ev.target.value) }, spanField]; }
    else if (d.type) fields = [spanField];
    if (d.type === "leaderboard" && !this.lbMetrics(d.subject).some((m) => m.key === d.metric)) d.metric = this.lbMetrics(d.subject)[0].key;
    const builder = { open: b.open, stepType: b.step === "type", stepConfig: b.step === "config", isNew: !b.id, heading: b.step === "type" ? "Add a widget" : "Configure widget", sub: b.step === "type" ? "Pick what this widget should show" : "Set it up, then add it to your dashboard", saveLabel: b.id ? "Save changes" : "Add to dashboard", blockTypes: blockDefs.map((t) => ({ ...t, onClick: () => this.chooseType(t.key) })), prebuiltTypes: prebuiltDefs.map((t) => ({ ...t, onClick: () => this.chooseType(t.key) })), typeIcon: curType.icon, typeLabel: curType.label, typeDesc: curType.desc || "Prebuilt panel", fields, draft: d, preview: this.builderPreview(d) };

    const nfMax = Math.max(...D.netFlow.map((n) => Math.max(n.gains, n.losses)));
    const netFlowRows = D.netFlow.map((n) => ({ name: n.name, lossPct: Math.round((n.losses / nfMax) * 100), gainPct: Math.round((n.gains / nfMax) * 100), netLabel: (n.net >= 0 ? "+" : "") + n.net, netColor: n.net > 0 ? "#1f8a5b" : n.net < 0 ? "#c0612f" : "#8a8e88", onGains: (ev) => { if (ev && ev.stopPropagation) ev.stopPropagation(); this.openOffice(n.key); }, onLosses: (ev) => { if (ev && ev.stopPropagation) ev.stopPropagation(); this.openOffice(n.key); } }));
    const movesFeed = D.movesFeed.map((m) => ({ initials: m.initials, member: m.member, from: m.from, to: m.to, date: m.date, onMember: link(() => this.openMember(m.memberKey)), onFrom: link(() => this.openOffice(m.fromKey)), onTo: link(() => this.openOffice(m.toKey)) }));
    const corridors = D.corridors.map((c) => ({ fromName: c.fromName, toName: c.toName, n: c.n, onFrom: link(() => this.openOffice(c.from)), onTo: link(() => this.openOffice(c.to)) }));
    const ai = { q: s.ai.q, a: s.ai.a, loading: s.ai.loading, hasAnswer: !!s.ai.a && !s.ai.loading, showSamples: !s.ai.a && !s.ai.loading, btnIcon: s.ai.loading ? this.ic("loader", 16, "#fff") : this.ic("arrow-up", 16, "#fff") };
    const aiSamples = D.aiSamples.map((x) => ({ q: x.q, onClick: () => this.askAI(x.q) }));

    const rows = this.filtered(); const featLookup = {}; D.FEATURES.forEach((f) => featLookup[f.key] = f.label);
    const officeFacets = D.OFFICES.map((o) => ({ name: o.name, on: s.filters.offices.includes(o.key), toggle: () => this.toggleArr("offices", o.key) }));
    const chipStyle = (on) => `font-family:inherit;font-size:11.5px;font-weight:600;padding:5px 10px;border-radius:999px;cursor:pointer;border:1px solid ${on ? "#006747" : "#e2e0d8"};background:${on ? "#eef4f1" : "#fff"};color:${on ? "#006747" : "#6b706a"}`;
    const statusFacets = D.STATUSES.map((st2) => ({ label: st2, style: chipStyle(s.filters.status.includes(st2)), toggle: () => this.toggleArr("status", st2) }));
    const tenureFacets = D.TENURE_BANDS.map((t) => ({ label: t.label, style: chipStyle(s.filters.tenure.includes(t.key)), toggle: () => this.toggleArr("tenure", t.key) }));
    const prodFacets = D.PRODUCTION_BANDS.map((p) => ({ label: p.label, style: chipStyle(s.filters.prod.includes(p.key)), toggle: () => this.toggleArr("prod", p.key) }));
    const activityFacets = [{ label: "Active 30d" }, { label: "Active 90d" }, { label: "Dormant" }, { label: "Never seen" }];
    const modeBtn = (on) => `font-family:inherit;font-size:10.5px;font-weight:600;cursor:pointer;border:0;border-radius:5px;padding:4px 8px;background:${on ? "#fff" : "transparent"};color:${on ? "#006747" : "#8a8e88"};box-shadow:${on ? "0 1px 2px rgba(0,0,0,.12)" : "none"}`;
    const featureFacets = D.FEATURES.map((f) => { const cur = s.filters.features[f.key] || "any"; return { label: f.label, modes: [["any", "Any"], ["has", "Has"], ["hasnt", "Hasn’t"]].map(([m, lab]) => ({ label: lab, style: modeBtn(cur === m), onClick: m === "any" ? () => { if (cur !== "any") this.setFeature(f.key, cur); } : () => this.setFeature(f.key, m) })) }; });
    const statColsAll = [["activeListings", "Active listings"], ["closedSides", "Closed sides 12m"], ["showingsHosted", "Showings hosted 90d"], ["showingsRequested", "Showings requested 90d"], ["offers", "Offers 12m"], ["lmsCompleted", "LMS completions"]];
    const usedStat = s.filters.stats.map((x) => x.col); const statPickOpts = statColsAll.filter((c) => !usedStat.includes(c[0])).map((c) => ({ label: c[1], onClick: () => this.addStat(c[0], c[1]) }));
    const statRows = s.filters.stats.map((st2, i) => ({ label: st2.label, min: st2.min, max: st2.max, setMin: (ev) => this.setStat(i, "min", ev.target.value), setMax: (ev) => this.setStat(i, "max", ev.target.value), remove: () => this.removeStat(i) }));
    const cohortChips = [];
    if (s.filters.q) cohortChips.push({ label: `“${s.filters.q}”`, remove: () => this.setFilter("q", "") });
    s.filters.offices.forEach((k) => cohortChips.push({ label: D.officeName(k), remove: () => this.toggleArr("offices", k) }));
    s.filters.status.forEach((k) => cohortChips.push({ label: k.charAt(0).toUpperCase() + k.slice(1), remove: () => this.toggleArr("status", k) }));
    s.filters.tenure.forEach((k) => cohortChips.push({ label: (D.TENURE_BANDS.find((b2) => b2.key === k) || {}).label, remove: () => this.toggleArr("tenure", k) }));
    s.filters.prod.forEach((k) => cohortChips.push({ label: "Sides " + (D.PRODUCTION_BANDS.find((b2) => b2.key === k) || {}).label, remove: () => this.toggleArr("prod", k) }));
    for (const k in s.filters.features) cohortChips.push({ label: featLookup[k] + (s.filters.features[k] === "has" ? " ✓" : " ✗"), remove: () => this.setFeature(k, s.filters.features[k]) });
    s.filters.stats.forEach((st2, i) => { if (st2.min !== "" || st2.max !== "") cohortChips.push({ label: `${st2.label} ${st2.min || "0"}–${st2.max || "∞"}`, remove: () => this.removeStat(i) }); });
    const gridColDefs = [["name", "Member", "flex-start"], ["office", "Office", "flex-start"], ["tenure", "Tenure", "flex-start"], ["activeListings", "Listings", "flex-end"], ["closedSides", "Sides 12m", "flex-end"], ["showingsHosted", "Show. 90d", "flex-end"], ["offers", "Offers", "flex-end"], ["adoption", "Adoption", "center"], ["lastSeen", "Last seen", "flex-end"]];
    const sortable = { tenure: 1, activeListings: 1, closedSides: 1, showingsHosted: 1, offers: 1, lastSeen: 1 };
    const gridCols = gridColDefs.map(([col, label, justify]) => ({ label, justify, color: s.sort.col === col ? "#006747" : "#a8aca6", caret: sortable[col] ? (s.sort.col === col ? this.ic(s.sort.dir === "asc" ? "chevron-up" : "chevron-down", 12, "#006747") : this.ic("chevrons-up-down", 12, "#cfccc2")) : "", onSort: sortable[col] ? () => this.setSort(col) : () => {} }));
    const gridRows = rows.map((m) => ({ key: m.key, initials: m.initials, name: m.name, mls: m.mls, status: m.status, statusStyle: this.statusStyle(m.status), office: m.office, tenureLabel: m.tenureLabel, activeListings: m.activeListings, closedSides: m.closedSides, showingsHosted: m.showingsHosted, offers: m.offers, lastSeen: m.lastSeen, adoptStrip: D.FEATURES.map((f, fi) => { const on = m.adoption.includes(f.key); return React.createElement("span", { key: fi, title: f.label + (on ? " ✓" : ""), style: { width: 16, height: 16, borderRadius: 4, display: "inline-flex", alignItems: "center", justifyContent: "center", fontSize: 8, fontWeight: 700, background: on ? "#e8f4ee" : "#f1f0ea", color: on ? "#1f8a5b" : "#cfccc2" } }, f.label[0]); }), onClick: () => this.openMember(m.key) }));
    const savedViews = [{ label: "Top producers", onClick: () => this.applySaved({ prod: ["b4", "b5"] }) }, { label: "New · CMA gap", onClick: () => this.applySaved({ tenure: ["new"], features: { cma: "hasnt" } }) }, { label: "Dormant", onClick: () => this.applySaved({ status: ["inactive"] }) }];

    let member = null;
    if (s.memberKey) { const m = D.MEMBERS.find((x) => x.key === s.memberKey); if (m) { const mk = (base, jit) => D.MONTHS.map((_, i) => Math.max(0, Math.round(base * (0.55 + 0.09 * i) + Math.sin(i * 1.3) * jit))); const licColor = m.license.status === "active" ? "#1d241f" : "#9a6a12"; const licPillTone = m.license.status === "active" ? ["#e8f4ee", "#1f8a5b", "Active"] : m.license.status === "expiring" ? ["#fbf2dc", "#9a6a12", "Expiring soon"] : ["#faece3", "#c0612f", "Expired"]; member = { key: m.key, name: m.name, initials: m.initials, mls: m.mls, status: m.status, statusStyle: this.statusStyle(m.status), office: m.office, email: m.email, phone: m.phone, joinedLabel: m.joinedLabel, nrds: m.nrds, license: m.license, lmsCompleted: m.lmsCompleted, ceCredits: m.ceCredits, tickets: m.tickets, lastSeen: m.lastSeen, onOffice: () => this.openOffice(m.officeKey), licExpColor: licColor, licPill: licPillTone[2], licPillStyle: `font-size:10px;font-weight:600;padding:2px 8px;border-radius:999px;background:${licPillTone[0]};color:${licPillTone[1]}`, kpiStrip: [["Active listings", m.activeListings], ["Closed sides 12m", m.closedSides], ["Showings hosted", m.showingsHosted], ["Showings req.", m.showingsRequested], ["Offers 12m", m.offers], ["Feedback ★", m.rating]].map(([label, value]) => ({ label, value })), listingsChart: this.gbar([{ values: mk(Math.max(1, m.activeListings / 3), 1.4) }, { values: mk(Math.max(1, m.closedSides / 9), 1.1) }], D.MONTHS, [CC.GREEN, CC.ORANGE], { h: 170, yfmt: (v) => Math.round(v) }), showingsChart: this.gbar([{ values: mk(Math.max(1, m.showingsHosted / 6), 2) }, { values: mk(Math.max(1, m.showingsRequested / 6), 2) }], D.MONTHS, [CC.GREEN, CC.TEAL], { h: 170, yfmt: (v) => Math.round(v) }), officeHistory: [{ office: m.office, span: "Jun 2026 – present", dot: "#006747" }, { office: "History begins " + meta.warmingSince.moves, span: "warming boundary", dot: "#d8d6cd" }], adoptChecklist: D.FEATURES.map((f) => { const on = m.adoption.includes(f.key); return { label: f.label, mark: this.ic(on ? "check" : "minus", 13, on ? "#1f8a5b" : "#c2c0b7"), style: on ? "background:#e8f4ee;color:#1f8a5b" : "background:#f4f3ef;color:#a8aca6" }; }) }; } }

    const metricKey = { members: "members", listings: "activeListings", sides: "closedSides", showings: "showings" };
    const officeMetricTabs = [["members", "Members"], ["listings", "Listings"], ["sides", "Closed sides"], ["showings", "Showings"]].map(([k, lab]) => ({ label: lab, onClick: () => this.setOfficeMetric(k), style: `font-family:inherit;font-size:11px;font-weight:600;cursor:pointer;border:0;border-radius:6px;padding:5px 10px;background:${s.officeMetric === k ? "#fff" : "transparent"};color:${s.officeMetric === k ? "#006747" : "#8a8e88"};box-shadow:${s.officeMetric === k ? "0 1px 2px rgba(0,0,0,.12)" : "none"}` }));
    const mkKey = metricKey[s.officeMetric]; const officesSorted = D.OFFICES.slice().sort((a, b2) => b2[mkKey] - a[mkKey]); const oMax = officesSorted[0][mkKey];
    const officeLeaderboard = officesSorted.map((o, i) => ({ rank: i + 1, name: o.name, barPct: Math.round((o[mkKey] / oMax) * 100), metricVal: nfmt(o[mkKey]), delta: (o.delta30 >= 0 ? "+" : "") + o.delta30, deltaColor: o.delta30 >= 0 ? "#1f8a5b" : "#c0612f", onClick: () => this.openOffice(o.key) }));

    return { icons, monthOpts, monthsLabel, asOf: meta.asOf, warmMoves: meta.warmingSince.moves,
      isOverview: cap("overview"), isMembers: cap("members"), isProfile: cap("profile") && !!member, isOffices: cap("offices"),
      dashTabs, widgets, editMode: s.editMode, noWidgets: widgets.length === 0, dashName: active.name, editBtnStyle, editBtnIcon: s.editMode ? this.ic("check", 14, "#fff") : this.ic("sliders-horizontal", 14, "#006747"), editBtnLabel: s.editMode ? "Done editing" : "Edit layout",
      builder, ai, aiSamples,
      filters: s.filters, officeFacets, statusFacets, tenureFacets, prodFacets, activityFacets, featureFacets, statRows, statPickOpts, statPickerOpen: s.statPickerOpen, cohortChips, hasChips: cohortChips.length > 0, resultCount: nfmt(rows.length === D.MEMBERS.length ? 1284 : rows.length), totalMembers: nfmt(D.TOTAL_MEMBERS), savedViews, gridCols, gridRows, hasResults: rows.length > 0, noResults: rows.length === 0, pageShown: Math.min(rows.length, 25),
      member, officeMetricTabs, officeLeaderboard, corridors, netFlowRows, movesFeed, exportOpen: s.exportOpen };
  }

  // ================= RENDER =================
  aiBox(v) {
    return (
      <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);overflow:hidden;margin-top:16px")}>
        <div style={st("display:flex;align-items:center;gap:9px;padding:13px 18px;border-bottom:1px solid #f1f0ea")}>
          <span style={st("width:26px;height:26px;border-radius:50%;background:#eef4f1;display:flex;align-items:center;justify-content:center")}>{v.icons.sparkles}</span>
          <h3 style={st("margin:0;font-size:14.5px;font-weight:600;flex:1")}>Ask about your members</h3>
          <span style={st("font-size:11px;color:#bcbfba")}>Answers are estimates · turn a question into a widget</span>
        </div>
        <div style={st("padding:16px 18px")}>
          <div style={st("display:flex;gap:8px;align-items:flex-end;background:#f7f6f1;border-radius:8px;padding:8px 8px 8px 12px;border:1px solid #ece9e0;max-width:820px")}>
            <textarea value={v.ai.q} onChange={(ev) => this.setState({ ai: { ...this.state.ai, q: ev.target.value } })} onKeyDown={(ev) => { if (ev.key === "Enter" && !ev.shiftKey) { ev.preventDefault(); this.askAI(); } }} placeholder="e.g. Which offices are losing top producers fastest?" rows={2} style={st("flex:1;border:0;background:transparent;outline:0;resize:none;font-family:inherit;font-size:13px;color:#1d241f;line-height:1.4")} />
            <button onClick={() => this.askAI()} style={st("width:32px;height:32px;border-radius:7px;border:0;background:#006747;color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;flex:none")}>{v.ai.btnIcon}</button>
          </div>
          {v.ai.showSamples && (
            <div style={st("display:flex;gap:8px;margin-top:11px;flex-wrap:wrap")}>
              {v.aiSamples.map((sm, i) => <button key={i} onClick={sm.onClick} style={st("text-align:left;font-family:inherit;font-size:12.5px;color:#006747;background:#f4f6f4;border:1px solid #e6ece8;border-radius:7px;padding:8px 11px;cursor:pointer;display:inline-flex;align-items:center;gap:8px")}>{v.icons.cornerDown}{sm.q}</button>)}
            </div>
          )}
          {v.ai.loading && <div style={st("margin-top:14px;font-size:12.5px;color:#8a8e88;display:flex;align-items:center;gap:8px")}>{v.icons.loaderMute} Analyzing your membership…</div>}
          {v.ai.hasAnswer && (
            <div style={st("margin-top:14px;max-width:820px")}>
              <div style={st("font-size:13px;color:#2d332e;line-height:1.6;background:#f7f6f1;border-radius:8px;padding:13px 15px;border-left:2.5px solid #006747")}>{v.ai.a}</div>
              <button onClick={() => this.resetAI()} style={st("margin-top:9px;font-family:inherit;font-size:12px;font-weight:600;color:#006747;background:none;border:0;cursor:pointer;display:inline-flex;align-items:center;gap:5px")}>{v.icons.rotate} Ask another</button>
            </div>
          )}
        </div>
      </div>
    );
  }

  viewDashboard(v) {
    return (
      <div style={st("padding:22px 28px 20px;max-width:1440px;margin:0 auto")}>
        <div style={st("display:flex;align-items:flex-end;justify-content:space-between;flex-wrap:wrap;gap:14px;margin-bottom:14px")}>
          <div>
            <h1 style={st("margin:0;font-size:25px;font-weight:600;letter-spacing:-0.6px;color:#1d241f")}>Member Insights</h1>
            <p style={st("margin:5px 0 0;font-size:14px;color:#8a8e88;max-width:600px")}>Build your own membership dashboards — hit <strong style={st("color:#006747;font-weight:600")}>Add widget</strong> to drop in any metric, comparison, breakdown or chart, then save the layouts your team runs on.</p>
          </div>
          <div style={st("display:flex;gap:10px;align-items:center")}>
            <div style={st("display:inline-flex;background:#fff;border-radius:8px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.08);padding:3px")}>
              {v.monthOpts.map((mo, i) => <button key={i} onClick={mo.onClick} title="Sets trend-widget windows" style={st(mo.style)}>{mo.label}</button>)}
            </div>
            <button onClick={() => this.openExport()} style={st("display:inline-flex;align-items:center;gap:7px;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:9px 14px;border-radius:8px;border:1px solid #d7d4c9;background:#fff;color:#006747")}>{v.icons.download} Export</button>
            <button onClick={() => this.openExport()} style={st("display:inline-flex;align-items:center;gap:7px;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:9px 14px;border-radius:8px;border:1px solid #d7d4c9;background:#fff;color:#006747")}>{v.icons.calendarClock} Schedule digest</button>
            <button onClick={() => this.openBuilderNew()} style={st("display:inline-flex;align-items:center;gap:7px;font-family:inherit;font-size:13px;font-weight:700;cursor:pointer;padding:9px 16px;border-radius:8px;border:0;background:#006747;color:#fff;box-shadow:0 1px 2px rgba(0,0,0,.12)")}>{v.icons.plusWhite} Add widget</button>
          </div>
        </div>

        <div style={st("display:flex;align-items:center;gap:4px;flex-wrap:wrap;border-bottom:1px solid #e6e4db;margin-bottom:14px")}>
          {v.dashTabs.map((d, i) => <button key={i} className="dash-tab" onClick={d.onClick} style={st(d.style)}>{d.name}<span style={st("font-family:'Space Mono',monospace;font-size:10px;color:#bcbfba;margin-left:7px")}>{d.count}</span></button>)}
          <button onClick={() => this.addDashboard()} title="New dashboard" style={st("display:inline-flex;align-items:center;gap:5px;font-family:inherit;font-size:12.5px;font-weight:600;color:#8a8e88;background:none;border:0;cursor:pointer;padding:9px 10px")}>{v.icons.plusSm} New</button>
          <div style={st("flex:1")} />
          <div style={st("display:flex;align-items:center;gap:8px;padding-bottom:6px")}>
            <button onClick={() => this.duplicateDashboard()} title="Duplicate this dashboard" className="wctl">{v.icons.copy}</button>
            <button onClick={() => this.deleteDashboard()} title="Delete this dashboard" className="wctl">{v.icons.trash}</button>
            <button onClick={() => this.toggleEdit()} style={st(v.editBtnStyle)}>{v.editBtnIcon}{v.editBtnLabel}</button>
          </div>
        </div>

        {v.editMode && (
          <div style={st("display:flex;align-items:center;gap:12px;flex-wrap:wrap;background:#eef4f1;border:1px solid #d3e4db;border-radius:9px;padding:11px 14px;margin-bottom:14px")}>
            {v.icons.sliders}
            <span style={st("font-size:12.5px;font-weight:600;color:#006747")}>Editing layout</span>
            <div style={st("display:flex;align-items:center;gap:7px")}><span style={st("font-size:12px;color:#4a504a")}>Name:</span><input value={v.dashName} onChange={(ev) => this.renameDashboard(ev.target.value)} style={st("border:1px solid #cfe0d7;border-radius:6px;padding:6px 9px;font-family:inherit;font-size:12.5px;min-width:220px;background:#fff")} /></div>
            <div style={st("flex:1")} />
            <button onClick={() => this.openBuilderNew()} style={st("display:inline-flex;align-items:center;gap:6px;font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;padding:8px 13px;border-radius:7px;border:0;background:#006747;color:#fff")}>{v.icons.plusWhite} Add widget</button>
            <button onClick={() => this.toggleEdit()} style={st("font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;padding:8px 13px;border-radius:7px;border:1px solid #cfe0d7;background:#fff;color:#006747")}>Done</button>
          </div>
        )}

        <div style={st("display:flex;align-items:center;gap:8px;font-size:12px;color:#a8aca6;margin-bottom:16px")}>{v.icons.refresh}<span>As of {v.asOf} — updates nightly.</span><span style={st("color:#d6d4cb")}>·</span><span>Trend widgets reflect the {v.monthsLabel} window. Saved automatically to this workspace.</span></div>

        <div className="mi-grid" style={st("display:grid;grid-template-columns:repeat(6,1fr);gap:14px;align-items:stretch")}>
          {v.widgets.map((w) => (
            <div key={w.id} style={st(w.cardStyle)}>
              <div style={st("display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:12px")}>
                <div style={st("min-width:0")}>
                  <h3 style={st("margin:0;font-size:13.5px;font-weight:600;color:#1d241f;white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{w.title}</h3>
                  {w.subtitle && <div style={st("font-size:11px;color:#a8aca6;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{w.subtitle}</div>}
                </div>
                <div style={st("display:flex;align-items:center;gap:5px;flex:none")}>
                  {w.warming && <span style={st("font-size:9px;font-weight:600;color:#9a6a12;background:#fbf2dc;border-radius:999px;padding:2px 7px;white-space:nowrap")}>Warming</span>}
                  {v.editMode && (<div style={{ display: "contents" }}>
                    <button onClick={w.onLeft} title="Move left" className="wctl">{v.icons.arrowLeftSm}</button>
                    <button onClick={w.onResize} title="Resize" className="wctl">{v.icons.resize}</button>
                    <button onClick={w.onRight} title="Move right" className="wctl">{v.icons.arrowRightCtl}</button>
                    <button onClick={w.onEdit} title="Configure" className="wctl">{v.icons.pencil}</button>
                    <button onClick={w.onDup} title="Duplicate" className="wctl">{v.icons.copy}</button>
                    <button onClick={w.onRemove} title="Remove" className="wctl">{v.icons.xSm}</button>
                  </div>)}
                </div>
              </div>
              {w.node}
            </div>
          ))}
          {v.editMode && <button className="add-tile" onClick={() => this.openBuilderNew()} style={st("grid-column:span 3;min-height:150px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;font-family:inherit;font-size:13.5px;font-weight:600;color:#8a8e88;background:#fbfaf6;border:1.5px dashed #cdcabf;border-radius:3px;cursor:pointer")}>{v.icons.plusBig} Add a widget</button>}
        </div>

        {v.noWidgets && (
          <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:56px 20px;text-align:center;margin-top:2px")}>
            <div style={st("font-size:15px;font-weight:600;color:#1d241f")}>This dashboard is empty</div>
            <div style={st("font-size:13px;color:#8a8e88;margin-top:5px")}>Add metrics, breakdowns, trends and leaderboards to build your view.</div>
            <button onClick={() => this.openBuilderNew()} style={st("margin-top:16px;display:inline-flex;align-items:center;gap:7px;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:10px 18px;border-radius:8px;border:0;background:#006747;color:#fff")}>{v.icons.plusWhite} Add your first widget</button>
          </div>
        )}
        {this.aiBox(v)}
      </div>
    );
  }

  viewExplorer(v) {
    const gcols = "2fr 1.4fr 1fr 0.9fr 0.9fr 0.9fr 0.8fr 1.3fr 0.9fr";
    return (
      <div style={st("padding:22px 28px 20px;max-width:1440px;margin:0 auto")}>
        <div style={st("display:flex;align-items:center;gap:8px;font-size:12px;color:#a8aca6;margin-bottom:6px")}><a href="#" onClick={(e) => { e.preventDefault(); this.goOverview(); }}>Member Insights</a> {v.icons.crumbChev} <span style={st("color:#6b706a;font-weight:600")}>Members</span></div>
        <h1 style={st("margin:0 0 16px;font-size:24px;font-weight:600;letter-spacing:-0.5px")}>Member explorer</h1>
        <div className="mx-split" style={st("display:grid;grid-template-columns:264px 1fr;gap:18px;align-items:start")}>
          <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:4px 0;position:sticky;top:0")}>
            <div style={st("display:flex;align-items:center;justify-content:space-between;padding:13px 15px 10px")}><span style={st("font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:#8a8e88")}>Filters</span><button onClick={() => this.clearFilters()} style={st("font-family:inherit;font-size:11.5px;font-weight:600;color:#006747;background:none;border:0;cursor:pointer")}>Clear all</button></div>
            <div style={st("padding:0 15px 14px;display:flex;flex-direction:column;gap:14px")}>
              <div style={st("display:flex;align-items:center;gap:8px;background:#f4f3ef;border-radius:7px;padding:8px 10px")}>{v.icons.searchSm}<input value={v.filters.q} onChange={(ev) => this.setFilter("q", ev.target.value)} placeholder="Name or MLS #" style={st("flex:1;border:0;background:transparent;outline:0;font-family:inherit;font-size:12.5px")} /></div>
              <div><div style={st("font-size:11px;font-weight:600;color:#6b706a;margin-bottom:7px")}>Office</div><div style={st("display:flex;flex-direction:column;gap:5px;max-height:132px;overflow-y:auto")}>{v.officeFacets.map((o, i) => <label key={i} style={st("display:flex;align-items:center;gap:8px;font-size:12.5px;color:#4a504a;cursor:pointer")}><input type="checkbox" checked={o.on} onChange={o.toggle} style={{ accentColor: "#006747" }} />{o.name}</label>)}</div></div>
              <div><div style={st("font-size:11px;font-weight:600;color:#6b706a;margin-bottom:7px")}>Status</div><div style={st("display:flex;flex-wrap:wrap;gap:6px")}>{v.statusFacets.map((x, i) => <button key={i} onClick={x.toggle} style={st(x.style)}>{x.label}</button>)}</div></div>
              <div><div style={st("font-size:11px;font-weight:600;color:#6b706a;margin-bottom:7px")}>Tenure</div><div style={st("display:flex;flex-wrap:wrap;gap:6px")}>{v.tenureFacets.map((x, i) => <button key={i} onClick={x.toggle} style={st(x.style)}>{x.label}</button>)}</div></div>
              <div><div style={st("font-size:11px;font-weight:600;color:#6b706a;margin-bottom:7px")}>Joined date</div><div style={st("display:flex;gap:7px")}><input placeholder="From" style={st("flex:1;min-width:0;border:1px solid #e2e0d8;border-radius:6px;padding:6px 8px;font-family:inherit;font-size:12px;color:#8a8e88")} /><input placeholder="To" style={st("flex:1;min-width:0;border:1px solid #e2e0d8;border-radius:6px;padding:6px 8px;font-family:inherit;font-size:12px;color:#8a8e88")} /></div></div>
              <div><div style={st("font-size:11px;font-weight:600;color:#6b706a;margin-bottom:7px")}>Production — closed sides 12m</div><div style={st("display:flex;flex-wrap:wrap;gap:6px")}>{v.prodFacets.map((x, i) => <button key={i} onClick={x.toggle} style={st(x.style)}>{x.label}</button>)}</div></div>
              <div><div style={st("display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:#a8aca6;margin-bottom:7px")}>Activity band {v.icons.lockSm}</div><div style={st("display:flex;flex-wrap:wrap;gap:6px")}>{v.activityFacets.map((x, i) => <button key={i} disabled title="Warming — enabled once sign-in capture matures" style={st("font-family:inherit;font-size:11.5px;font-weight:600;padding:5px 9px;border-radius:999px;border:1px dashed #dcdad2;background:#faf9f5;color:#c2c0b7;cursor:not-allowed")}>{x.label}</button>)}</div><div style={st("font-size:10px;color:#bcbfba;margin-top:5px")}>Warming — sign-in capture started {v.warmMoves}.</div></div>
              <div><div style={st("font-size:11px;font-weight:600;color:#6b706a;margin-bottom:7px")}>Feature adoption</div><div style={st("display:flex;flex-direction:column;gap:8px")}>{v.featureFacets.map((f, i) => <div key={i} style={st("display:flex;align-items:center;gap:8px")}><span style={st("flex:1;font-size:12.5px;color:#4a504a")}>{f.label}</span><div style={st("display:inline-flex;background:#f1f0ea;border-radius:6px;padding:2px")}>{f.modes.map((md, j) => <button key={j} onClick={md.onClick} style={st(md.style)}>{md.label}</button>)}</div></div>)}</div></div>
              <div style={st("border-top:1px solid #f1f0ea;padding-top:12px")}>
                <div style={st("display:flex;align-items:center;justify-content:space-between;margin-bottom:8px")}><span style={st("font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;color:#8a8e88")}>Stat filters</span></div>
                {v.statRows.map((sr, i) => <div key={i} style={st("background:#f7f6f1;border-radius:7px;padding:8px 9px;margin-bottom:7px")}><div style={st("display:flex;align-items:center;justify-content:space-between;margin-bottom:6px")}><span style={st("font-size:12px;font-weight:600;color:#1d241f")}>{sr.label}</span><button onClick={sr.remove} style={st("background:none;border:0;cursor:pointer;padding:0;display:flex")}>{v.icons.xSm}</button></div><div style={st("display:flex;gap:6px")}><input value={sr.min} onChange={sr.setMin} placeholder="min" style={st("width:50%;border:1px solid #e2e0d8;border-radius:6px;padding:5px 7px;font-family:'Space Mono',monospace;font-size:12px")} /><input value={sr.max} onChange={sr.setMax} placeholder="max" style={st("width:50%;border:1px solid #e2e0d8;border-radius:6px;padding:5px 7px;font-family:'Space Mono',monospace;font-size:12px")} /></div></div>)}
                <div style={st("position:relative")}>
                  <button onClick={() => this.toggleStatPicker()} style={st("width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;padding:8px;border-radius:7px;border:1px dashed #c8c6bc;background:#fff;color:#006747")}>{v.icons.plusSm} Add a stat filter</button>
                  {v.statPickerOpen && <div style={st("position:absolute;bottom:calc(100% + 6px);left:0;right:0;background:#fff;border-radius:8px;box-shadow:0 8px 28px rgba(0,0,0,.16),inset 0 0 0 1px rgba(0,0,0,.06);padding:6px;z-index:20")}>{v.statPickOpts.map((sp, i) => <div key={i} className="rc-nav-item" onClick={sp.onClick} style={st("padding:7px 9px;border-radius:6px;cursor:pointer;font-size:12.5px;color:#1d241f")}>{sp.label}</div>)}</div>}
                </div>
              </div>
            </div>
          </div>

          <div style={st("min-width:0")}>
            <div style={st("display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px")}>
              {v.cohortChips.map((c, i) => <span key={i} style={st("display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;color:#006747;background:#eef4f1;border:1px solid #d3e4db;border-radius:999px;padding:5px 6px 5px 11px")}>{c.label}<button onClick={c.remove} style={st("background:none;border:0;cursor:pointer;padding:0;display:flex")}>{v.icons.xTiny}</button></span>)}
              {v.hasChips && <button onClick={() => this.clearFilters()} style={st("font-family:inherit;font-size:12px;font-weight:600;color:#8a8e88;background:none;border:0;cursor:pointer")}>Clear all</button>}
            </div>
            <div style={st("display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px")}>
              <span style={st("font-family:'Space Mono',monospace;font-size:13px;font-weight:700;color:#1d241f")}>{v.resultCount}</span><span style={st("font-size:12.5px;color:#a8aca6")}>of {v.totalMembers} members</span>
              <div style={st("flex:1")} />
              <span style={st("font-size:11px;font-weight:600;color:#a8aca6;margin-right:2px")}>Saved views:</span>
              {v.savedViews.map((sv, i) => <button key={i} onClick={sv.onClick} style={st("font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;padding:6px 11px;border-radius:999px;border:1px solid #e2e0d8;background:#fff;color:#4a504a")}>{sv.label}</button>)}
              <button style={st("display:inline-flex;align-items:center;gap:6px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;padding:6px 11px;border-radius:999px;border:1px dashed #c8c6bc;background:#fff;color:#006747")}>{v.icons.saveSm} Save view</button>
              <button onClick={() => this.openExport()} style={st("display:inline-flex;align-items:center;gap:6px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;padding:7px 13px;border-radius:8px;border:0;background:#006747;color:#fff")}>{v.icons.downloadSm} Export CSV</button>
            </div>
            <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);overflow:hidden")}>
              <div style={st(`display:grid;grid-template-columns:${gcols};gap:8px;padding:11px 16px;background:#faf9f5;border-bottom:1px solid #eee`)}>{v.gridCols.map((col, i) => <div key={i} className="th-sort" onClick={col.onSort} style={st(`font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:${col.color};display:flex;align-items:center;gap:3px;justify-content:${col.justify}`)}>{col.label}{col.caret}</div>)}</div>
              {v.hasResults && v.gridRows.map((r) => (
                <div key={r.key} className="pv-row" onClick={r.onClick} style={st(`display:grid;grid-template-columns:${gcols};gap:8px;padding:11px 16px;border-top:1px solid #f4f3ee;align-items:center`)}>
                  <div style={st("display:flex;align-items:center;gap:10px;min-width:0")}><div style={st("width:30px;height:30px;border-radius:50%;background:#e7e1d4;color:#006747;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;flex:none")}>{r.initials}</div><div style={st("min-width:0")}><div style={st("font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{r.name}</div><div style={st("display:flex;align-items:center;gap:6px")}><span style={st("font-size:10.5px;color:#a8aca6;font-family:'Space Mono',monospace")}>{r.mls}</span><span style={st(r.statusStyle)}>{r.status}</span></div></div></div>
                  <div style={st("font-size:12px;color:#4a504a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{r.office}</div>
                  <div style={st("font-size:12px;color:#6b706a")}>{r.tenureLabel}</div>
                  <div style={st("font-family:'Space Mono',monospace;font-size:13px;text-align:right")}>{r.activeListings}</div>
                  <div style={st("font-family:'Space Mono',monospace;font-size:13px;font-weight:700;text-align:right")}>{r.closedSides}</div>
                  <div style={st("font-family:'Space Mono',monospace;font-size:13px;text-align:right")}>{r.showingsHosted}</div>
                  <div style={st("font-family:'Space Mono',monospace;font-size:13px;text-align:right;color:#6b706a")}>{r.offers}</div>
                  <div style={st("display:flex;gap:3px;justify-content:center")}>{r.adoptStrip}</div>
                  <div style={st("font-size:11.5px;color:#a8aca6;text-align:right")}>{r.lastSeen}</div>
                </div>
              ))}
              {v.noResults && <div style={st("padding:56px 20px;text-align:center")}><div style={st("font-size:14px;font-weight:600;color:#6b706a")}>No members match this cohort</div><button onClick={() => this.clearFilters()} style={st("margin-top:12px;font-family:inherit;font-size:13px;font-weight:600;color:#006747;background:none;border:1px solid #d3e4db;border-radius:7px;padding:8px 16px;cursor:pointer")}>Clear filters</button></div>}
            </div>
            <div style={st("display:flex;align-items:center;justify-content:space-between;margin-top:12px;font-size:12px;color:#a8aca6")}><span>Showing 1–{v.pageShown} · 25 per page</span><span>Last seen is warming — approximate until sign-in capture matures.</span></div>
          </div>
        </div>
      </div>
    );
  }

  viewProfile(v) {
    const m = v.member;
    return (
      <div style={st("padding:22px 28px 20px;max-width:1200px;margin:0 auto")}>
        <div style={st("display:flex;align-items:center;gap:8px;font-size:12px;color:#a8aca6;margin-bottom:14px")}><a href="#" onClick={(e) => { e.preventDefault(); this.goOverview(); }}>Member Insights</a> {v.icons.crumbChev} <a href="#" onClick={(e) => { e.preventDefault(); this.goMembers(); }}>Members</a> {v.icons.crumbChev} <span style={st("color:#6b706a;font-weight:600")}>{m.name}</span></div>
        <div className="mx-split" style={st("display:grid;grid-template-columns:320px 1fr;gap:18px;align-items:start")}>
          <div style={st("display:flex;flex-direction:column;gap:14px")}>
            <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:20px")}>
              <div style={st("display:flex;align-items:center;gap:14px")}><div style={st("width:56px;height:56px;border-radius:50%;background:#006747;color:#fff;display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:600;flex:none")}>{m.initials}</div><div><div style={st("font-size:18px;font-weight:600;letter-spacing:-0.3px")}>{m.name}</div><div style={st("display:flex;align-items:center;gap:7px;margin-top:4px")}><span style={st("font-family:'Space Mono',monospace;font-size:11px;color:#a8aca6")}>{m.mls}</span><span style={st(m.statusStyle)}>{m.status}</span></div></div></div>
              <div style={st("display:flex;flex-direction:column;gap:9px;margin-top:18px;font-size:12.5px")}>
                <div style={st("display:flex;align-items:center;gap:9px;color:#4a504a")}>{v.icons.building}<a href="#" onClick={(e) => { e.preventDefault(); m.onOffice(); }}>{m.office}</a></div>
                <div style={st("display:flex;align-items:center;gap:9px;color:#4a504a")}>{v.icons.mail}{m.email}</div>
                <div style={st("display:flex;align-items:center;gap:9px;color:#4a504a")}>{v.icons.phone}{m.phone}</div>
                <div style={st("display:flex;align-items:center;gap:9px;color:#4a504a")}>{v.icons.calSm}Member since {m.joinedLabel}</div>
                <div style={st("display:flex;align-items:center;gap:9px;color:#a8aca6")}>{v.icons.idCard}NRDS {m.nrds}</div>
              </div>
              <div style={st("display:flex;gap:8px;margin-top:16px")}><button style={st("flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;padding:8px;border-radius:7px;border:0;background:#006747;color:#fff")}>{v.icons.msgWhite} Message</button><button onClick={() => m.onOffice()} style={st("flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;padding:8px;border-radius:7px;border:1px solid #d7d4c9;background:#fff;color:#006747")}>{v.icons.building2} Office</button></div>
            </div>
            <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:16px 18px")}>
              <div style={st("display:flex;align-items:center;justify-content:space-between")}><h3 style={st("margin:0;font-size:13px;font-weight:600")}>Licensing</h3><span style={st(m.licPillStyle)}>{m.licPill}</span></div>
              <div style={st("display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:14px;font-size:12.5px")}><div><div style={st("font-size:10px;color:#a8aca6;text-transform:uppercase;letter-spacing:.5px")}>License #</div><div style={st("font-family:'Space Mono',monospace;margin-top:3px")}>{m.license.no}</div></div><div><div style={st("font-size:10px;color:#a8aca6;text-transform:uppercase;letter-spacing:.5px")}>Type</div><div style={st("margin-top:3px")}>{m.license.type}</div></div><div><div style={st("font-size:10px;color:#a8aca6;text-transform:uppercase;letter-spacing:.5px")}>Status</div><div style={st("margin-top:3px;text-transform:capitalize")}>{m.license.status}</div></div><div><div style={st("font-size:10px;color:#a8aca6;text-transform:uppercase;letter-spacing:.5px")}>Expires</div><div style={st(`font-family:'Space Mono',monospace;margin-top:3px;color:${m.licExpColor}`)}>{m.license.expires}</div></div></div>
            </div>
            <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:16px 18px")}>
              <h3 style={st("margin:0 0 4px;font-size:13px;font-weight:600")}>Office history</h3>
              <div style={st("font-size:11px;color:#a8aca6;margin-bottom:12px")}>Begins {v.warmMoves} — the day Recursive started tracking moves.</div>
              {m.officeHistory.map((oh, i) => <div key={i} style={st("display:flex;gap:11px")}><div style={st("display:flex;flex-direction:column;align-items:center;flex:none")}><span style={st(`width:10px;height:10px;border-radius:50%;background:${oh.dot};margin-top:3px`)} /><span style={st("width:2px;flex:1;background:#ece9e0;margin:3px 0")} /></div><div style={st("padding-bottom:14px")}><div style={st("font-size:13px;font-weight:600")}>{oh.office}</div><div style={st("font-size:11.5px;color:#a8aca6")}>{oh.span}</div></div></div>)}
            </div>
          </div>
          <div style={st("min-width:0;display:flex;flex-direction:column;gap:14px")}>
            <div style={st("display:grid;grid-template-columns:repeat(6,1fr);gap:10px")}>{m.kpiStrip.map((k, i) => <div key={i} style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:12px 13px")}><div style={st("font-family:'Space Mono',monospace;font-size:20px;font-weight:700;letter-spacing:-0.5px")}>{k.value}</div><div style={st("font-size:10px;color:#8a8e88;margin-top:4px;line-height:1.3")}>{k.label}</div></div>)}</div>
            <div className="mi-2col" style={st("display:grid;grid-template-columns:1fr 1fr;gap:14px")}>
              <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:15px 18px 12px")}><h3 style={st("margin:0 0 10px;font-size:13px;font-weight:600")}>Listings activity</h3>{m.listingsChart}<div style={st("display:flex;gap:14px;margin-top:8px")}><span style={st("display:inline-flex;align-items:center;gap:6px;font-size:11px;color:#6b706a")}><span style={st("width:12px;height:9px;background:#006747;border-radius:2px")} />New</span><span style={st("display:inline-flex;align-items:center;gap:6px;font-size:11px;color:#6b706a")}><span style={st("width:12px;height:9px;background:#c0612f;border-radius:2px")} />Closed</span></div></div>
              <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:15px 18px 12px")}><h3 style={st("margin:0 0 10px;font-size:13px;font-weight:600")}>Showings</h3>{m.showingsChart}<div style={st("display:flex;gap:14px;margin-top:8px")}><span style={st("display:inline-flex;align-items:center;gap:6px;font-size:11px;color:#6b706a")}><span style={st("width:12px;height:9px;background:#006747;border-radius:2px")} />Hosted</span><span style={st("display:inline-flex;align-items:center;gap:6px;font-size:11px;color:#6b706a")}><span style={st("width:12px;height:9px;background:#2a6f8a;border-radius:2px")} />Requested</span></div></div>
            </div>
            <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:16px 18px")}>
              <h3 style={st("margin:0 0 14px;font-size:13px;font-weight:600")}>Platform engagement</h3>
              <div style={st("display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:16px")}>
                <div style={st("background:#f7f6f1;border-radius:7px;padding:11px 12px")}><div style={st("font-family:'Space Mono',monospace;font-size:17px;font-weight:700")}>{m.lmsCompleted}</div><div style={st("font-size:10.5px;color:#a8aca6;margin-top:3px")}>LMS courses</div></div>
                <div style={st("background:#f7f6f1;border-radius:7px;padding:11px 12px")}><div style={st("font-family:'Space Mono',monospace;font-size:17px;font-weight:700")}>{m.ceCredits}</div><div style={st("font-size:10.5px;color:#a8aca6;margin-top:3px")}>CE credits</div></div>
                <div style={st("background:#f7f6f1;border-radius:7px;padding:11px 12px")}><div style={st("font-family:'Space Mono',monospace;font-size:17px;font-weight:700")}>{m.tickets}</div><div style={st("font-size:10.5px;color:#a8aca6;margin-top:3px")}>Support tickets</div></div>
                <div style={st("background:#f7f6f1;border-radius:7px;padding:11px 12px")}><div style={st("font-family:'Space Mono',monospace;font-size:17px;font-weight:700;color:#9a6a12")}>{m.lastSeen}</div><div style={st("font-size:10.5px;color:#a8aca6;margin-top:3px")}>Last seen · approx</div></div>
              </div>
              <div style={st("display:flex;flex-wrap:wrap;gap:8px")}>{m.adoptChecklist.map((a, i) => <span key={i} style={st("display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:500;padding:6px 11px;border-radius:999px;" + a.style)}>{a.mark}{a.label}</span>)}</div>
            </div>
          </div>
        </div>
      </div>
    );
  }

  viewOffices(v) {
    return (
      <div style={st("padding:22px 28px 20px;max-width:1360px;margin:0 auto")}>
        <div style={st("display:flex;align-items:center;gap:8px;font-size:12px;color:#a8aca6;margin-bottom:6px")}><a href="#" onClick={(e) => { e.preventDefault(); this.goOverview(); }}>Member Insights</a> {v.icons.crumbChev} <span style={st("color:#6b706a;font-weight:600")}>Offices</span></div>
        <h1 style={st("margin:0 0 16px;font-size:24px;font-weight:600;letter-spacing:-0.5px")}>Office insights</h1>
        <div className="mi-2col" style={st("display:grid;grid-template-columns:1.15fr 1fr;gap:14px;margin-bottom:14px")}>
          <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:15px 18px 16px")}>
            <div style={st("display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px")}><h3 style={st("margin:0;font-size:14px;font-weight:600")}>Office leaderboard</h3><div style={st("display:inline-flex;background:#f1f0ea;border-radius:8px;padding:2px")}>{v.officeMetricTabs.map((m, i) => <button key={i} onClick={m.onClick} style={st(m.style)}>{m.label}</button>)}</div></div>
            <div style={st("display:flex;flex-direction:column;gap:9px;margin-top:16px")}>{v.officeLeaderboard.map((o, i) => <div key={i} className="pv-row" onClick={o.onClick} style={st("display:flex;align-items:center;gap:11px;padding:6px;border-radius:7px")}><span style={st("font-family:'Space Mono',monospace;font-size:12px;font-weight:700;color:#c0c3bd;width:16px;flex:none")}>{o.rank}</span><span style={st("width:150px;flex:none;font-size:12.5px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{o.name}</span><div style={st("flex:1;height:16px;background:#f1f0ea;border-radius:4px;overflow:hidden")}><div style={st(`height:100%;width:${o.barPct}%;background:#006747;border-radius:4px`)} /></div><span style={st("font-family:'Space Mono',monospace;font-size:12.5px;font-weight:700;width:60px;text-align:right;flex:none")}>{o.metricVal}</span><span style={st(`font-size:11px;font-weight:600;color:${o.deltaColor};width:42px;text-align:right;flex:none`)}>{o.delta}</span></div>)}</div>
          </div>
          <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:15px 18px 16px")}>
            <div style={st("display:flex;align-items:center;gap:9px;margin-bottom:2px")}><h3 style={st("margin:0;font-size:14px;font-weight:600")}>Migration — net flow</h3><span style={st("font-size:10px;font-weight:600;color:#9a6a12;background:#fbf2dc;border-radius:999px;padding:2px 8px")}>Warming</span></div>
            <div style={st("display:flex;justify-content:space-between;font-size:11px;color:#a8aca6;margin:8px 0 6px")}><span>← Losses</span><span>net</span><span>Gains →</span></div>
            <div style={st("display:flex;flex-direction:column;gap:7px")}>{v.netFlowRows.map((n, i) => <div key={i} style={st("display:flex;align-items:center;gap:8px")}><span style={st("width:118px;flex:none;font-size:11.5px;color:#4a504a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis")}>{n.name}</span><div style={st("flex:1;display:flex;align-items:center;height:16px")}><div style={st("flex:1;display:flex;justify-content:flex-end")}><div onClick={n.onLosses} style={st(`height:14px;width:${n.lossPct}%;background:#c0612f;border-radius:3px 0 0 3px;cursor:pointer`)} /></div><div style={st("width:1px;height:18px;background:#d6d4cb;flex:none")} /><div style={st("flex:1")}><div onClick={n.onGains} style={st(`height:14px;width:${n.gainPct}%;background:#1f8a5b;border-radius:0 3px 3px 0;cursor:pointer`)} /></div></div><span style={st(`font-family:'Space Mono',monospace;font-size:12px;font-weight:700;color:${n.netColor};width:34px;text-align:right;flex:none`)}>{n.netLabel}</span></div>)}</div>
          </div>
        </div>
        <div className="mi-2col" style={st("display:grid;grid-template-columns:1fr 1fr;gap:14px")}>
          <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:15px 18px 10px")}>
            <h3 style={st("margin:0 0 4px;font-size:14px;font-weight:600")}>Top corridors</h3><div style={st("font-size:11.5px;color:#a8aca6;margin-bottom:8px")}>where movers go · trailing 12m</div>
            {v.corridors.map((c, i) => <div key={i} className="rc-row" style={st("display:flex;align-items:center;gap:8px;padding:9px 4px;border-top:1px solid #f4f3ee;font-size:12.5px")}><a href="#" onClick={c.onFrom} style={st("color:#c0612f;font-weight:600")}>{c.fromName}</a><span style={st("color:#bcbfba")}>→</span><a href="#" onClick={c.onTo} style={st("color:#1f8a5b;font-weight:600;flex:1")}>{c.toName}</a><span style={st("font-family:'Space Mono',monospace;font-weight:700")}>{c.n}</span><span style={st("color:#a8aca6;font-size:11px")}>members</span></div>)}
          </div>
          <div style={st("background:#fff;border-radius:3px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.07);padding:15px 18px 8px")}>
            <div style={st("display:flex;align-items:center;gap:9px;margin-bottom:8px")}><h3 style={st("margin:0;font-size:14px;font-weight:600")}>Recent moves</h3><span style={st("font-size:10px;font-weight:600;color:#9a6a12;background:#fbf2dc;border-radius:999px;padding:2px 8px")}>Warming</span></div>
            {v.movesFeed.map((mv, i) => <div key={i} className="rc-row" style={st("display:flex;align-items:center;gap:10px;padding:8px 4px;border-top:1px solid #f4f3ee")}><div style={st("width:26px;height:26px;border-radius:50%;background:#e7e1d4;color:#006747;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;flex:none")}>{mv.initials}</div><div style={st("flex:1;min-width:0;font-size:12.5px;color:#4a504a;line-height:1.35")}><a href="#" onClick={mv.onMember} style={st("font-weight:600;color:#1d241f")}>{mv.member}</a> · <a href="#" onClick={mv.onFrom} style={st("color:#c0612f")}>{mv.from}</a> <span style={st("color:#bcbfba")}>→</span> <a href="#" onClick={mv.onTo} style={st("color:#1f8a5b")}>{mv.to}</a></div><span style={st("font-size:11px;color:#a8aca6;flex:none")}>{mv.date}</span></div>)}
          </div>
        </div>
      </div>
    );
  }

  viewBuilder(v) {
    const b = v.builder;
    return (
      <div onClick={() => this.cancelBuilder()} style={st("position:fixed;inset:0;background:rgba(29,36,31,.42);z-index:45;display:flex;align-items:center;justify-content:center;padding:20px")}>
        <div onClick={(e) => e.stopPropagation()} style={st("width:640px;max-width:100%;max-height:88vh;overflow-y:auto;background:#fff;border-radius:12px;box-shadow:0 24px 60px rgba(0,0,0,.28)")}>
          <div style={st("display:flex;align-items:center;gap:11px;padding:18px 22px;border-bottom:1px solid #f1f0ea;position:sticky;top:0;background:#fff;z-index:2")}>
            <span style={st("width:34px;height:34px;border-radius:8px;background:#eef4f1;display:flex;align-items:center;justify-content:center")}>{v.icons.widget}</span>
            <div style={st("flex:1")}><h3 style={st("margin:0;font-size:16px;font-weight:600")}>{b.heading}</h3><div style={st("font-size:12px;color:#a8aca6;margin-top:1px")}>{b.sub}</div></div>
            <button onClick={() => this.cancelBuilder()} style={st("background:none;border:0;cursor:pointer;display:flex;padding:4px")}>{v.icons.xClose}</button>
          </div>
          {b.stepType && (
            <div style={st("padding:18px 22px")}>
              <div style={st("font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.7px;color:#8a8e88;margin-bottom:11px")}>Building blocks</div>
              <div style={st("display:grid;grid-template-columns:1fr 1fr;gap:10px")}>{b.blockTypes.map((t, i) => <button key={i} className="type-card" onClick={t.onClick} style={st("text-align:left;display:flex;gap:12px;align-items:flex-start;font-family:inherit;background:#fff;border:1px solid #e2e0d8;border-radius:9px;padding:13px 14px;cursor:pointer")}><span style={st("width:32px;height:32px;border-radius:7px;background:#eef4f1;display:flex;align-items:center;justify-content:center;flex:none")}>{t.icon}</span><span style={st("min-width:0")}><span style={st("display:block;font-size:13px;font-weight:600;color:#1d241f")}>{t.label}</span><span style={st("display:block;font-size:11.5px;color:#8a8e88;margin-top:2px;line-height:1.35")}>{t.desc}</span></span></button>)}</div>
              <div style={st("font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.7px;color:#8a8e88;margin:18px 0 11px")}>Prebuilt panels</div>
              <div style={st("display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px")}>{b.prebuiltTypes.map((t, i) => <button key={i} className="type-card" onClick={t.onClick} style={st("text-align:left;display:flex;gap:10px;align-items:center;font-family:inherit;background:#fff;border:1px solid #e2e0d8;border-radius:9px;padding:11px 12px;cursor:pointer")}><span style={st("width:28px;height:28px;border-radius:7px;background:#eef4f1;display:flex;align-items:center;justify-content:center;flex:none")}>{t.icon}</span><span style={st("font-size:12.5px;font-weight:600;color:#1d241f")}>{t.label}</span></button>)}</div>
            </div>
          )}
          {b.stepConfig && (
            <div style={{ display: "contents" }}>
              <div style={st("padding:18px 22px;display:flex;flex-direction:column;gap:16px")}>
                <div style={st("display:flex;align-items:center;gap:9px;background:#f7f6f1;border-radius:8px;padding:10px 13px")}><span style={st("width:28px;height:28px;border-radius:7px;background:#eef4f1;display:flex;align-items:center;justify-content:center;flex:none")}>{b.typeIcon}</span><div style={st("flex:1")}><div style={st("font-size:12.5px;font-weight:600")}>{b.typeLabel}</div><div style={st("font-size:11px;color:#a8aca6")}>{b.typeDesc}</div></div>{b.isNew && <button onClick={() => this.backToType()} style={st("font-family:inherit;font-size:12px;font-weight:600;color:#006747;background:none;border:0;cursor:pointer")}>Change</button>}</div>
                {b.fields.map((f, i) => (
                  <div key={i}>
                    <div style={st("font-size:11.5px;font-weight:600;color:#4a504a;margin-bottom:7px")}>{f.label}</div>
                    {f.isSelect && <select value={f.value} onChange={f.onChange} style={st("width:100%;border:1px solid #d7d4c9;border-radius:7px;padding:9px 11px;font-size:13px;color:#1d241f;background:#fff;cursor:pointer")}>{f.options.map((o, j) => <option key={j} value={o.value}>{o.label}</option>)}</select>}
                    {f.isChips && <div style={st("display:flex;gap:7px;flex-wrap:wrap")}>{f.chips.map((c, j) => <button key={j} onClick={c.onClick} style={st(c.style)}>{c.label}</button>)}</div>}
                    {f.isToggle && <button onClick={f.onToggle} style={st(f.toggleStyle)}><span style={st(`width:30px;height:18px;border-radius:999px;background:${f.toggleTrack};position:relative;transition:background .12s;flex:none`)}><span style={st(`position:absolute;top:2px;left:${f.knobLeft};width:14px;height:14px;border-radius:50%;background:#fff;transition:left .12s`)} /></span>{f.toggleLabel}</button>}
                  </div>
                ))}
                <div style={st("background:#f7f6f1;border-radius:8px;padding:12px 13px")}><div style={st("font-size:11px;font-weight:600;color:#a8aca6;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px")}>Preview</div>{b.preview}</div>
              </div>
              <div style={st("padding:14px 22px;background:#faf9f5;display:flex;justify-content:space-between;align-items:center;gap:10px;position:sticky;bottom:0;border-top:1px solid #f1f0ea")}><div style={st("font-size:11.5px;color:#a8aca6")}>Saved to “{v.dashName}”</div><div style={st("display:flex;gap:10px")}><button onClick={() => this.cancelBuilder()} style={st("font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:9px 16px;border-radius:8px;border:1px solid #d7d4c9;background:#fff;color:#4a504a")}>Cancel</button><button onClick={() => this.saveBuilder()} style={st("font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:9px 18px;border-radius:8px;border:0;background:#006747;color:#fff")}>{b.saveLabel}</button></div></div>
            </div>
          )}
        </div>
      </div>
    );
  }

  viewExport(v) {
    return (
      <div onClick={() => this.closeExport()} style={st("position:fixed;inset:0;background:rgba(29,36,31,.42);z-index:40;display:flex;align-items:center;justify-content:center;padding:20px")}>
        <div onClick={(e) => e.stopPropagation()} style={st("width:440px;max-width:100%;background:#fff;border-radius:12px;box-shadow:0 24px 60px rgba(0,0,0,.28);overflow:hidden")}>
          <div style={st("padding:20px 22px 0")}><div style={st("display:flex;align-items:center;gap:10px")}><span style={st("width:34px;height:34px;border-radius:8px;background:#eef4f1;display:flex;align-items:center;justify-content:center")}>{v.icons.downloadBig}</span><h3 style={st("margin:0;font-size:16px;font-weight:600")}>Export cohort to CSV</h3></div></div>
          <div style={st("padding:16px 22px")}>
            <div style={st("font-size:13px;color:#4a504a;line-height:1.55")}>You’re exporting <strong>{v.resultCount}</strong> members matching the current cohort.</div>
            <div style={st("margin-top:14px;display:flex;flex-direction:column;gap:9px")}>
              <div style={st("display:flex;align-items:flex-start;gap:9px;font-size:12.5px;color:#4a504a")}>{v.icons.checkGreen}<span>Contact details (email &amp; phone) are <strong>never</strong> included in exports.</span></div>
              <div style={st("display:flex;align-items:flex-start;gap:9px;font-size:12.5px;color:#4a504a")}>{v.icons.checkGreen}<span>This export is recorded in the access log.</span></div>
            </div>
          </div>
          <div style={st("padding:14px 22px;background:#faf9f5;display:flex;justify-content:flex-end;gap:10px")}><button onClick={() => this.closeExport()} style={st("font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:9px 16px;border-radius:8px;border:1px solid #d7d4c9;background:#fff;color:#4a504a")}>Cancel</button><button onClick={() => this.closeExport()} style={st("font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:9px 16px;border-radius:8px;border:0;background:#006747;color:#fff")}>Download CSV</button></div>
        </div>
      </div>
    );
  }

  render() {
    const v = this.vals(); if (!v.icons) return null;
    return (
      <div style={{ display: "contents" }}>
        {v.isOverview && this.viewDashboard(v)}
        {v.isMembers && this.viewExplorer(v)}
        {v.isProfile && this.viewProfile(v)}
        {v.isOffices && this.viewOffices(v)}
        <div style={st("max-width:1440px;margin:0 auto;padding:8px 28px 32px")}>
          <div style={st("display:flex;align-items:center;gap:9px;font-size:11.5px;color:#a8aca6;border-top:1px solid #ececec;padding-top:14px")}>{v.icons.shield}<span>Member Insights shows member personal and performance data. Access is limited to MLS staff · profile views and exports are logged.</span></div>
        </div>
        {v.builder.open && this.viewBuilder(v)}
        {v.exportOpen && this.viewExport(v)}
      </div>
    );
  }
}

window.MemberInsights = MemberInsights;
