// Bylt Capital CRM — Home: the numbers, what needs you, and what changed since you were last here.
//
// Wide screens: everything on one screen — figures across the top, the lists in
// a grid that scroll inside their own cards, and Grok's updates down the right.
// Phones and tablets: the same lists, one at a time, picked from a row of counts.

const UPDATE_ICON = { accepted: "check", opened: "sms", "opened-email": "mail", bounced: "alert", offer: "offer", decline: "x", document: "file", note: "note", update: "edit", milestone: "flag", renewal: "flag", advance: "flag", lead: "funnel", application: "file", funded: "check",
  "portal-application": "file", "portal-upload": "upload", "portal-message": "mail", "portal-account": "user", portal: "user",
  reapplied: "swap", assigned: "users", import: "upload" };
const plural = (n, one, many) => `${n} ${n === 1 ? one : (many || one + "s")}`;
// "Today at 3:12 PM" → "today at 3:12 PM" for use mid-sentence; "Oct 4 at…" keeps its capital.
const lower1 = (s) => String(s || "").replace(/^(Today|Yesterday)/, (m) => m.toLowerCase());

// Loads the Home summary and keeps it fresh when the window comes back into view.
// `watching` is true while Home or the updates sheet is on screen. Only then
// does a load count as a look; otherwise it just refreshes the header badge,
// so a day spent in Deals doesn't quietly mark everything as seen.
function useHome(user, watching) {
  const [data, setData] = useState(null);
  const [error, setError] = useState("");
  const watchingRef = useRef(watching); watchingRef.current = watching;
  const latest = useRef(0);
  const load = useCallback(async () => {
    const mine = ++latest.current;
    try { const d = await API.home(!watchingRef.current); if (mine === latest.current) { setData(d); setError(""); } }
    catch (err) { if (mine === latest.current && err.status !== 401) setError(err.message); }
  }, []);
  useEffect(() => {
    if (!user) { latest.current++; setData(null); return; }
    load();
    const onShow = () => { if (document.visibilityState === "visible") load(); };
    document.addEventListener("visibilitychange", onShow);
    return () => document.removeEventListener("visibilitychange", onShow);
  }, [user && (user.id || user.email)]);
  // Opening Home or the updates sheet shows the latest, including anything just changed in a deal.
  const firstRun = useRef(true);
  useEffect(() => { if (firstRun.current) { firstRun.current = false; return; } if (user && watching) load(); }, [watching]);
  const markSeen = useCallback(async () => { await API.homeSeen(); await load(); }, []);
  // Clearing Needs-attention items (or bringing them back): shown at once, then saved and reloaded.
  const dismiss = useCallback(async (keys, restore) => {
    if (!keys.length) return;
    latest.current++;                                  // a load already on its way would bring the old list back
    setData(prev => {
      if (!prev) return prev;
      const set = new Set(prev.dismissed || []);
      for (const k of keys) { if (restore) set.delete(k); else set.add(k); }
      return { ...prev, dismissed: [...set] };
    });
    try { await API.homeDismiss(keys, restore); } finally { await load(); }
  }, []);
  return { data, error, reload: load, markSeen, dismiss };
}

// Everything the screen shows, narrowed to "my deals" when asked.
function scopeHome(data, mine, user) {
  if (!data) return null;
  const own = (x) => !mine || (x.repId && x.repId === user.id);
  const ownOrShared = (x) => !mine || !x.repId || x.repId === user.id;   // things with no owner stay visible
  const d = {
    ...data,
    offers: data.offers.filter(own), accepted: (data.accepted || []).filter(own), declines: data.declines.filter(own), funded: data.funded.filter(own),
    renewals: data.renewals.filter(own), bounced: data.bounced.filter(own), stale: data.stale.filter(own),
    grok: data.grok.filter(ownOrShared), notices: data.notices.filter(ownOrShared),
    leads: data.leads, birthdays: data.birthdays.filter(ownOrShared),
  };
  // Totals follow the same switch as the lists, so "My deals" never sits over company-wide numbers.
  if (mine) {
    const m = (data.month.reps || {})[user.id] || {};
    d.month = { ...data.month, fundedCount: m.fundedCount || 0, fundedAmount: m.fundedAmount || 0, prevFundedAmount: m.prevFundedAmount || 0, undated: 0 };
    d.pipeline = data.pipeline.map(p => { const r = (p.reps || {})[user.id] || {}; return { ...p, count: r.count || 0, amount: r.amount || 0 }; });
  }
  d.anyNew = [data.offers, data.funded, data.grok, data.notices].some(list => list.some(x => x.isNew)) || data.declines.length > 0;
  // One list of things that need a person, most urgent first.
  const attention = []
    .concat(d.accepted.map(a => ({ kind: "accepted", tone: "good", icon: "check", key: "a" + a.slug, dealId: a.dealId,
      title: `${a.biz}: client accepted the offer`, action: "Open deal",
      detail: [a.amount ? fmt.money(a.amount) : "", a.term ? (a.termCount && a.termUnit !== "days" ? fmt.term(a.termCount, a.termUnit) : `${a.term} days`) : "", a.payment ? `${fmt.money(a.payment)} ${String(a.freq).toLowerCase()}` : "", a.name ? `by ${a.name}` : "", fmt.when(a.at).replace(/^T/, "t").replace(/^Y/, "y")].filter(Boolean).join(" · ") })))
    .concat(d.bounced.map(b => ({ kind: "bounced", tone: "bad", icon: "alert", key: "b" + b.dealId + b.date, dealId: b.dealId,
      title: `${b.biz}: ${fmt.money(b.amount)} payment bounced`, action: "Open deal",
      detail: [b.date, b.reason, b.daysAgo >= 2 ? `open ${b.daysAgo} days` : "", b.openCount > 1 ? `${b.openCount} not made good yet` : "", b.count30 > 1 ? `${b.count30} bounces in 30 days` : ""].filter(Boolean).join(" · ") })))
    .concat(d.offers.filter(o => o.expiresInDays != null && o.expiresInDays >= 0 && o.expiresInDays <= 1).map(o => ({ kind: "expiring", tone: "warn", icon: "clock", key: "e" + o.subId, dealId: o.dealId,
      title: `${o.biz}: ${o.lender} ${o.status} runs out ${o.expiresInDays === 0 ? "today" : "tomorrow"}`, detail: `${fmt.money(o.amount)} at ${o.factor}${o.term ? ` · ${fmt.term(o.term, o.termUnit)}` : ""}`, action: "Open deal" })))
    .concat(d.leads.filter(l => l.hours >= 4).map(l => ({ kind: "lead", tone: "warn", icon: "funnel", key: "l" + l.id, page: "leads",
      title: `${l.biz || l.name}: new lead waiting ${l.hours >= 24 ? plural(Math.floor(l.hours / 24), "day") : plural(l.hours, "hour")}`, detail: `${l.amount ? fmt.money(l.amount) + " wanted · " : ""}${l.source}`, action: "Open leads" })))
    .concat(d.stale.map(s => ({ kind: "stale", tone: "quiet", icon: "clock", key: "s" + s.dealId + ":" + (s.last || ""), dealId: s.dealId,
      title: `${s.biz}: no activity for ${plural(s.days, "day")}`, detail: `In ${statusLabel(DEAL_STATUSES, s.status)}${s.repName ? " · " + s.repName : " · unassigned"}`, action: "Open deal" })));
  // Admins, looking at everyone: commissions nobody has entered, and lenders slow to pay.
  const c = !mine && data.commissions;
  if (c && c.late && c.late.count) attention.push({ kind: "commission", tone: "warn", icon: "clock", key: `c-late:${c.late.count}:${c.late.amount}`, page: "commissions", action: "Open commissions",
    title: `${plural(c.late.count, "lender commission")} unpaid for 30 days or more`, detail: `${fmt.money(c.late.amount)} outstanding · oldest ${plural(c.late.oldestDays, "day")}` });
  if (c && c.missing) attention.push({ kind: "commission", tone: "quiet", icon: "edit", key: `c-missing:${c.missing}`, page: "commissions", action: "Open commissions",
    title: `${plural(c.missing, "funded deal")} with no commission entered`, detail: "Payouts can't be worked out until the commission is entered." });
  // Items this person cleared stay off the list until something new happens to them (see api-home.js).
  const cleared = new Set(data.dismissed || []);
  d.attention = attention.filter(a => !cleared.has(a.key));
  d.attentionCleared = attention.filter(a => cleared.has(a.key));
  d.newOffers = d.offers.filter(o => o.isNew);
  d.newFunded = d.funded.filter(f => f.isNew);
  d.newGrok = d.grok.filter(g => g.isNew);
  d.newNotices = d.notices.filter(n => n.isNew);
  return d;
}

// ── Shared pieces ───────────────────────────────────────────────────────────
function HomeSection({ id, title, count, hint, action, children, foot, tone, on, flash }) {
  return (
    <section className={`card hsec${tone ? " t-" + tone : ""}${on ? " on" : ""}${flash ? " flash" : ""}`} id={"h-" + id} aria-label={title}>
      <div className="hsec-head">
        <h2>{title}{count ? <span className="n tnum">{count}</span> : null}</h2>
        {action || null}
      </div>
      {hint ? <p className="hsec-hint">{hint}</p> : null}
      <div className="hsec-body scroll">{children}</div>
      {foot || null}
    </section>
  );
}
function HomeRow({ icon, tone, title, detail, right, sub, onClick, isNew, label }) {
  return (
    <button className="hrow" onClick={onClick} aria-label={label}>
      {icon ? <span className={`hicon${tone ? " t-" + tone : ""}`}><Icon name={icon} size={15} /></span> : null}
      <span className="grow">
        <span className="t">{isNew ? <><i className="dot" aria-hidden="true" title="New since your last visit" /><span className="sr-only">New: </span></> : null}{title}</span>
        {detail ? <span className="d">{detail}</span> : null}
      </span>
      {right != null || sub ? <span className="r">{right != null ? <b className="serif">{right}</b> : null}{sub ? <span>{sub}</span> : null}</span> : null}
      <span className="chev"><Icon name="chev" size={15} /></span>
    </button>
  );
}
const NothingHere = ({ children }) => <div className="hnone">{children}</div>;

function OffersList({ offers, go, limit }) {
  if (!offers.length) return <NothingHere>No open offers right now.</NothingHere>;
  return offers.slice(0, limit || 99).map(o => (
    <HomeRow key={o.subId} icon="offer" isNew={o.isNew} onClick={() => go("deals", o.dealId)}
      title={<>{o.biz} <Pill status={o.status} label={statusLabel(SUB_STATUSES, o.status)} /></>}
      detail={[
        o.lender,
        o.factor ? "factor " + o.factor : "",
        o.term ? fmt.term(o.term, o.termUnit) : "",
        o.payment ? fmt.money(o.payment) + " " + String(o.freq || "daily").toLowerCase() : "",
        o.expiresAt ? (o.expiresInDays < 0 ? "ran out " : "good until ") + fmt.day(o.expiresAt) : "",
        o.createdBy === "Grok" ? "via Grok" : "",
      ].filter(Boolean).join(" · ")}
      right={fmt.money(o.amount)} />
  ));
}
function FundedList({ funded, go }) {
  if (!funded.length) return <NothingHere>Nothing funded yet this month.</NothingHere>;
  return funded.map(f => (
    <HomeRow key={f.dealId} icon="check" tone="good" isNew={f.isNew} onClick={() => go("deals", f.dealId)}
      title={f.biz} detail={`${f.lender ? "Funded by " + f.lender : "Funded"}${f.repName ? " · " + f.repName : ""} · ${lower1(fmt.when(f.fundedAt))}`}
      right={fmt.money(f.amount)} />
  ));
}
function RenewalsList({ renewals, go }) {
  if (!renewals.length) return <NothingHere>No advances are past the renewal mark yet.</NothingHere>;
  return renewals.map(r => (
    <button className="hrow renewal" key={r.advanceId || r.dealId} onClick={() => (r.clientId ? go("clients", r.clientId) : go("deals", r.dealId))}>
      <span className="grow">
        <span className="t">{r.biz}{r.paidPct >= 75 ? <span className="pill plain s-funded">Ready now</span> : null}{r.brokeredBy && r.brokeredBy !== "Bylt Capital" ? <span className="pill plain s-neutral">{r.brokeredBy}</span> : null}</span>
        <span className="meter" role="img" aria-label={`${r.paidPct}% repaid`}><i style={{ width: r.paidPct + "%" }} /></span>
        <span className="d">{r.funder ? r.funder + " · " : ""}{r.paidPct}% repaid{r.payback ? ` · ${fmt.money(r.remaining)} left of ${fmt.money(r.payback)}` : ""}{r.repName ? " · " + r.repName : ""}</span>
      </span>
      <span className="chev"><Icon name="chev" size={15} /></span>
    </button>
  ));
}
function BirthdaysList({ birthdays, go }) {
  if (!birthdays.length) return <NothingHere>No client birthdays in the next two weeks.</NothingHere>;
  return birthdays.map(b => (
    <HomeRow key={b.clientId} icon="cake" onClick={() => (b.dealId ? go("deals", b.dealId) : go("clients"))}
      title={b.owner} detail={b.biz}
      right={b.inDays === 0 ? "Today" : b.inDays === 1 ? "Tomorrow" : b.weekday} sub={b.date} />
  ));
}
function AttentionList({ items, go, onClear }) {
  if (!items.length) return <NothingHere>Nothing is waiting on you. Bounced payments, expiring offers, waiting leads and stalled deals show up here.</NothingHere>;
  return items.map(a => (
    <div className="hrow-wrap" key={a.key}>
      <HomeRow icon={a.icon} tone={a.tone} title={a.title} detail={a.detail}
        onClick={() => (a.dealId ? go("deals", a.dealId) : go(a.page))} label={`${a.title}. ${a.action}`} />
      {onClear ? <button type="button" className="icon-btn hclear" onClick={() => onClear([a.key])} aria-label={`Clear: ${a.title}`} title="Clear"><Icon name="check" size={14} /></button> : null}
    </div>
  ));
}

// Grok's changes and other notifications, newest first, new ones on top.
function UpdatesFeed({ data, go, onNavigate }) {
  const [only, setOnly] = useState("all");
  const items = (only === "grok" ? data.grok.map(g => ({ ...g, from: "Grok" })) : data.grok.map(g => ({ ...g, from: "Grok" })).concat(data.notices))
    .sort((a, b) => String(b.ts).localeCompare(String(a.ts)));
  const fresh = items.filter(i => i.isNew), older = items.filter(i => !i.isNew);
  const row = (u, i) => (
    <button className="urow" key={u.ts + i} onClick={() => { if (u.kind === "renewal" && u.clientId) { go("clients", u.clientId); onNavigate && onNavigate(); } else if (u.leadId) { go("leads", u.leadId); onNavigate && onNavigate(); } else if (u.dealId) { go("deals", u.dealId); onNavigate && onNavigate(); } else if (u.kind === "import" && u.importId) { go("import", u.importId); onNavigate && onNavigate(); } else if (u.page) { go(u.page); onNavigate && onNavigate(); } }}>
      <span className={`hicon${u.from === "Grok" ? " t-grok" : ""}`}><Icon name={u.from === "Grok" ? "spark" : (UPDATE_ICON[u.kind] || "note")} size={14} /></span>
      <span className="grow">
        <span className="t">{u.text}</span>
        <span className="d">{u.biz ? u.biz + " · " : ""}{u.from === "Grok" ? "Grok · " : ""}{fmt.when(u.ts)}</span>
      </span>
    </button>
  );
  return (
    <div className="updates">
      <div className="seg" role="group" aria-label="Show">
        <button type="button" aria-pressed={only === "all"} onClick={() => setOnly("all")}>Everything</button>
        <button type="button" aria-pressed={only === "grok"} onClick={() => setOnly("grok")}>Grok only</button>
      </div>
      {!items.length ? <NothingHere>No updates yet. Offers, notes and files Grok logs will be listed here.</NothingHere> : null}
      {fresh.length ? <><div className="label ulabel">New since your last visit</div>{fresh.map(row)}</> : null}
      {older.length ? <><div className="label ulabel">Earlier</div>{older.map(row)}</> : null}
    </div>
  );
}

// The updates feed as a sheet, opened from the bell (phones and narrower screens).
function UpdatesSheet({ data, go, onClose }) {
  const closeBtn = useRef(null);
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    const prev = document.activeElement;
    if (closeBtn.current) closeBtn.current.focus();
    return () => { document.removeEventListener("keydown", onKey); if (prev && prev.focus && document.contains(prev)) prev.focus(); };
  }, []);
  return (
    <>
      <div className="scrim" onClick={onClose} />
      <div className="sheet" role="dialog" aria-modal="true" aria-label="Updates">
        <div className="sheet-head">
          <div><div className="label">Grok and notifications</div><h2>Updates</h2></div>
          <button type="button" className="icon-btn" onClick={onClose} aria-label="Close" ref={closeBtn}><Icon name="x" /></button>
        </div>
        <div className="sheet-body" style={{ padding: "14px 16px 24px" }}>
          {data ? <UpdatesFeed data={data} go={go} onNavigate={onClose} /> : <NothingHere>Loading…</NothingHere>}
        </div>
      </div>
    </>
  );
}

// ── Home ────────────────────────────────────────────────────────────────────
function HomeView({ user, home, go, toast }) {
  // Someone who only sees their own deals has nothing to switch between.
  const ownOnly = user.sees === "own";
  const [mine, setMine] = useState(!ownOnly && user.role === "Rep");
  const [busy, setBusy] = useState(false);
  const [picked, setPicked] = useState(null);     // the list showing on phones and tablets
  const [flash, setFlash] = useState(null);
  const flashTimer = useRef(null);
  useEffect(() => () => clearTimeout(flashTimer.current), []);
  const d = useMemo(() => scopeHome(home.data, mine, user), [home.data, mine, user.id]);

  if (home.error && !d) return <main className="workspace single"><div className="page"><div className="banner" role="alert">Couldn't load your overview: {home.error} <button className="link-btn" onClick={home.reload}>Try again</button></div></div></main>;
  if (!d) return <main className="workspace single"><div className="page"><div className="empty"><p>Loading your overview…</p></div></div></main>;

  const hour = new Date().getHours();
  const hello = hour < 12 ? "Good morning" : hour < 18 ? "Good afternoon" : "Good evening";
  const first = String(user.name || "").trim().split(/\s+/)[0];
  const bits = [
    d.newOffers.length ? plural(d.newOffers.length, "new offer") : "",
    d.newFunded.length ? plural(d.newFunded.length, "deal") + " funded" : "",
    d.declines.length ? plural(d.declines.length, "decline") : "",
    d.newGrok.length ? plural(d.newGrok.length, "update") + " from Grok" : "",
  ].filter(Boolean);
  const since = lower1(fmt.when(d.since));
  const summary = d.firstVisit
    ? (bits.length ? `In the last 24 hours: ${bits.join(", ")}.` : "Nothing new in the last 24 hours.")
    : (bits.length ? `Since you were last here ${since}: ${bits.join(", ")}.` : `Nothing new since you were last here ${since}.`);
  const seen = async () => { setBusy(true); try { await home.markSeen(); toast("Marked as seen"); } catch (e) { toast(e.message, true); } setBusy(false); };
  const unseen = d.newGrok.length + d.newNotices.length;
  const clearItems = async (keys, restore) => {
    try { await home.dismiss(keys, restore); if (keys.length > 1 || restore) toast(restore ? "Brought back" : `Cleared ${plural(keys.length, "item")}`); }
    catch (e) { toast(e.message, true); }
  };

  // A figure at the top brings its list forward: on phones it becomes the one showing;
  // on wide screens it is already on screen, so it lights up for a moment.
  const show = (id) => {
    setPicked(id); setFlash(id);
    clearTimeout(flashTimer.current); flashTimer.current = setTimeout(() => setFlash(null), 1400);
    requestAnimationFrame(() => { const el = document.getElementById("h-" + id); if (el) el.scrollIntoView({ behavior: "smooth", block: "nearest" }); });
  };
  const current = picked || (d.attention.length ? "attention" : d.offers.length ? "offers" : "attention");

  const inPlay = d.pipeline.filter(p => p.status !== "funded" && p.status !== "declined");
  const inPlayAmount = inPlay.reduce((t, p) => t + p.amount, 0), inPlayCount = inPlay.reduce((t, p) => t + p.count, 0);
  const closed = d.pipeline.filter(p => p.status === "funded" || p.status === "declined");
  const tile = (label, value, sub, onClick, cls) => (
    <button className={`kpi${cls ? " " + cls : ""}`} onClick={onClick}>
      <span className="k">{label}</span><span className="v serif">{value}</span><span className="s">{sub}</span>
    </button>
  );
  const declinesLine = d.declines.length ? (
    <div className="hfoot">
      {plural(d.declines.length, "decline")} since your last visit: {d.declines.map((x, i) => <span key={i}>{i ? "; " : ""}<button className="link-btn" onClick={() => go("deals", x.dealId)}>{x.biz}</button> ({x.lender})</span>)}
    </div>
  ) : null;
  // The month's total is in the figures above; this only speaks up when a funded deal can't be placed in a month.
  const undatedLine = d.month.undated ? (
    <div className="hfoot">{plural(d.month.undated, "funded deal")} {d.month.undated === 1 ? "has" : "have"} no funding date and {d.month.undated === 1 ? "isn't" : "aren't"} counted. Add one under Edit deal.</div>
  ) : null;
  const lists = [
    { id: "attention", chip: "Attention", title: "Needs attention", n: d.attention.length, tone: d.attention.some(a => a.tone === "bad") ? "bad" : null,
      action: d.attention.length ? <button type="button" className="btn btn-sm btn-quiet" onClick={() => clearItems(d.attention.map(a => a.key))}><Icon name="check" size={14} />Clear all</button> : null,
      foot: d.attentionCleared.length ? <div className="hfoot">{plural(d.attentionCleared.length, "item")} cleared · <button type="button" className="link-btn" onClick={() => clearItems(d.attentionCleared.map(a => a.key), true)}>Bring back</button></div> : null,
      body: <AttentionList items={d.attention} go={go} onClear={clearItems} /> },
    { id: "offers", chip: "Offers", title: "Offers on the table", n: d.offers.length, foot: declinesLine, body: <OffersList offers={d.offers} go={go} /> },
    { id: "renewals", chip: "Renewals", title: "Coming up for renewal", n: d.renewals.length, body: <RenewalsList renewals={d.renewals} go={go} /> },
    { id: "funded", chip: "Funded", title: `Funded in ${d.month.label}`, n: d.funded.length, foot: undatedLine, body: <FundedList funded={d.funded} go={go} /> },
    { id: "birthdays", chip: "Birthdays", title: "Birthdays", n: d.birthdays.length, body: <BirthdaysList birthdays={d.birthdays} go={go} /> },
  ];
  const chips = lists.map(l => ({ id: l.id, label: l.chip, n: l.n, tone: l.tone })).concat([{ id: "updates", label: "Updates", n: unseen, tone: unseen ? "grok" : null }]);

  return (
    <main className="workspace single fit">
      <div className="home dash">
        <div className="dash-main">
          <header className="home-head">
            <div>
              <div className="label">{new Date().toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" })}</div>
              <h1>{hello}{first ? `, ${first}` : ""}.</h1>
              <p>{summary}</p>
            </div>
            <div className="home-tools">
              {ownOnly ? null : (
                <div className="seg" role="group" aria-label="Whose deals">
                  <button type="button" aria-pressed={!mine} onClick={() => setMine(false)}>{user.sees === "team" ? "My team" : "Everyone"}</button>
                  <button type="button" aria-pressed={mine} onClick={() => setMine(true)}>My deals</button>
                </div>
              )}
              <button className="btn btn-sm" onClick={seen} disabled={busy || !d.anyNew}><Icon name="check" size={14} />Mark all as seen</button>
            </div>
          </header>

          <div className="kpis">
            {tile("In play", fmt.moneyShort(inPlayAmount), plural(inPlayCount, "open deal"), () => go("deals"), "money")}
            {tile(`Funded in ${d.month.label}`, fmt.moneyShort(d.month.fundedAmount), d.month.prevFundedAmount ? `${plural(d.month.fundedCount, "deal")} so far · ${d.month.prevLabel.slice(0, 3)} ${fmt.moneyShort(d.month.prevFundedAmount)}` : `${plural(d.month.fundedCount, "deal")} so far`, () => show("funded"), "money")}
            {tile("Offers on the table", String(d.offers.length), d.offers.length ? `${d.newOffers.length ? d.newOffers.length + " new" : "None new"} · best ${fmt.moneyShort(Math.max(...d.offers.map(o => o.amount)))}` : "None right now", () => show("offers"))}
            {tile("Ready to renew", String(d.renewals.length), d.renewals.length ? `${fmt.moneyShort(d.renewals.reduce((t, r) => t + r.remaining, 0))} still to collect` : "None past halfway yet", () => show("renewals"))}
            {tile("Bounced payments", String(d.bounced.length), d.bounced.length ? "Not made good yet" : "None open", () => show("attention"), d.bounced.length ? "t-bad" : "")}
          </div>

          <section className="card pipe-card" aria-label="Open pipeline">
            <h2>Open pipeline<span className="n tnum">{inPlayCount}</span></h2>
            <div className="pipe" role="img" aria-label={inPlay.map(p => `${statusLabel(DEAL_STATUSES, p.status)} ${p.count}`).join(", ")}>
              {inPlay.filter(p => p.count).map(p => <i key={p.status} className={`s-${p.status}`} style={{ flexGrow: p.count }} title={`${statusLabel(DEAL_STATUSES, p.status)}: ${plural(p.count, "deal")}, ${fmt.money(p.amount)}`} />)}
            </div>
            <div className="pipe-legend">
              {inPlay.map(p => (
                <span key={p.status} className="pl"><Pill status={p.status} label={statusLabel(DEAL_STATUSES, p.status)} /><b className="tnum">{p.count}</b><span className="tnum">{fmt.moneyShort(p.amount)}</span></span>
              ))}
              <span className="pl closed">Closed: {closed.map((p, i) => <span key={p.status}>{i ? " · " : ""}<b className="tnum">{p.count}</b> {p.status}</span>)}</span>
            </div>
          </section>

          <div className="hchips" role="group" aria-label="Show list">
            {chips.map(c => (
              <button key={c.id} type="button" data-id={c.id} className={c.tone && c.n ? "t-" + c.tone : ""} aria-pressed={current === c.id} onClick={() => setPicked(c.id)}>
                <b className="tnum">{c.n}</b><span>{c.label}</span>
              </button>
            ))}
          </div>

          <div className="dash-cards">
            {lists.map(l => (
              <HomeSection key={l.id} id={l.id} title={l.title} count={l.n} hint={l.hint} foot={l.foot} action={l.action} tone={l.tone} on={current === l.id} flash={flash === l.id}>{l.body}</HomeSection>
            ))}
          </div>
        </div>

        <aside className={`card hsec upd${current === "updates" ? " on" : ""}${flash === "updates" ? " flash" : ""}`} id="h-updates" aria-label="Updates">
          <div className="hsec-head"><h2>Updates{unseen ? <span className="n tnum">{unseen}</span> : null}</h2></div>
          <p className="hsec-hint">What Grok logged, and other notifications.</p>
          <div className="hsec-body scroll"><UpdatesFeed data={d} go={go} /></div>
        </aside>
      </div>
    </main>
  );
}

Object.assign(window, { HomeView, UpdatesSheet, useHome, scopeHome });
