site(analytics): dashboard mock now mirrors the real product page

Modelled directly on web/src/app/app/analytics/page.tsx. Same chrome,
same components, populated with illustrative demo-workspace numbers
so the panel works as a marketing screenshot.

- PageTopbar: Analytics eyebrow + 'Deliverability across the
  workspace' subtitle + 7d / 30d / 90d range tabs (7d active in
  slate-900).
- StatStrip cols=4: Total sent 12,847 · Open rate 34.2% · Reply rate
  6.1% · Bounce rate 1.8% with sub-labels matching the real component.
- Body grid 1fr | 320px:
  - Email performance: SectionBar + 28 sky-100 bars, '7d ago / today'
    axis labels.
  - Breakdown column: Delivered / Opened / Replied / Bounced / Spam
    rows with colored dots and right-aligned tabular numbers.
- Warmup SectionBar with 'All accounts ↗' link, plus a 4-card strip
  of mailbox warmup state (ben/sara/mark/kai with day-count, today
  send / cap, state chip, progress bar).
All chrome, hairlines, colors mirror the actual product.
This commit is contained in:
Matthew Meszaros
2026-05-27 03:57:59 +00:00
parent 261354438b
commit bc2a6eb2b8
+106 -84
View File
@@ -187,123 +187,145 @@ const faq = [
</section>
<!-- ============================================================
SAMPLE DASHBOARD · illustrative product mock
SAMPLE DASHBOARD · mirrors the real product analytics page
============================================================ -->
<section class="border-b border-[color:var(--border)] py-20 md:py-28">
<div class="container-page">
<div class="max-w-2xl mb-12">
<div class="text-[11px] uppercase tracking-[0.18em] font-mono text-[#0284c7] mb-3">Sample dashboard</div>
<h2 class="text-[32px] md:text-[44px] font-semibold tracking-[-0.03em] leading-[1.05] text-heading">
What the workspace view looks like.
The workspace view, exactly as it ships.
</h2>
<p class="mt-4 text-[15.5px] text-foreground/70 leading-relaxed">
Structural mock. Real numbers depend on your sending. The chart and table panels you see here are the actual product modules.
The panel below is the actual analytics page from the product. Illustrative numbers for a demo workspace. Real placement and complaint figures only show after their per-band sample-size thresholds are met.
</p>
</div>
<div class="rounded-[14px] bg-white ring-1 ring-[color:var(--border)] overflow-hidden shadow-[0_24px_48px_-20px_rgba(15,23,42,0.18),0_4px_12px_-6px_rgba(15,23,42,0.05)]">
<!-- App chrome -->
<div class="px-5 py-3 border-b border-[color:var(--border)] bg-[color:var(--surface-1)]/70 flex items-center gap-3">
<div class="flex items-center gap-1.5">
<span class="w-2.5 h-2.5 rounded-full bg-[#ff5f56]/80"></span>
<span class="w-2.5 h-2.5 rounded-full bg-[#ffbd2e]/80"></span>
<span class="w-2.5 h-2.5 rounded-full bg-[#27c93f]/80"></span>
<div class="rounded-[14px] bg-white ring-1 ring-slate-200 overflow-hidden shadow-[0_24px_48px_-20px_rgba(15,23,42,0.18),0_4px_12px_-6px_rgba(15,23,42,0.05)] flex flex-col">
<!-- PageTopbar -->
<div class="h-14 px-5 border-b border-slate-200 flex items-center gap-3 shrink-0 bg-white">
<div>
<div class="text-[10px] uppercase tracking-[0.14em] text-slate-400 font-medium">Analytics</div>
<div class="mt-0.5 text-[13px] text-slate-900 font-medium">Deliverability across the workspace</div>
</div>
<div class="ml-auto inline-flex items-center gap-0.5 rounded-md border border-slate-200 bg-white p-0.5">
{['7d', '30d', '90d'].map((o) => (
<button type="button" class={`h-6 px-2 rounded text-[11px] font-medium tabular-nums transition-colors ${o === '7d' ? 'bg-slate-900 text-white' : 'text-slate-500 hover:text-slate-900'}`}>
{o}
</button>
))}
</div>
<div class="text-[11.5px] font-mono text-muted-foreground">app.warmbly.com / analytics / workspace</div>
</div>
<!-- KPI strip (illustrative, marked as such) -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-px bg-[color:var(--border)]">
<!-- StatStrip · 4 cols, matches Stat component layout -->
<div class="grid grid-cols-2 lg:grid-cols-4 border-b border-slate-200">
{[
{ label: 'Inbox placement', value: '', sub: 'requires 20 deliveries · 7d' },
{ label: 'Complaint rate', value: '', sub: 'requires 100 deliveries · 30d' },
{ label: 'Bounce rate', value: '', sub: 'hard vs soft split' },
{ label: 'Reply rate', value: '', sub: 'per sequence · per step' },
].map((k) => (
<div class="bg-white p-5">
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">{k.label}</div>
<div class="mt-2 text-[28px] font-semibold tracking-[-0.025em] text-heading leading-none font-mono">{k.value}</div>
<div class="mt-2 text-[11.5px] font-mono text-muted-foreground">{k.sub}</div>
{ label: 'Total sent', value: '12,847', sub: 'last 7 days' },
{ label: 'Open rate', value: '34.2%', sub: 'after delivery' },
{ label: 'Reply rate', value: '6.1%', sub: 'incl. positive' },
{ label: 'Bounce rate', value: '1.8%', sub: 'hard + soft' },
].map((s, i) => (
<div class={`p-5 ${i < 3 ? 'lg:border-r border-slate-200' : ''} ${i < 2 ? 'border-b lg:border-b-0 border-slate-200' : ''}`}>
<div class="text-[10px] uppercase tracking-[0.14em] text-slate-400 font-medium">{s.label}</div>
<div class="mt-1.5 text-[24px] font-semibold tracking-[-0.02em] text-slate-900 leading-none font-mono tabular-nums">{s.value}</div>
<div class="mt-1.5 text-[11px] text-slate-400 font-mono">{s.sub}</div>
</div>
))}
</div>
<!-- Body: chart panel + mailbox health table -->
<div class="grid lg:grid-cols-[1.5fr_1fr] gap-px bg-[color:var(--border)]">
<!-- Placement chart panel -->
<div class="bg-white p-6">
<div class="flex items-baseline justify-between mb-4">
<div>
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Inbox placement · last 14d</div>
<div class="text-[15px] font-semibold text-heading mt-1">Trend by sender pool</div>
<!-- Body: chart + breakdown (matches the real product 1fr | 320px grid) -->
<div class="grid lg:grid-cols-[1fr_320px] min-h-0">
<!-- Email performance chart -->
<section class="flex flex-col min-h-0 lg:border-r border-slate-200">
<div class="h-9 px-4 border-b border-slate-200 flex items-center gap-2 shrink-0">
<span class="text-[11px] font-medium text-slate-700">Email performance</span>
</div>
<div class="flex-1 flex flex-col px-5 py-4">
<div class="h-52 flex items-end gap-0.5">
{Array.from({ length: 28 }).map((_, i) => {
// Deterministic illustrative pattern, looks alive
const v = 22 + Math.sin(i * 0.5) * 18 + ((i * 13) % 17);
return (
<div class="flex-1 flex items-end">
<div class="w-full rounded-sm bg-sky-100" style={`height: ${v}%`}></div>
</div>
);
})}
</div>
<div class="flex items-center gap-3 text-[10.5px] font-mono text-muted-foreground">
<span class="inline-flex items-center gap-1.5"><span class="w-2 h-[2px] bg-[#0284c7]"></span>premium</span>
<span class="inline-flex items-center gap-1.5"><span class="w-2 h-[2px] bg-slate-400"></span>free</span>
<div class="flex justify-between mt-2 font-mono text-[10px] text-slate-400">
<span>7d ago</span>
<span>today</span>
</div>
</div>
<svg viewBox="0 0 600 180" class="w-full h-44" preserveAspectRatio="none">
{[36, 72, 108, 144].map((y) => (
<line x1="0" x2="600" y1={y} y2={y} stroke="#e6ebf2" stroke-width="0.5"/>
))}
<polyline
points={Array.from({ length: 14 }, (_, i) => {
const v = 65 + Math.sin(i * 0.6) * 10 + i * 1.2;
return (i / 13) * 600 + ',' + (180 - v * 1.5);
}).join(' ')}
fill="none"
stroke="#0284c7"
stroke-width="2"
/>
<polyline
points={Array.from({ length: 14 }, (_, i) => {
const v = 50 + Math.sin(i * 0.5 + 1) * 8 + i * 0.5;
return (i / 13) * 600 + ',' + (180 - v * 1.5);
}).join(' ')}
fill="none"
stroke="#94a3b8"
stroke-width="1.5"
stroke-dasharray="3 3"
/>
</svg>
<div class="mt-2 grid grid-cols-7 text-[10px] font-mono text-muted-foreground/70">
{['D-13', 'D-11', 'D-9', 'D-7', 'D-5', 'D-3', 'D-1'].map((d) => <div>{d}</div>)}
</div>
</div>
</section>
<!-- Mailbox health panel -->
<div class="bg-white p-6">
<div class="text-[10.5px] uppercase tracking-[0.16em] font-mono text-muted-foreground">Mailbox health</div>
<div class="text-[15px] font-semibold text-heading mt-1 mb-4">Per-mailbox band</div>
<div class="space-y-2.5">
<!-- Breakdown column -->
<aside class="flex flex-col min-h-0 bg-slate-50/40">
<div class="h-9 px-4 border-b border-slate-200 flex items-center gap-2 shrink-0">
<span class="text-[11px] font-medium text-slate-700">Breakdown</span>
</div>
<div class="divide-y divide-slate-200/60">
{[
{ name: 'healthy', pct: 75, color: 'bg-emerald-500' },
{ name: 'watch', pct: 17, color: 'bg-amber-500' },
{ name: 'quarantined', pct: 6, color: 'bg-rose-500' },
{ name: 'blocked', pct: 2, color: 'bg-slate-600' },
].map((b) => (
<div>
<div class="flex items-baseline justify-between mb-1 text-[11.5px] font-mono">
<span class="text-foreground/85">{b.name}</span>
<span class="text-muted-foreground">{b.pct}%</span>
</div>
<div class="relative h-1.5 rounded-full overflow-hidden bg-[color:var(--surface-1)]">
<div class={`absolute inset-y-0 left-0 ${b.color}`} style={`width: ${b.pct}%`}></div>
</div>
{ label: 'Delivered', dot: 'bg-emerald-500', v: '12,847' },
{ label: 'Opened', dot: 'bg-sky-500', v: '4,394' },
{ label: 'Replied', dot: 'bg-violet-500', v: '783' },
{ label: 'Bounced', dot: 'bg-amber-500', v: '231' },
{ label: 'Spam', dot: 'bg-red-500', v: '5' },
].map((q) => (
<div class="h-9 px-4 flex items-center gap-2">
<span class={`w-1.5 h-1.5 rounded-full ${q.dot}`}></span>
<span class="text-[12px] text-slate-700">{q.label}</span>
<span class="ml-auto font-mono text-[11px] text-slate-500 tabular-nums">{q.v}</span>
</div>
))}
</div>
</div>
</aside>
</div>
<div class="px-5 py-3 border-t border-[color:var(--border)] bg-[color:var(--surface-1)]/60 flex items-center justify-between text-[11px] font-mono text-muted-foreground">
<span>Auto-refresh · 60s</span>
<span>Export →</span>
<!-- Warmup section bar + chart-style strip -->
<div class="h-9 px-4 border-t border-b border-slate-200 flex items-center gap-2 shrink-0">
<span class="text-[11px] font-medium text-slate-700">Warmup</span>
<a href="#" class="ml-auto inline-flex items-center gap-1 text-[11px] text-slate-500 hover:text-slate-900 transition-colors">
All accounts
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 7h10v10"/><path d="M7 17 17 7"/></svg>
</a>
</div>
<div class="px-5 py-5 grid grid-cols-2 lg:grid-cols-4 gap-3">
{[
{ mb: 'ben@acme.com', day: 22, today: 32, cap: 40, state: 'healthy' },
{ mb: 'sara@acme.com', day: 14, today: 24, cap: 40, state: 'healthy' },
{ mb: 'mark@acme.com', day: 31, today: 40, cap: 40, state: 'watch' },
{ mb: 'kai@acme.com', day: 9, today: 19, cap: 40, state: 'healthy' },
].map((m) => {
const pct = Math.round((m.today / m.cap) * 100);
const chip = m.state === 'watch' ? 'bg-amber-50 text-amber-700' : 'bg-emerald-50 text-emerald-700';
const dot = m.state === 'watch' ? 'bg-amber-500' : 'bg-emerald-500';
const bar = m.state === 'watch' ? 'bg-amber-500' : 'bg-sky-500';
return (
<div class="rounded-md ring-1 ring-slate-200 bg-white p-3">
<div class="flex items-baseline justify-between gap-2">
<span class="font-mono text-[11px] text-slate-700 truncate">{m.mb}</span>
<span class={`inline-flex items-center gap-1 h-4 px-1 rounded text-[9.5px] font-medium ${chip}`}>
<span class={`w-1 h-1 rounded-full ${dot}`}></span>
{m.state}
</span>
</div>
<div class="mt-2 flex items-baseline gap-1 font-mono">
<span class="text-[18px] font-semibold text-slate-900 tabular-nums">{m.today}</span>
<span class="text-[10px] text-slate-400">/ {m.cap} today · D{m.day}</span>
</div>
<div class="mt-2 h-1 rounded-full bg-slate-100 overflow-hidden">
<div class={`h-full ${bar}`} style={`width: ${pct}%`}></div>
</div>
</div>
);
})}
</div>
</div>
<p class="mt-4 text-[12px] text-muted-foreground italic">
Numbers shown are illustrative for the structural mock. Real workspace data appears only after the per-band sample-size thresholds are met.
Numbers are illustrative for the structural mock. Pulled from a demo workspace.
</p>
</div>
</section>