/* Conversion Funnel ================================================ */
function Funnel({ steps, compact=false }){
const max = Math.max(...steps.map(s=>s.value));
const tones = [
{ bar:'#8351ff', text:'text-accent-700 dark:text-accent-200', bg:'bg-accent-50 dark:bg-accent-500/10' },
{ bar:'#06b6d4', text:'text-sky-700 dark:text-sky-200', bg:'bg-sky-50 dark:bg-sky-500/10' },
{ bar:'#f59e0b', text:'text-amber-700 dark:text-amber-200', bg:'bg-amber-50 dark:bg-amber-500/10' },
{ bar:'#10b981', text:'text-emerald-700 dark:text-emerald-200',bg:'bg-emerald-50 dark:bg-emerald-500/10' },
];
return (
{steps.map((s,i) => {
const t = tones[i];
const w = (s.value / max) * 100;
const prev = i>0 ? steps[i-1].value : null;
const drop = prev ? ((prev - s.value)/prev*100) : null;
return (
{/* Background bar showing rel. magnitude */}
{s.short || s.stage}
{s.value >= 1000 ? (s.value/1000).toFixed(s.value>=10000?0:1)+'k' : s.value.toLocaleString()}
{drop !== null && (
↳ {(100-drop).toFixed(1)}% pass-through
)}
{drop === null && (
top of funnel
)}
);
})}
);
}
/* Compact horizontal funnel (used inside expanded campaign rows) */
function FunnelStrip({ steps }){
if (!steps || !steps.length) return null;
const max = steps[0].value;
const last = steps[steps.length-1];
const tones = ['#8351ff','#06b6d4','#f59e0b','#10b981'];
return (
Conversion funnel
End-to-end conversion {((last.value/max)*100).toFixed(2)}%
{steps.map((s,i)=>{
const w = (s.value / max) * 100;
const prev = i>0 ? steps[i-1].value : null;
const drop = prev ? ((prev - s.value)/prev*100) : null;
return (
{s.short}
{((s.value/max)*100).toFixed(1)}%
{s.value >= 1000 ? (s.value/1000).toFixed(s.value>=10000?0:1)+'k' : s.value.toLocaleString()}
{i < steps.length-1 && (
{drop !== null && (
70?'text-rose-500':drop>40?'text-amber-500':'text-ink-400'}`}>
−{drop.toFixed(0)}%
)}
)}
);
})}
);
}
Object.assign(window, { Funnel, FunnelStrip });