Where contacts come from, what they ask for, who converts.
{/* Segmented control: do NOT use .btn.primary for the active state
— that class is reserved for primary CTAs (e.g. "+ New lead")
and the mobile rule forces full-width on it. Inline-style the
active look like home.jsx:340-348 does. */}
{[7, 30, 90].map(d => (
))}
— Activity · contact volume
{['today','yesterday','wtd','mtd'].map(p => (
))}
— Funnel · cohort, last {days}d
{cohortTotal} new · {membersTotal} members lifetime
{funnelData.error && (
Funnel unavailable.
)}
{funnel.map((step, i) => {
const pct = (step.value / funnelMax) * 100;
// Conversion from previous step in current period (monotonic by construction).
const conv = i > 0 && funnel[i-1].value > 0 ? (step.value / funnel[i-1].value) : null;
// Real period-over-period delta vs the immediately preceding window.
let deltaPct = null;
if (step.prev > 0) deltaPct = ((step.value - step.prev) / step.prev) * 100;
else if (step.value > 0) deltaPct = null; // no baseline — show "new"
const deltaColor = deltaPct == null ? 'var(--muted-2)'
: deltaPct >= 0 ? 'var(--signal)' : 'var(--muted)';
return (
Cohort = contacts created in the last {days}d. Each step counts members
who reached at-or-beyond that step. Showed is heuristic (no event log
for SHOWED transitions yet). Members lifetime is shown separately and
not mixed into Joined.