/* 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 });