/* Bento KPI tiles =================================================== */
function BentoKPI({ def, value, delta, sparkData, tone, format, live, variant='standard' }){
const [hover, setHover] = useState(false);
const positive = delta > 0;
const goodIsUp = def.good === 'up';
const deltaGood = goodIsUp ? positive : !positive;
const deltaColor = deltaGood ? 'text-emerald-600 dark:text-emerald-400' : 'text-rose-600 dark:text-rose-400';
const deltaBg = deltaGood ? 'bg-emerald-50 dark:bg-emerald-500/10' : 'bg-rose-50 dark:bg-rose-500/10';
/* ---------- HERO variant: large card with prominent value + chart */
if (variant === 'hero') {
return (
setHover(true)}
onMouseLeave={()=>setHover(false)}
className="relative h-full rounded-2xl border border-ink-200/80 dark:border-white/10 bg-white dark:bg-ink-900/60 shadow-card p-5 overflow-hidden flex flex-col group transition hover:shadow-float">
{def.label}
{live && }
{Math.abs(delta).toFixed(1)}%
vs {fmt(value/(1+(delta||0)/100), 'currency')} prior period
);
}
/* ---------- COMPACT variant: short tile, inline sparkline ----- */
if (variant === 'compact'){
return (
setHover(true)}
onMouseLeave={()=>setHover(false)}
className="relative h-full rounded-2xl border border-ink-200/80 dark:border-white/10 bg-white dark:bg-ink-900/60 shadow-card p-3.5 overflow-hidden flex flex-col">
{def.label}{live && }
{Math.abs(delta).toFixed(1)}%
);
}
/* ---------- STANDARD variant ----------------------------------- */
return (
setHover(true)}
onMouseLeave={()=>setHover(false)}
className="relative h-full rounded-2xl border border-ink-200/80 dark:border-white/10 bg-white dark:bg-ink-900/60 shadow-card p-4 overflow-hidden flex flex-col group transition hover:shadow-float">
{Math.abs(delta).toFixed(1)}%
);
}
/* ---------- Bento KPI grid (asymmetric, visibility-aware) --------- */
function BentoKPIGrid({ accountId, platformId, onAddCustomCol, widgets }){
const snap = kpiSnapshot(accountId, platformId);
const values = { spend:snap.spend, cpa:snap.cpa, roas:snap.roas, cpc:snap.cpc, ctr:snap.ctr };
const tones = { spend:'#8351ff', roas:'#10b981', cpa:'#f59e0b', cpc:'#06b6d4', ctr:'#ec4899' };
const formats = { spend:'currency', roas:'x', cpa:'currency2', cpc:'currency2', ctr:'pct' };
const defs = Object.fromEntries(KPI_DEFS.map(d => [d.id, d]));
/* which tiles are enabled (default on); widgets uses kpi_ keys;
per-account blocklist (e.g. Al Munawarah: no ROAS) applies last */
const isOn = (id)=> (!widgets || widgets['kpi_'+id] !== false) && !metricHiddenFor(accountId, id);
const order = ['spend','roas','cpa','cpc','ctr'];
const enabled = order.filter(isOn);
if (enabled.length === 0) return null;
const tile = (id, variant)=>(
);
const heroSpend = isOn('spend');
const rest = enabled.filter(id=>id!=='spend');
/* No hero → uniform responsive grid */
if (!heroSpend){
return (
{enabled.map(id=>
{tile(id,'standard')}
)}
);
}
/* Hero spend + the rest flowing in a side grid */
return (
2?'row-span-2':'row-span-2 md:row-span-2'}`}>
{tile('spend','hero')}
{rest.map((id,i)=>(
{tile(id, i%2===1 ? 'compact' : 'standard')}
))}
);
}
Object.assign(window, { BentoKPI, BentoKPIGrid });