/* Host CRM — the "grey room". Home · Constituents (list + record) · Revenue ·
Major Giving (the deliberate black-box foil). Calm, dense, minimal color. */
const HC = window.Ds95ForwardDesignSystem_31a0c4 || {};
/* Generic, deliberately-opaque host "AI" mark — NOT iris (that's reserved for
the 95 Forward copilot). A flat grey number with no story. */
function BlackBoxBadge({ small }) {
const { Icon } = window.POC;
return (
AI
);
}
/* -------------------------------------------------- HOME */
function HostHome({ go }) {
const D = window.POC_DATA;
const { Icon, HostCard, Eyebrow } = window.POC;
const h = D.home;
const maxBar = 100;
const Tile = ({ children, style }) => {children} ;
const TileHead = ({ children, action }) => (
);
return (
{/* Recent gifts */}
Recent gifts
{h.recentGifts.map((g, i) => (
{g.name}
{g.type}
{g.amount}
{g.date}
))}
{/* YoY fundraising */}
Year-over-year fundraising
{h.yoy.map((q, i) => (
))}
Last year
This year
{/* Active donor growth */}
Active donor growth
{h.growth.value}
{h.growth.delta}
{h.growth.note}
{/* Major gift likelihood — the BLACK BOX */}
}>Major gift likelihood
12
donors trending toward a major gift
Model output · no breakdown available
{/* Tasks due */}
{h.tasks.length} due}>Tasks & actions
{h.tasks.map((t, i) => (
{t.what}
{t.due}
))}
{/* 95 Forward promo — color enters the grey room */}
95 Forward
Your major-gifts workspace
The whole picture — and the next right move.
{HC.Button ? go("today")} iconRight={ }>Open : null}
);
}
/* -------------------------------------------------- CONSTITUENTS LIST */
function HostConstituents({ go }) {
const D = window.POC_DATA;
const { Icon, HostBtn } = window.POC;
const [f, setF] = React.useState("all");
const FILTERS = [["all", "All"], ["Prospect", "Prospects"], ["Active", "Active"], ["Lapsed", "Lapsed"]];
const rows = D.constituents.filter(c => f === "all" || c.status === f);
const StatusPill = ({ s }) => {
const map = { Prospect: ["#E7EEF4", "#2C5B7E"], Active: ["#E6EFEA", "#3B7458"], Lapsed: ["#EFE9E6", "#A8765A"] };
const [bg, col] = map[s] || ["#E7ECEF", "#71808B"];
return {s} ;
};
return (
{FILTERS.map(([id, l]) => (
setF(id)} style={{
height: 32, padding: "0 13px", borderRadius: "var(--radius-sm)", cursor: "pointer",
border: "1px solid " + (f === id ? "#9FB0BC" : "var(--host-rule, #D5DCE1)"),
background: f === id ? "#4A5965" : "var(--surface-card)", color: f === id ? "#fff" : "var(--ink-700)",
font: "var(--fw-medium) var(--fs-caption) var(--font-sans)",
}}>{l}
))}
Filters
{rows.length} constituents · AND logic
Name Type Lifetime Last gift Last contact Status Assigned
{rows.map(c => (
go("constituent", { id: c.id })}>
{c.name}
{c.type}
{c.lifetime}
{c.lastGift}
{c.lastContact}
{c.owner}
))}
);
}
/* -------------------------------------------------- CONSTITUENT RECORD */
function HostConstituent({ params, go }) {
const D = window.POC_DATA;
const { Icon, HostCard, HostBtn } = window.POC;
const c = D.constituents.find(x => x.id === (params && params.id)) || D.constituents[0];
const [tab, setTab] = React.useState("Profile");
const TABS = ["Profile", "Giving history", "Relationships", "Actions", "Tags", "Volunteer"];
const initials = c.name.split(/\s+/).map(w => w[0]).slice(0, 2).join("").toUpperCase();
return (
go("constituents")} style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "none", border: "none", cursor: "pointer", color: "var(--host-muted, #71808B)", font: "var(--fw-medium) var(--fs-small) var(--font-sans)", padding: 0, width: "fit-content" }}>
Constituents
{/* Header */}
{initials}
{c.name}
{c.type}
{c.status === "Prospect" ? Prospect : null}
Lifetime giving
{c.lifetime}
{c.prospect ? (
go("prospect", { id: c.prospectId })} style={{
display: "inline-flex", alignItems: "center", gap: 8, height: 38, padding: "0 14px", borderRadius: "var(--radius-md)",
border: "1px solid var(--blue-100)", background: "linear-gradient(160deg, var(--blue-50), var(--white))", cursor: "pointer",
font: "var(--fw-semibold) var(--fs-small) var(--font-sans)", color: "var(--blue-700)", flex: "none",
}}>
Open in 95 Forward
) : null}
{/* Tabs */}
{TABS.map(t => (
setTab(t)} style={{
padding: "10px 14px", border: "none", background: "none", cursor: "pointer",
font: (tab === t ? "var(--fw-semibold)" : "var(--fw-medium)") + " var(--fs-small) var(--font-sans)",
color: tab === t ? "var(--host-ink-strong, #2A3640)" : "var(--host-muted, #8896A0)",
borderBottom: "2px solid " + (tab === t ? "#4A5965" : "transparent"), marginBottom: -1,
}}>{t}
))}
{tab === "Giving history" ? (
Date Fund Campaign Type Amount
{D.revenue.gifts.filter(g => g.name === c.name).concat([
{ date: "Mar 2024", fund: "Youth Center", campaign: "Capital", type: "Grant", amount: "$60,000" },
{ date: "Feb 2023", fund: "Youth Center", campaign: "Capital", type: "Grant", amount: "$40,000" },
{ date: "Jan 2022", fund: "Annual Fund", campaign: "FY22", type: "Cash", amount: "$25,000" },
]).slice(0, 5).map((g, i) => (
{g.date} {g.fund} {g.campaign} {g.type} {g.amount}
))}
) : (
{[["Primary contact", c.type === "Individual" ? c.name : "Eleanor Hartwell"], ["Email", "—"], ["Phone", "—"], ["Address", c.region], ["Constituent since", "2019"], ["Assigned to", c.owner]].map(([k, v], i) => (
{k}
{v}
))}
)}
);
}
/* -------------------------------------------------- REVENUE */
function HostRevenue() {
const D = window.POC_DATA;
const { HostBtn } = window.POC;
const r = D.revenue;
const Stat = ({ label, value }) => (
);
return (
Filters
Export
Showing recent gifts
Constituent Date Fund Campaign Appeal Type Receipt Amount
{r.gifts.map((g, i) => (
{g.name}
{g.date}
{g.fund} {g.campaign} {g.appeal} {g.type}
{g.receipt}
{g.amount}
))}
);
}
/* -------------------------------------------------- MAJOR GIVING — THE FOIL */
function HostMajorGiving({ params }) {
const D = window.POC_DATA;
const { Icon, HostCard } = window.POC;
const mg = D.majorGiving;
const tab = (params && params.tab) || "opportunities";
const StageDot = ({ stage }) => {
const order = ["Identification", "Cultivation", "Solicitation", "Stewardship"];
const idx = order.indexOf(stage);
return (
{order.map((_, i) => )}
{stage}
);
};
const Likelihood = ({ v }) => (
{v}%
);
return (
{tab === "opportunities" ? (
Constituent Stage Ask Expected Close Likelihood Owner
{mg.opportunities.map((o, i) => (
{o.name}
{o.ask}
{o.expected}
{o.close}
{o.owner}
))}
) : tab === "proposals" ? (
Constituent Purpose Amount Status Deadline
{mg.proposals.map((p, i) => (
{p.name} {p.purpose} {p.amount} {p.status} {p.deadline}
))}
) : (
{mg.portfolio.map((p, i) => (
{p.name}
{p.lifetime} lifetime
{p.stage}
))}
)}
{/* "Insights" panel that asserts without showing why */}
{[
"Hartwell Family Foundation is your highest-likelihood opportunity this quarter.",
"3 opportunities are at risk of slipping their close date.",
"Cultivation stage is converting below your historical average.",
].map((t, i) => (
{t}
))}
Conclusions only · the model does not explain its reasoning.
);
}
window.POC = Object.assign(window.POC || {}, { HostHome, HostConstituents, HostConstituent, HostRevenue, HostMajorGiving });