/* ============================================================
   PAGE 2 — DRIVERS
   FBC leaderboard + a detail card per driver with goal lines.
   • The scorecard (leaderboard) expands an FBC across EVERY driver.
   • Each driver box expands independently — opening Conversion no
     longer opens Frequency, Retention, etc.
   • Hovering any FBC row or owner row shows a context popover with
     the raw value, a YTD trend sparkline, and the system rank.
   Drivers: Conversion · Frequency · Retention · Engagement · NPS
   ============================================================ */

/* ---- contacts pace: 10/yr goal, prorated to where we are in the fiscal year ---- */
const CONTACTS_GOAL = MM.goals.engagement && MM.goals.engagement.targetYear || 10;
const CONTACTS_PACE = MM.config.currentWeek / MM.config.weeks * CONTACTS_GOAL;
function contactsTone(c) {return c >= CONTACTS_PACE ? "great" : c >= CONTACTS_PACE * 0.6 ? "average" : "opportunity";}

/* clamp any computed percentage to the visible track [0,100] so extreme
   live values can never paint past the bar's rounded outer shape. */
const clampPct = (p) => Math.max(0, Math.min(100, p));
const isN = (v) => typeof v === "number" && !isNaN(v);

/* ---- status thresholds vs each 2026 goal (null data → neutral) ---- */
function status(driver, d) {
  switch (driver) {
    case "conversion":return !isN(d.conversionYoy) ? "info" : d.conversionYoy >= 5 ? "great" : d.conversionYoy >= 0 ? "average" : "opportunity";
    case "frequency":return !isN(d.frequencyYoy) ? "info" : d.frequencyYoy > 0 ? "great" : "opportunity";
    case "retention":{if (!isN(d.churnYoy)) return "info";const imp = -d.churnYoy;return imp >= 2 ? "great" : imp >= 0 ? "average" : "opportunity";}
    case "engagement":return d.engAll >= 80 && d.engNeg >= 100 ? "great" : d.engAll >= 70 ? "average" : "opportunity";
    case "nps":return !isN(d.npsSpring) ? "info" : d.npsSpring >= 65 ? "great" : d.npsSpring >= 55 ? "average" : "opportunity";
    default:return "info";
  }
}
const TONE_LABEL = { great: "On goal", average: "Watch", opportunity: "Off goal", info: "—" };
const sgn1 = (v) => (v >= 0 ? "+" : "") + v.toFixed(1);

/* per-owner metric config for the expansions (one place to swap).
   Every value() is null-safe: when a year's source cell is missing we
   return null (renders "—", neutral) instead of letting JS coerce the
   missing value to 0 — which previously made a missing current-year
   churn read as a huge bogus "improvement" (green). */
const OWNER_CFG = {
  conversion: { value: (o) => (o.conv2026 == null || o.conv2025 == null) ? null : o.conv2026 - o.conv2025, fmt: (v) => v == null ? "—" : sgn1(v) + " pts", tone: (v) => v == null ? "info" : v >= 5 ? "great" : v >= 0 ? "average" : "opportunity", diverging: true, label: "estimates-closed YoY change" },
  frequency: { value: (o) => (o.freq2025 == null || o.freq2026 == null || !o.freq2025) ? null : (o.freq2026 - o.freq2025) / o.freq2025 * 100, fmt: (v) => v == null ? "—" : MM.fmt.pct(v), tone: (v) => v == null ? "info" : v > 0 ? "great" : "opportunity", diverging: true, label: "cleans-per-customer YoY % change" },
  retention: { value: (o) => (o.churn2026 == null || o.churn2025 == null) ? null : -(o.churn2026 - o.churn2025), fmt: (v) => v == null ? "—" : sgn1(v) + " pts", tone: (v) => v == null ? "info" : v >= 2 ? "great" : v >= 0 ? "average" : "opportunity", diverging: true, label: "churn improvement YoY (lower churn = better)" },
  nps: { value: (o) => o.npsRating == null ? null : o.npsRating, fmt: (v) => v == null ? "—" : v + "/10 · " + (v >= 9 ? "Promoter" : v >= 7 ? "Passive" : "Detractor"), tone: (v) => v == null ? "info" : v >= 9 ? "great" : v >= 7 ? "average" : "opportunity", diverging: false, domain: [0, 10], label: "franchisee rating (0–10)" }
};

/* ---- one owner row inside an expansion (hover popover + clamped bar) ---- */
function OwnerMetricRow({ o, v, cfg, driver, onPickOwner, lo, hi, zero }) {
  const { open, pos, bind } = useHoverPop();
  const span = (hi - lo) || 1;
  const xPct = (val) => clampPct((val - lo) / span * 100);
  const hasVal = isN(v);
  const tone = cfg.tone(v);
  let left = 0, width = 0;
  if (hasVal) {
    const x = xPct(v);
    if (cfg.diverging) {if (v >= 0) {left = zero;width = x - zero;} else {left = x;width = zero - x;}} else
    {left = 0;width = x;}
    if (left < 0) {width += left;left = 0;}
    if (left + width > 100) width = 100 - left;
  }
  return (
    <button className="orow owner-pick" type="button" {...bind} onClick={() => onPickOwner(o)}
    aria-label={"Open owner details for " + o.contact + " in " + o.city + ", " + o.state}>
      <span className="oname">{o.contact}<span className="ocontact">{o.city}, {o.state}</span></span>
      <span className="otrack">
        {cfg.diverging && <span className="ozero" style={{ left: zero + "%" }}></span>}
        {hasVal && <span className={"ofill grad-" + tone} style={{ left: left + "%", width: Math.max(1.5, width) + "%" }}></span>}
      </span>
      <span className={"oval tone-" + tone}>{cfg.fmt(v)}</span>
      {open && <MetricPopover pos={pos} driver={driver} kind="owner" entity={o} />}
    </button>);

}

/* ---- per-FBC owner expansion (shared by all driver cards) ---- */
function OwnerExpansion({ fbcId, driver, onPickOwner }) {
  const cfg = OWNER_CFG[driver];
  const rows = MM.owners.filter((o) => o.fbcId === fbcId).map((o) => ({ o, v: cfg.value(o) }));
  const nums = rows.map((r) => r.v).filter(isN);
  // numeric rows sorted best→worst; null-data rows fall to the bottom
  rows.sort((a, b) => {
    const an = isN(a.v), bn = isN(b.v);
    if (an && bn) return b.v - a.v;
    return an ? -1 : bn ? 1 : 0;
  });
  let lo, hi;
  if (cfg.domain) {lo = cfg.domain[0];hi = cfg.domain[1];} else
  {const m = Math.max(0.0001, ...(nums.length ? nums.map((v) => Math.abs(v)) : [0.0001]));lo = -m;hi = m;}
  const span = (hi - lo) || 1;
  const zero = cfg.diverging ? clampPct((0 - lo) / span * 100) : 0;
  return (
    <div className="hbar-owners">
      <div className="hbar-owners-head">{rows.length} owners · sorted by {cfg.label}</div>
      <div className="orows">
        {rows.map(({ o, v }) =>
        <OwnerMetricRow key={o.id} o={o} v={v} cfg={cfg} driver={driver} onPickOwner={onPickOwner} lo={lo} hi={hi} zero={zero} />
        )}
      </div>
    </div>);

}

/* ---- a single FBC ranking row (hover popover + clamped bar + expansion) ---- */
function FbcBarRow({ it, lo, hi, zero, diverging, target, targets, format, driver, isOpen, onToggle, onPickOwner, height }) {
  const { open, pos, bind } = useHoverPop();
  const span = (hi - lo) || 1;
  const xPct = (v) => clampPct((v - lo) / span * 100);
  const x = xPct(it.value);
  let left, width;
  if (diverging) {if (it.value >= 0) {left = zero;width = x - zero;} else {left = x;width = zero - x;}} else
  {left = 0;width = x;}
  if (left < 0) {width += left;left = 0;}
  if (left + width > 100) width = 100 - left;
  return (
    <React.Fragment>
      <button className={"hbar-row clickable" + (isOpen ? " open" : "")} style={{ height }}
      {...bind} onClick={() => onToggle(it.id)} aria-expanded={isOpen}>
        <span className="hbar-name">
          <span className="hbar-caret">{isOpen ? "▾" : "▸"}</span>
          <span className="fbcdot" style={{ background: it.color }}></span>{it.name}</span>
        <span className="hbar-track">
          {diverging && <span className="hbar-zero" style={{ left: zero + "%" }}></span>}
          <span className={"hbar-fill grad-" + it.tone} style={{ left: left + "%", width: Math.max(0, width) + "%" }}></span>
          {(targets || (target != null ? [target] : [])).map((t, i) =>
          <span className="hbar-target" key={i} style={{ left: xPct(t) + "%" }} title={"2026 goal for this point in the year: " + format(t)}></span>
          )}
        </span>
        <span className={"hbar-val tone-" + it.tone}>{format(it.value)}</span>
        {open && <MetricPopover pos={pos} driver={driver} kind="fbc" entity={it} />}
      </button>
      {isOpen && <OwnerExpansion fbcId={it.id} driver={driver} onPickOwner={onPickOwner} />}
    </React.Fragment>);

}

/* ---- horizontal bar ranking with a goal line + expandable rows ---- */
function HBarRank({ items, domain, target, targets, format, diverging, driver, open, onToggle, onPickOwner, height = 26 }) {
  const [lo, hi] = domain;
  const span = (hi - lo) || 1;
  const zero = diverging ? clampPct((0 - lo) / span * 100) : 0;
  return (
    <div className="hbar">
      {items.map((it) =>
      <FbcBarRow key={it.id} it={it} lo={lo} hi={hi} zero={zero} diverging={diverging}
      target={target} targets={targets} format={format} driver={driver}
      isOpen={open === it.id} onToggle={onToggle} onPickOwner={onPickOwner} height={height} />
      )}
    </div>);

}

/* ---- driver detail card ---- */
function DriverCard({ eyebrow, title, info, goalText, driver, drivers, valueKey, domain, target, targets, format, diverging, open, onToggle, onPickOwner }) {
  const items = drivers.
  map((d) => ({ id: d.id, name: d.name, color: d.color, value: d[valueKey], tone: status(driver, d), full: d })).
  sort((a, b) => b.value - a.value);
  const onGoal = drivers.filter((d) => status(driver, d) === "great").length;
  return (
    <Card eyebrow={eyebrow} title={title} info={info}
    right={<Pill tone={onGoal >= 4 ? "great" : onGoal >= 2 ? "average" : "opportunity"} soft>{onGoal + " / 7 on goal"}</Pill>}>
      <div className="goal-note"><span className="goal-note-lab">2026 Goal</span>{goalText}</div>
      <p className="expand-hint">Click any FBC to expand their full book of owners · hover any row for trend &amp; rank.</p>
      <HBarRank items={items} domain={domain} target={target} targets={targets} format={format} diverging={diverging} driver={driver} open={open} onToggle={onToggle} onPickOwner={onPickOwner} />
    </Card>);

}

/* ---- leaderboard (expands an FBC across EVERY driver section) ---- */
function Leaderboard({ drivers, onPickFbc }) {
  const DRV = ["conversion", "frequency", "retention", "engagement", "nps"];
  const scored = drivers.map((d) => {
    const greats = DRV.filter((k) => status(k, d) === "great").length;
    const watch = DRV.filter((k) => status(k, d) === "average").length;
    return { ...d, greats, watch, score: greats * 2 + watch };
  }).sort((a, b) => b.revYoyPct - a.revYoyPct || b.score - a.score);
  const maxRev = Math.max(...drivers.map((d) => d.revYtd));

  // whole-system aggregate — same metrics & targets, scored against every owner
  const sys = (() => {
    const pool = MM.owners,n = pool.length || 1;
    const a = (k) => pool.reduce((s, o) => s + o[k], 0) / n;
    const negYoy = pool.filter((o) => o.behindYoy);
    const onPaceAll = pool.filter((o) => o.contactsOnPace).length;
    const onPaceNeg = negYoy.filter((o) => o.contactsOnPace).length;
    const ytd26 = pool.reduce((s, o) => s + o.ytd2026, 0);
    const ytd25 = pool.reduce((s, o) => s + o.ytd2025, 0);
    return {
      revYtd: ytd26, revYoyPct: ytd25 ? (ytd26 - ytd25) / ytd25 * 100 : 0,
      conversionYoy: a("conv2026") - a("conv2025"),
      frequencyYoy: a("freq2025") ? (a("freq2026") - a("freq2025")) / a("freq2025") * 100 : 0,
      churnYoy: a("churn2026") - a("churn2025"),
      engAll: onPaceAll / n * 100,
      engNeg: negYoy.length ? onPaceNeg / negYoy.length * 100 : 100,
      npsSpring: MM.fbcs.reduce((s, f) => s + f.npsSpring, 0) / MM.fbcs.length,
      owners: n
    };
  })();

  return (
    <Card eyebrow="Leaderboard" title="FBC Performance Scorecard"
    info="FBCs ranked by year-over-year sales growth. Driver score remains supplemental: each dot shows status at a glance — green on goal, amber watch, red off goal. Clicking a row expands that FBC's owners across every driver below.">
      <div className="lb">
        <div className="lb-head">
          <span className="lb-c-rank">#</span>
          <span className="lb-c-name">FBC</span>
          <span className="lb-c-rev">Revenue YTD / YoY Growth <InfoDot text="2026 year-to-date revenue across this FBC's book of owners. Row order is based on the year-over-year sales growth percentage." /></span>
          <span className="lb-c-drv">Conv</span>
          <span className="lb-c-drv">Freq</span>
          <span className="lb-c-drv">Reten</span>
          <span className="lb-c-drv">Engage</span>
          <span className="lb-c-drv">NPS</span>
        </div>
        <div className="lb-row system">
          <span className="lb-c-rank"><span className="lb-sysbadge"><img src="assets/molly-maid-lotus-mark.png" alt="" className="lb-syslogo" /></span></span>
          <span className="lb-c-name">
            <span className="fbcdot" style={{ background: "var(--mly-navy)" }}></span>
            <span className="lb-name-txt"><span className="lb-sysname">Total System</span><span className="lb-region">All {MM.fbcs.length} FBCs · {sys.owners} FBOs</span></span>
          </span>
          <span className="lb-c-rev">
            <span className="lb-revbar"><span style={{ width: "100%", background: "var(--mly-navy)" }}></span></span>
            <span className="lb-revnum" data-comment-anchor="b566869ec8-span-184-13">{MM.fmt.money(sys.revYtd, { dp: 1 })}<span className={"lb-yoy " + (sys.revYoyPct >= 0 ? "up" : "down")}>{MM.fmt.pct(sys.revYoyPct)}</span></span>
          </span>
          {["conversion", "frequency", "retention", "engagement", "nps"].map((k) => {
            const t = status(k, sys);
            return <span className="lb-c-drv" key={k}><span className={"lb-dot tone-" + t} title={TONE_LABEL[t]}></span></span>;
          })}
        </div>
        {scored.map((d, i) =>
        <button className="lb-row" key={d.id} onClick={() => onPickFbc && onPickFbc(d.id)}>
            <span className="lb-c-rank"><span className={"lb-medal m" + (i + 1)}>{i + 1}</span></span>
            <span className="lb-c-name">
              <span className="fbcdot" style={{ background: d.color }}></span>
              <span className="lb-name-txt">{d.name}<span className="lb-region">{d.owners} FBOs</span></span>
            </span>
            <span className="lb-c-rev">
              <span className="lb-revbar"><span style={{ width: clampPct(d.revYtd / maxRev * 100) + "%", background: d.color }}></span></span>
              <span className="lb-revnum">{MM.fmt.money(d.revYtd, { dp: 1 })}<span className={"lb-yoy " + (d.revYoyPct >= 0 ? "up" : "down")}>{MM.fmt.pct(d.revYoyPct)}</span></span>
            </span>
            {["conversion", "frequency", "retention", "engagement", "nps"].map((k) => {
            const t = status(k, d);
            return <span className="lb-c-drv" key={k}><span className={"lb-dot tone-" + t} title={TONE_LABEL[t]}></span></span>;
          })}
          </button>
        )}
      </div>
      <div className="lb-legend">
        <span><span className="lb-dot tone-great"></span> On goal</span>
        <span><span className="lb-dot tone-average" data-comment-anchor="2449a4c552-span-207-15"></span> Watch</span>
        <span><span className="lb-dot tone-opportunity"></span> Off goal</span>
        <span className="lb-legend-hint">Tip: click an FBC to expand their owners across every driver below ↓</span>
      </div>
    </Card>);

}

function DriversPage({ onFocusFbc }) {
  const drivers = MM.fbcDrivers();
  const pctPt = (v) => (v >= 0 ? "+" : "") + v.toFixed(1) + " pts";
  // per-section expansion: each driver box tracks its own open FBC, so opening
  // one section never opens the others. The scorecard opens ALL of them.
  const SECTIONS = ["conversion", "frequency", "retention", "nps", "engagement"];
  const [expanded, setExpanded] = useState({});
  const [picked, setPicked] = useState(null);
  const toggleSection = (key, id) => setExpanded((cur) => ({ ...cur, [key]: cur[key] === id ? null : id }));
  const OwnerDrawer = window.OwnerDrawer;
  function onPickOwner(o) {
    setPicked(window.deriveOwner ? window.deriveOwner(o, "ytd") : o);
  }
  // leaderboard click: expand the FBC across EVERY section, then scroll down
  // to the first driver section so the user can read that FBC's owners.
  function pickFromLeaderboard(id) {
    const all = {};
    SECTIONS.forEach((k) => all[k] = id);
    setExpanded(all);
    setTimeout(() => {
      const el = document.getElementById("drivers-detail");
      if (el) {
        const y = el.getBoundingClientRect().top + window.scrollY - 76;
        window.scrollTo({ top: y, behavior: "smooth" });
      }
    }, 0);
  }

  return (
    <div className="page">
      <div className="page-intro">
        <h2 className="page-intro-h">Goal Drivers</h2>
        <p className="page-intro-p">The five performance levers behind the revenue, royalty and EBITDA goals. Each card ranks all seven FBCs against the 2026 goal line — click any FBC to expand their full book of owners, or hover any row for its trend and system rank.</p>
      </div>

      <Leaderboard drivers={drivers} onPickFbc={pickFromLeaderboard} />

      <div className="driver-grid" id="drivers-detail">
        <DriverCard
          eyebrow="Conversion" title="Estimates Closed — YoY Change"
          info="The year-over-year change in the share of estimates that convert to a sold job, averaged across each FBC's owners. Expand an FBC for every owner's individual change."
          goalText="Increase YoY total estimates closed by more than 5 percentage points."
          driver="conversion" drivers={drivers} valueKey="conversionYoy"
          domain={[-8, 12]} target={5} diverging format={pctPt} open={expanded.conversion} onToggle={(id) => toggleSection("conversion", id)} onPickOwner={onPickOwner} />

        <DriverCard
          eyebrow="Frequency" title="Monthly Cleans per Customer — YoY % Change"
          info="The year-over-year percentage change in how often the average customer is cleaned each month. The goal is any positive increase over last year. Expand an FBC for owner-level detail."
          goalText="Increase YoY Monthly Cleans per Customer — any positive change over last year."
          driver="frequency" drivers={drivers} valueKey="frequencyYoy"
          domain={[-10, 12]} target={0} diverging
          format={(v) => MM.fmt.pct(v)} open={expanded.frequency} onToggle={(id) => toggleSection("frequency", id)} onPickOwner={onPickOwner} />

        <DriverCard
          eyebrow="Retention" title="13-Week Churn — YoY Improvement"
          info="How much annualized 13-week churn improved year-over-year. Positive means churn fell (good); the goal is a 2+ point reduction. Expand an FBC for owner-level detail."
          goalText="Decrease YoY Avg Annualized 13-Week Churn by more than 2 percentage points."
          driver="retention" drivers={drivers.map((d) => ({ ...d, retImp: -d.churnYoy }))} valueKey="retImp"
          domain={[-4, 6]} target={2} diverging format={pctPt} open={expanded.retention} onToggle={(id) => toggleSection("retention", id)} onPickOwner={onPickOwner} />

        <DriverCard
          eyebrow="NPS" title="Franchisee NPS — Spring"
          info="Net Promoter Score from franchisees about their consultant. Spring is measured; the goal is 65 in Spring and 70 in Fall. Expand an FBC to see each franchisee's 0–10 rating."
          goalText="Achieve Spring NPS of 65 or higher and Fall NPS of 70 or higher."
          driver="nps" drivers={drivers} valueKey="npsSpring"
          domain={[40, 85]} targets={[65, 70]} format={(v) => Math.round(v)} open={expanded.nps} onToggle={(id) => toggleSection("nps", id)} onPickOwner={onPickOwner} />
      </div>

      {/* Engagement is special — two thresholds, so its own wide card */}
      <EngagementCard drivers={drivers} open={expanded.engagement} onToggle={(id) => toggleSection("engagement", id)} onPickOwner={onPickOwner} />

      {OwnerDrawer && <OwnerDrawer owner={picked} onClose={() => setPicked(null)} rangeLabel="YTD" />}
    </div>);

}

/* engagement: % of owners on pace to log 10 FranConnect contacts by year-end.
   Goal: 80% of ALL owners on pace, and 100% of owners with negative YTD YoY
   sales growth. Click an FBC to see each owner's contacts YTD. */
function EngFbcRow({ d, isOpen, onToggle, onPickOwner, allTone, negTone, EngBar }) {
  const { open, pos, bind } = useHoverPop();
  const it = { id: d.id, name: d.name, full: d };
  return (
    <React.Fragment>
      <button className={"eng-row clickable" + (isOpen ? " open" : "")} {...bind} onClick={() => onToggle(d.id)} aria-expanded={isOpen}>
        <span className="eng-name"><span className="hbar-caret">{isOpen ? "▾" : "▸"}</span><span className="fbcdot" style={{ background: d.color }}></span>{d.name}</span>
        <EngBar pct={d.engAll} goalLeft="80%" tone={allTone(d.engAll)} sub="of all" />
        <EngBar pct={d.engNeg} goalLeft="99.5%" tone={negTone(d.engNeg)} sub={d.negCount + " neg-YoY"} />
        {open && <MetricPopover pos={pos} driver="engagement" kind="fbc" entity={it} />}
      </button>
      {isOpen && <EngagementOwners fbcId={d.id} onPickOwner={onPickOwner} />}
    </React.Fragment>);

}

function EngagementCard({ drivers, open, onToggle, onPickOwner }) {
  const sorted = drivers.slice().sort((a, b) => b.engAll - a.engAll);
  const onGoal = drivers.filter((d) => status("engagement", d) === "great").length;
  const allTone = (p) => p >= 80 ? "great" : p >= 70 ? "average" : "opportunity";
  const negTone = (p) => p >= 100 ? "great" : p >= 80 ? "average" : "opportunity";
  const EngBar = ({ pct, goalLeft, tone, sub }) =>
  <span className="eng-bar">
      <span className="eng-track">
        <span className={"eng-fill grad-" + tone} style={{ width: clampPct(pct) + "%" }}></span>
        <span className="eng-goal-line" style={{ left: goalLeft }}></span>
      </span>
      <span className={"eng-val tone-" + tone}><span className="eng-pct">{Math.round(pct)}%</span><span className="eng-sub">{sub}</span></span>
    </span>;
  return (
    <Card eyebrow="FBO Engagement" title="FranConnect Contacts — On Pace for 10 by Year-End"
    info="The share of owners on pace to log 10 FranConnect contacts by year-end — i.e. who have already logged enough contacts for where we are in the year. Two goals: 80% of ALL owners on pace, and 100% of owners with negative 2026 YTD year-over-year sales growth. Click an FBC to see each owner's contacts YTD."
    right={<Pill tone={onGoal >= 4 ? "great" : onGoal >= 2 ? "average" : "opportunity"} soft>{onGoal + " / 7 on goal"}</Pill>}>
      <div className="goal-note"><span className="goal-note-lab">2026 Goal</span>10 contacts logged per owner by year-end — for 80%+ of all owners and 100% of owners with negative YTD YoY sales growth.</div>
      <p className="expand-hint">Click any FBC to see each owner's contacts YTD · hover any row for trend &amp; rank.</p>
      <div className="eng-grid">
        <div className="eng-col-head"><span>FBC</span><span>All owners on pace <span className="eng-goal">goal 80%</span></span><span>Negative-YoY owners on pace <span className="eng-goal">goal 100%</span></span></div>
        {sorted.map((d) =>
        <EngFbcRow key={d.id} d={d} isOpen={open === d.id} onToggle={onToggle} onPickOwner={onPickOwner}
        allTone={allTone} negTone={negTone} EngBar={EngBar} />
        )}
      </div>
    </Card>);

}

/* ---- one engagement owner row (hover popover + clamped bar) ---- */
function EngOwnerRow({ o, pacePct, pct, onPickOwner }) {
  const { open, pos, bind } = useHoverPop();
  const tone = contactsTone(o.contactsYtd);
  return (
    <button className="orow owner-pick" type="button" {...bind} onClick={() => onPickOwner(o)}
    aria-label={"Open owner details for " + o.contact + " in " + o.city + ", " + o.state}>
      <span className="oname">{o.contact}{o.behindYoy && <span className="obehind">Neg YoY</span>}<span className="ocontact">{o.city}, {o.state}</span></span>
      <span className="otrack">
        <span className="opace" style={{ left: pacePct + "%" }}></span>
        <span className={"ofill grad-" + tone} style={{ left: 0, width: Math.max(1.5, pct(o.contactsYtd)) + "%" }}></span>
      </span>
      <span className={"oval tone-" + tone}>{o.contactsYtd}<span className="oval-goal"> / 10</span></span>
      {open && <MetricPopover pos={pos} driver="engagement" kind="owner" entity={o} />}
    </button>);

}

function EngagementOwners({ fbcId, onPickOwner }) {
  const rows = MM.owners.filter((o) => o.fbcId === fbcId).
  slice().
  sort((a, b) => a.contactsYtd - b.contactsYtd); // fewest contacts first — surface the gaps
  const offPace = rows.filter((o) => !o.contactsOnPace).length;
  const pct = (c) => clampPct(c / CONTACTS_GOAL * 100);
  const pacePct = pct(CONTACTS_PACE);
  return (
    <div className="hbar-owners">
      <div className="hbar-owners-head">{offPace} of {rows.length} owners off pace · dashed line = on-pace point · goal 10/yr</div>
      <div className="orows">
        {rows.map((o) =>
        <EngOwnerRow key={o.id} o={o} pacePct={pacePct} pct={pct} onPickOwner={onPickOwner} />
        )}
      </div>
    </div>);

}

window.DriversPage = DriversPage;
