Taylor Simmons
Mortgage · Loan officer
Chapter 12 · Narrative systems
Every branded system diagram, timeline, and wireframe abstraction the marketing site needs — built in code, themed by tokens, animated on reveal. Decay curves, scoreboards, race timelines, the engine, handoff and profile cards, the integration hub, HAPPA arc, Guardian shield, journey maps, the closest-edge race, ecosystem rings, the Cost-of-Inaction calculator, a brand timeline, and a hero bloom canvas — plus four mini-atoms (compliance row, channel orbit, spark, stat badge) that slot into the big pieces.
A single conversation, the whole story. The flagship scenario is a mortgage refinance — chosen because the maths (rate, monthly saving, loan amount) makes the agent's intelligence concrete in a way SaaS pricing can't. Watch the agent move from intent signal to booked meeting, with the context it pulled from memory highlighted alongside. Plays on a continuous 15.5s loop (12.5s action + 3s rest on Booked) so the scene is always live; respects prefers-reduced-motion and renders full state immediately for reduced-motion users.
Lead started a refi quote two days ago; agent reaches out with the current rate, handles a points/loan-size question, quotes the monthly saving against the existing rate, and books the call — all under 30 seconds of human-feeling exchange. Annotations on the right reveal what MagicBlocks knew at each turn.
<div class="hero-scene reveal" data-hero-scene>
<div class="hs-phone">
<div class="hs-notch"></div>
<div class="hs-screen">
<div class="hs-trigger" data-step="trigger">
<span class="hs-trigger-dot"></span>
<span class="hs-trigger-title">Intent signal</span>
<span class="hs-trigger-sub">Refi calc · 2× · draft app</span>
</div>
<div class="hs-thread" data-thread>
<div class="hs-msg out" data-step="m1">Hi Mike 👋 Saw you started a refi quote on Saturday. Quick update — 30-yr fixed is at <strong>6.45%</strong> today, just under your last view (6.51%). Worth 5 mins?</div>
<div class="hs-msg in" data-step="m2">is that with points</div>
<div class="hs-msg out" data-step="m3">No-points, owner-occupied, 740+ FICO. Loan around $420k like your draft?</div>
<div class="hs-msg in" data-step="m4">closer to 450</div>
<div class="hs-msg out" data-step="m5">At $450k / 6.45% you'd save <strong>~$280/mo</strong> vs your current 7.10%. 15-min call with a loan officer to lock it?</div>
<div class="hs-msg in" data-step="m6">today if possible</div>
<div class="hs-msg out hs-msg-card" data-step="m7">
<span class="hs-cal-head">Two slots open today:</span>
<div class="hs-cal">
<button type="button" class="hs-cal-slot">
<span class="hs-cal-day">Today</span><span class="hs-cal-time">2:30 PM</span>
</button>
<button type="button" class="hs-cal-slot">
<span class="hs-cal-day">Today</span><span class="hs-cal-time">4:00 PM</span>
</button>
</div>
</div>
<div class="hs-msg in" data-step="m8">2:30 works</div>
<div class="hs-msg out hs-msg-booked" data-step="m9">✓ Booked with Priya · today 2:30 PM. Calendar invite + your draft application sent.</div>
<!-- Typing indicator — JS toggles [hidden] between bubbles -->
<div class="hs-typing" data-typing hidden><span></span><span></span><span></span></div>
</div>
</div>
</div>
<aside class="hs-annotations">
<div class="hs-ann" data-ann="a1"><span class="hs-ann-k">Last intent</span><span class="hs-ann-v">30-yr fixed · 2 visits</span></div>
<div class="hs-ann" data-ann="a2"><span class="hs-ann-k">Loan stage</span><span class="hs-ann-v">Draft app · day 2</span></div>
<div class="hs-ann" data-ann="a3"><span class="hs-ann-k">Existing rate</span><span class="hs-ann-v">7.10% · current loan</span></div>
<div class="hs-ann" data-ann="a4"><span class="hs-ann-k">Preferred</span><span class="hs-ann-v">SMS · evenings</span></div>
</aside>
</div>
/* Timed reveal via JS toggling .is-visible on each bubble + annotation.
Phone shell is chapter-scoped (not .device.phone), purpose-built for
hosting a live thread with pinned trigger and scrollable history. */
.hero-scene { display: grid; grid-template-columns: 340px 280px; gap: var(--s-7); }
.hs-phone { aspect-ratio: 9/18; background: var(--ink); border-radius: 44px; padding: 14px; }
.hs-screen { background: var(--warm-3); border-radius: 32px; overflow: hidden; }
.hs-msg.out { background: var(--accent); color: var(--paper); }
.hs-msg.in { background: var(--bg-paper); border: 1px solid var(--hair); }
.hs-msg.is-visible { opacity: 1; transform: translateY(0); }
.hs-ann { border-left: 2px solid var(--accent); padding: 10px 14px; }
@media (prefers-reduced-motion: reduce) {
.hs-msg, .hs-ann, .hs-trigger { opacity: 1 !important; transform: none !important; }
.hs-typing { display: none !important; }
}
import { HeroScene } from "@magicblocksai/ui";
<HeroScene
triggerTitle="Intent signal"
triggerSub="Refi calc · 2× · draft app"
messages={[
{ step: "m1", from: "out", text: "Hi Mike — saw you started a refi quote on Saturday." },
{ step: "m2", from: "in", text: "is that with points" },
// …m3–m9 including a calendar slot card and a booked confirmation
]}
annotations={[
{ id: "a1", kicker: "Last intent", value: "30-yr fixed · 2 visits" },
{ id: "a2", kicker: "Loan stage", value: "Draft app · day 2" },
{ id: "a3", kicker: "Existing rate", value: "7.10% · current loan" },
{ id: "a4", kicker: "Preferred", value: "SMS · evenings" },
]}
/>
A line graph of lead intent against time. Steep exponential drop, shaded dead zone after 5 minutes, two pinned markers — "Your team" (10 min, red) vs "MagicBlocks" (5 sec, pink). The single most-reused visual on the site.
Curve draws on scroll-in; red marker pulses; pink marker pops at the tail. Respects reduced-motion.
<div class="decay-curve reveal svg-draw" data-state="lost" style="--draw-len: 1100;">
<svg viewBox="0 0 900 340" role="img" aria-label="Lead intent decays within minutes">
<!-- gridlines, axis labels, dead-zone rect -->
<rect class="dead-zone" x="460" y="60" width="400" height="240"/>
<!-- curve (draws on reveal via stroke-dasharray) -->
<path class="curve-path draw"
d="M 60 60 Q 130 72, 200 110 T 360 200 T 520 260 T 700 285 T 860 295"/>
<!-- markers -->
<g class="marker won">
<circle class="marker-ring" cx="90" cy="65" r="9"/>
<circle class="marker-dot" cx="90" cy="65" r="4"/>
</g>
<g class="marker lost">
<circle class="marker-ring" cx="540" cy="265" r="9"/>
<circle class="marker-dot" cx="540" cy="265" r="4"/>
</g>
</svg>
</div>
.decay-curve .curve-path { fill: none; stroke: var(--ink); stroke-width: 2.4; stroke-linecap: round; }
.decay-curve .dead-zone { fill: color-mix(in oklab, #D64545 16%, transparent); }
.decay-curve .marker.lost .marker-ring { stroke: #D64545; animation: dc-pulse-lost 1.8s ease-out infinite; }
.decay-curve .marker.won .marker-ring { stroke: var(--accent); }
/* draw primitive (shared) */
.svg-draw .draw { stroke-dasharray: var(--draw-len, 1200); stroke-dashoffset: var(--draw-len, 1200); transition: stroke-dashoffset 1200ms ease-out; }
.svg-draw.is-visible .draw { stroke-dashoffset: 0; }
import { DecayCurve } from "@magicblocksai/ui";
<DecayCurve
ariaLabel="Lead intent decays within minutes of form submission"
wonTitle="MagicBlocks · 5 sec"
wonSub="CONVERSATION BEGINS"
lostTitle="Your team · 10 min"
lostSub="CONTACT RATE DOWN 80%"
/>
The other half of the story. Most of your CRM is dormant inquiries nobody had time to follow up on — real revenue sitting in boxes. The component plays a continuous 14s loop: each row of a mortgage CRM cycles cold → scanning → drafting → sent → replied, with a pink scan-sweep visualising the agent reaching the row. Pure CSS — no JS state. Adapted from the “Mining for Leads · V1 Database” design.
Dormant leads are revenue sitting in boxes. Agents open every box — in under a second, in the customer's language, with their full history loaded.
<div class="dormant-mine">
<div class="dm-bar">
<div class="dm-titles">
<div class="dm-title">Dormant inquiries · Mortgage CRM</div>
<div class="dm-meta">
<span class="dm-token">7 of 1,284 shown</span>
<span class="dm-token dm-token-live"><span class="dm-livedot"></span>Mining</span>
</div>
</div>
<div class="dm-revchip">
<div class="dm-revchip-label">Revenue recovered</div>
<div class="dm-revchip-value">$482,400</div>
<div class="dm-revchip-delta">▲ 7 leads re-engaged</div>
</div>
</div>
<div class="dm-table">
<div class="dm-cols"><div>Lead</div><div>Loan</div><div>Last touch</div><div>Status</div></div>
<div class="dm-rows">
<!-- 7 rows; each <div class="dm-row" style="--i:N"> has 4 cells +
a <div class="dm-scan-sweep">. The pill stacks 5 status spans
(lbl-cold/scan/draft/sent/ok) — only one is visible at a time
per the row's animation phase. -->
<div class="dm-row" style="--i:0">
<div class="dm-name">
<div class="dm-avatar">MR</div>
<div>
<div class="dm-name-text">Marcus Rivera</div>
<div class="dm-stage-text">30-yr fixed inquiry</div>
</div>
</div>
<div class="dm-amount">$485k</div>
<div class="dm-last"><span class="dm-last-stack"><span class="last-cold">47 days</span><span class="last-fresh">0 days</span></span></div>
<div class="dm-status">
<span class="dm-pill">
<span class="dm-pill-dot"></span>
<span class="dm-pill-lbl">
<span class="lbl-cold">Cold · 47d</span>
<span class="lbl-scan">Scanning…</span>
<span class="lbl-draft">Drafting…</span>
<span class="lbl-sent">Sent</span>
<span class="lbl-ok">Replied</span>
</span>
</span>
</div>
<div class="dm-scan-sweep"></div>
</div>
<div class="dm-row" style="--i:1">
<div class="dm-name"><div class="dm-avatar">PS</div><div><div class="dm-name-text">Priya Shah</div><div class="dm-stage-text">Refi quote, no follow-up</div></div></div>
<div class="dm-amount">$312k</div>
<div class="dm-last"><span class="dm-last-stack"><span class="last-cold">62 days</span><span class="last-fresh">0 days</span></span></div>
<div class="dm-status"><span class="dm-pill"><span class="dm-pill-dot"></span><span class="dm-pill-lbl"><span class="lbl-cold">Cold · 62d</span><span class="lbl-scan">Scanning…</span><span class="lbl-draft">Drafting…</span><span class="lbl-sent">Sent</span><span class="lbl-ok">Replied</span></span></span></div>
<div class="dm-scan-sweep"></div>
</div>
<div class="dm-row" style="--i:2">
<div class="dm-name"><div class="dm-avatar">JB</div><div><div class="dm-name-text">Jordan Bell</div><div class="dm-stage-text">Pre-approval, ghosted</div></div></div>
<div class="dm-amount">$640k</div>
<div class="dm-last"><span class="dm-last-stack"><span class="last-cold">28 days</span><span class="last-fresh">0 days</span></span></div>
<div class="dm-status"><span class="dm-pill"><span class="dm-pill-dot"></span><span class="dm-pill-lbl"><span class="lbl-cold">Cold · 28d</span><span class="lbl-scan">Scanning…</span><span class="lbl-draft">Drafting…</span><span class="lbl-sent">Sent</span><span class="lbl-ok">Replied</span></span></span></div>
<div class="dm-scan-sweep"></div>
</div>
<div class="dm-row" style="--i:3">
<div class="dm-name"><div class="dm-avatar">SN</div><div><div class="dm-name-text">Sara Nakamura</div><div class="dm-stage-text">FHA, partial app</div></div></div>
<div class="dm-amount">$295k</div>
<div class="dm-last"><span class="dm-last-stack"><span class="last-cold">84 days</span><span class="last-fresh">0 days</span></span></div>
<div class="dm-status"><span class="dm-pill"><span class="dm-pill-dot"></span><span class="dm-pill-lbl"><span class="lbl-cold">Cold · 84d</span><span class="lbl-scan">Scanning…</span><span class="lbl-draft">Drafting…</span><span class="lbl-sent">Sent</span><span class="lbl-ok">Replied</span></span></span></div>
<div class="dm-scan-sweep"></div>
</div>
<div class="dm-row" style="--i:4">
<div class="dm-name"><div class="dm-avatar">DA</div><div><div class="dm-name-text">Diego Alvarez</div><div class="dm-stage-text">Investor, paused</div></div></div>
<div class="dm-amount">$1.2M</div>
<div class="dm-last"><span class="dm-last-stack"><span class="last-cold">51 days</span><span class="last-fresh">0 days</span></span></div>
<div class="dm-status"><span class="dm-pill"><span class="dm-pill-dot"></span><span class="dm-pill-lbl"><span class="lbl-cold">Cold · 51d</span><span class="lbl-scan">Scanning…</span><span class="lbl-draft">Drafting…</span><span class="lbl-sent">Sent</span><span class="lbl-ok">Replied</span></span></span></div>
<div class="dm-scan-sweep"></div>
</div>
<div class="dm-row" style="--i:5">
<div class="dm-name"><div class="dm-avatar">LO</div><div><div class="dm-name-text">Lena Okafor</div><div class="dm-stage-text">Cash-out refi inquiry</div></div></div>
<div class="dm-amount">$418k</div>
<div class="dm-last"><span class="dm-last-stack"><span class="last-cold">39 days</span><span class="last-fresh">0 days</span></span></div>
<div class="dm-status"><span class="dm-pill"><span class="dm-pill-dot"></span><span class="dm-pill-lbl"><span class="lbl-cold">Cold · 39d</span><span class="lbl-scan">Scanning…</span><span class="lbl-draft">Drafting…</span><span class="lbl-sent">Sent</span><span class="lbl-ok">Replied</span></span></span></div>
<div class="dm-scan-sweep"></div>
</div>
<div class="dm-row" style="--i:6">
<div class="dm-name"><div class="dm-avatar">TW</div><div><div class="dm-name-text">Tom Wallace</div><div class="dm-stage-text">First-time buyer</div></div></div>
<div class="dm-amount">$365k</div>
<div class="dm-last"><span class="dm-last-stack"><span class="last-cold">73 days</span><span class="last-fresh">0 days</span></span></div>
<div class="dm-status"><span class="dm-pill"><span class="dm-pill-dot"></span><span class="dm-pill-lbl"><span class="lbl-cold">Cold · 73d</span><span class="lbl-scan">Scanning…</span><span class="lbl-draft">Drafting…</span><span class="lbl-sent">Sent</span><span class="lbl-ok">Replied</span></span></span></div>
<div class="dm-scan-sweep"></div>
</div>
</div>
</div>
<p class="dm-cap">Dormant leads are revenue sitting in boxes…</p>
</div>
/* All animations driven by ONE 14s loop; per-row --i (0..6) staggers by 1.4s.
Same delay drives row bg, avatar, pill bg/dot, the 5 stacked labels,
and the pink scan-sweep so they stay in lock-step. */
.dormant-mine .dm-row {
animation: dm-row-cycle 14s linear infinite;
animation-delay: calc(var(--i, 0) * -1.4s);
}
@keyframes dm-row-cycle {
0%, 9% { background: var(--paper); } /* cold */
12%, 18% { background: color-mix(in oklab, var(--accent) 6%, var(--paper)); } /* scanning */
21%, 31% { background: color-mix(in oklab, var(--accent) 7%, var(--paper)); } /* drafting */
34%, 44% { background: color-mix(in oklab, var(--info) 6%, var(--paper)); } /* sent */
47%, 92% { background: color-mix(in oklab, var(--green-500) 9%, var(--paper)); } /* replied */
100% { background: var(--paper); }
}
/* The 5 status labels share grid-area so they stack; only one is opaque
at a time per the row's phase. */
.dormant-mine .dm-pill-lbl > span { grid-area: 1 / 1; opacity: 0;
animation: 14s linear infinite;
animation-delay: calc(var(--i, 0) * -1.4s); }
@keyframes dm-lbl-cold { 0%, 9% { opacity: 1; } 10%, 100% { opacity: 0; } }
@keyframes dm-lbl-scan { 0%, 9% { opacity: 0; } 12%, 18% { opacity: 1; } 19%, 100% { opacity: 0; } }
@keyframes dm-lbl-draft { 0%, 18% { opacity: 0; } 21%, 31% { opacity: 1; } 32%, 100% { opacity: 0; } }
@keyframes dm-lbl-sent { 0%, 31% { opacity: 0; } 34%, 44% { opacity: 1; } 45%, 100% { opacity: 0; } }
@keyframes dm-lbl-ok { 0%, 44% { opacity: 0; } 47%, 92% { opacity: 1; } 95%, 100% { opacity: 0; } }
/* Pink scan-sweep that runs L→R across each row during the scan phase. */
.dormant-mine .dm-scan-sweep {
position: absolute; top: 0; bottom: 0; left: 0; width: 70px;
background: linear-gradient(90deg, transparent,
color-mix(in oklab, var(--accent) 32%, transparent) 50%, transparent);
opacity: 0;
animation: dm-sweep 14s linear infinite;
animation-delay: calc(var(--i, 0) * -1.4s);
}
@keyframes dm-sweep {
0%, 9% { left: 0; opacity: 0; }
10% { left: 0; opacity: 1; }
19% { left: 100%; opacity: 1; }
20%, 100%{ left: 100%; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
/* End-state snapshot — every row replied. Same story without motion. */
.dormant-mine .dm-row,
.dormant-mine .dm-avatar,
.dormant-mine .dm-pill,
.dormant-mine .dm-pill-dot,
.dormant-mine .dm-pill-lbl > span,
.dormant-mine .dm-scan-sweep { animation: none; }
.dormant-mine .dm-pill-lbl .lbl-ok { opacity: 1; }
}
import { DormantMine } from "@magicblocksai/ui";
<DormantMine
title="Dormant inquiries · Mortgage CRM"
totalLabel="7 of 1,284 shown"
revChipLabel="Revenue recovered"
revChipValue="$482,400"
revChipDelta="7 leads re-engaged"
rows={[
{ id: "mr", initials: "MR", name: "Marcus Rivera", stage: "30-yr fixed inquiry", amount: "$485k", lastTouch: "47 days" },
// …6 more rows; rows cycle cold → scanning → drafting → sent → replied
]}
caption="Dormant leads are revenue sitting in boxes."
/>
A two-column contrast table. Left column strikes through a losing value; right column pink-tinted with a one-shot glow on reveal. Used for every "most teams vs MagicBlocks" moment on the site.
Values strike through on the losing side; the winning side gets a pink-tinted cell + one-shot glow on first view.
<div class="scoreboard reveal">
<div class="sb-head">
<div></div>
<div class="bad">Most teams</div>
<div class="good">With MagicBlocks</div>
</div>
<div class="sb-row"><div class="label">Response time</div><div class="bad">8–12 minutes</div><div class="good">Under 5 seconds</div></div>
<div class="sb-row"><div class="label">Contact rate</div><div class="bad">15–20%</div><div class="good">2–4× higher</div></div>
<div class="sb-row"><div class="label">Follow-up consistency</div><div class="bad">Drops off by day 3</div><div class="good">Every lead, every time</div></div>
<div class="sb-row"><div class="label">Qualification</div><div class="bad">Manual, inconsistent</div><div class="good">Automatic, pre-handoff</div></div>
<div class="sb-row"><div class="label">Task completion</div><div class="bad">59% (single-prompt)</div><div class="good">97.5% (multi-prompt)</div></div>
<div class="sb-row"><div class="label">Hallucination rate</div><div class="bad">Unbounded</div><div class="good">Guardian-gated</div></div>
</div>
<div class="scoreboard-foot">Stress-tested across 400 simulated sessions · <strong>Z = 9.33</strong> · <em>p < 0.00001</em></div>
.scoreboard { display: grid; grid-template-columns: minmax(180px, 1fr) 1fr 1fr; border: 1px solid var(--hair); border-radius: var(--r-lg); overflow: hidden; }
.scoreboard .sb-head { display: contents; }
.scoreboard .sb-row { display: contents; }
.scoreboard .sb-row .bad { text-decoration: line-through; background: color-mix(in oklab, var(--error) 6%, transparent); }
.scoreboard .sb-row .good { background: color-mix(in oklab, var(--success) 8%, transparent); font-weight: 500; }
.scoreboard.is-visible .sb-row .good::before { /* one-shot glow */ animation: sb-glow 1200ms ease-out 600ms forwards; }
import { Scoreboard } from "@magicblocksai/ui";
<Scoreboard
tone="light" // or "dark" for the war-room variant
badHeading="Most teams"
goodHeading="With MagicBlocks"
rows={[
{ id: "rt", label: "Response time", bad: "8–12 minutes", good: "Under 5 seconds" },
{ id: "cr", label: "Contact rate", bad: "15–20%", good: "2–4× higher" },
// …more rows
]}
footer={<>Stress-tested across 400 sessions · <strong>Z = 9.33</strong></>}
/>
War-room surface. Pairs well with `.hero-bloom-canvas[data-variant="war-room"]` sections.
Split timestamp sequence — left track loses, right track wins. Replaces every "race lane" visual in the doc with a cleaner time-based contrast.
Used on the homepage signature proof + every use-case page variant.
<div class="race-timeline reveal">
<div class="rt-head bad">Without MagicBlocks</div>
<div class="rt-midline" aria-hidden="true"></div>
<div class="rt-head good">With MagicBlocks</div>
<div class="rt-tick rt-left">
<span class="rt-stamp">9:47 AM</span>
<div class="rt-body">Lead submits form<em>Enters CRM queue</em></div>
</div>
<div class="rt-tick rt-right win">
<span class="rt-stamp">9:47:00</span>
<div class="rt-body"><strong>Lead submits</strong><em>Engine fires</em></div>
</div>
<div class="rt-tick rt-left">
<span class="rt-stamp">9:55 AM</span>
<div class="rt-body">Rep reads notification<em>Between calls</em></div>
</div>
<div class="rt-tick rt-right win">
<span class="rt-stamp">9:47:08</span>
<div class="rt-body"><strong>Personalised conversation begins</strong><em>Across SMS</em></div>
</div>
<div class="rt-tick rt-left">
<span class="rt-stamp">10:12 AM</span>
<div class="rt-body">Rep calls back — no answer<em>Leaves voicemail</em></div>
</div>
<div class="rt-tick rt-right win">
<span class="rt-stamp">9:48:30</span>
<div class="rt-body"><strong>Lead qualified</strong><em>HAPPA checkpoints cleared</em></div>
</div>
<div class="rt-tick rt-left">
<span class="rt-stamp">2:30 PM</span>
<div class="rt-body">Lead went with competitor<em>Deal lost</em></div>
</div>
<div class="rt-tick rt-right win final">
<span class="rt-stamp">9:49:15</span>
<div class="rt-body"><strong>Appointment booked</strong><em>Handed off with full context</em></div>
</div>
</div>
.race-timeline { display: grid; grid-template-columns: 1fr auto 1fr; }
.race-timeline .rt-midline { grid-row: 2 / span 99; margin: 0 var(--s-6); width: 2px; background: linear-gradient(var(--hair), var(--accent)); }
.race-timeline .rt-left { grid-column: 1; text-align: right; direction: rtl; }
.race-timeline .rt-left > * { direction: ltr; }
.race-timeline .rt-right { grid-column: 3; }
.race-timeline .rt-tick.win .rt-stamp::after { content: "✓"; }
import { RaceTimeline } from "@magicblocksai/ui";
<RaceTimeline
leftHeading="Without MagicBlocks"
rightHeading="With MagicBlocks"
pairs={[
{
id: "submit",
leftStamp: "9:47 AM", leftBody: "Lead submits form", leftMeta: "Enters CRM queue",
rightStamp: "9:47:00", rightBody: <strong>Lead submits</strong>, rightMeta: "Engine fires",
},
// …more pairs; the last pair carries `final: true`
]}
/>
A central ink-block engine with four channel icons orbiting it. Lead sources stack on the left, qualified outputs on the right. Replaces the "lead conversion engine" diagram (plus the "knowledge flow" variant).
Orbit rotates at 40s / revolution. Sources left, outputs right. Mobile: orbit disappears and channels become a chip row under the engine.
<div class="engine-block reveal">
<div class="engine-sources">
<div class="engine-source"><span class="ico">◼</span>Website forms</div>
<div class="engine-source"><span class="ico">◼</span>Facebook / Instagram</div>
<div class="engine-source"><span class="ico">◼</span>Google Ads</div>
<div class="engine-source"><span class="ico">◼</span>Marketplaces</div>
<div class="engine-source"><span class="ico">◼</span>CRM backfill</div>
</div>
<div class="engine-centre">
<svg class="engine-orbit" viewBox="0 0 320 320">
<g class="orbit-anim">
<circle class="ring" cx="160" cy="160" r="140"/>
<g class="channel"><circle class="bg" cx="160" cy="20" r="20"/><text class="lbl" x="160" y="20">SMS</text></g>
<g class="channel"><circle class="bg" cx="300" cy="160" r="20"/><text class="lbl" x="300" y="160">MAIL</text></g>
<g class="channel"><circle class="bg" cx="160" cy="300" r="20"/><text class="lbl" x="160" y="300">CHAT</text></g>
<g class="channel"><circle class="bg" cx="20" cy="160" r="20"/><text class="lbl" x="20" y="160">VOICE</text></g>
</g>
</svg>
<div class="engine-core">
<div class="label">The engine</div>
<div class="name">MagicBlocks<br><em>conversion engine</em></div>
<div class="pill-row"><span>Engage</span><span>Qualify</span><span>Follow up</span><span>Reengage</span></div>
</div>
</div>
<div class="engine-outputs">
<div class="engine-output">Qualified conversations <span class="ico">→</span></div>
<div class="engine-output">Booked appointments <span class="ico">→</span></div>
<div class="engine-output">Context-rich handoffs <span class="ico">→</span></div>
</div>
</div>
.engine-block { display: grid; grid-template-columns: minmax(140px, 180px) 1fr minmax(140px, 180px); gap: var(--s-5); }
.engine-orbit .orbit-anim { transform-origin: center; animation: eb-orbit 40s linear infinite; }
@keyframes eb-orbit { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .engine-orbit .orbit-anim { animation: none; } }
import { EngineBlock } from "@magicblocksai/ui";
<EngineBlock
sources={["Website forms", "Facebook / Instagram", "Google Ads", "Marketplaces", "CRM backfill"]}
outputs={[
<>Qualified conversations <span className="ico">→</span></>,
<>Booked appointments <span className="ico">→</span></>,
<>Context-rich handoffs <span className="ico">→</span></>,
]}
coreLabel="The engine"
coreName="MagicBlocks"
coreSubline="conversion engine"
pills={["Engage", "Qualify", "Follow up", "Reengage"]}
/>
Three equal panels with arrow dividers. Used for "How It Works" across every page. Each panel has an eyebrow, title, body, and an art slot that can hold any other chapter-11 component.
On mobile, panels stack and arrows rotate to ↓.
Forms, marketplaces, ad platforms, your CRM. Setup takes days, not months.
Instant, personalised conversations on every channel. Qualifies, nurtures, follows up.
Pre-qualified, warm handoffs with full context. No more cold calls.
<div class="triptych reveal">
<div class="panel">
<div class="step">01 · Connect</div>
<h4>Plug in your leads</h4>
<p>Forms, marketplaces, ad platforms, your CRM.</p>
<div class="art"><!-- any chapter-11 atom --></div>
</div>
<div class="arrow"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12h16m-6-6 6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
<div class="panel">
<div class="step">02 · Run</div>
<h4>The engine takes over</h4>
<p>Instant, personalised conversations on every channel. Qualifies, nurtures, follows up.</p>
<div class="art"><span class="stat-badge"><span class="eyebrow">Avg.</span><span class="num">5<sup>s</sup></span><span class="cap">First response</span></span></div>
</div>
<div class="arrow"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12h16m-6-6 6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></div>
<div class="panel">
<div class="step">03 · Close</div>
<h4>Your team closes deals</h4>
<p>Pre-qualified, warm handoffs with full context. No more cold calls.</p>
<div class="art"><span class="stat-badge"><span class="eyebrow">Task completion</span><span class="num">97.5<sup>%</sup></span><span class="cap">vs 59% single-prompt</span></span></div>
</div>
</div>
.triptych { display: grid; grid-template-columns: 1fr auto 1fr auto 1fr; gap: var(--s-5); }
@media (max-width: 880px) { .triptych { grid-template-columns: 1fr; } .triptych .arrow { transform: rotate(90deg); } }
import { Triptych } from "@magicblocksai/ui";
<Triptych
panels={[
{ id: "connect", step: "01 · Connect", title: "Plug in your leads",
body: "Forms, marketplaces, ad platforms, your CRM. Setup takes days." },
{ id: "run", step: "02 · Run", title: "The engine takes over",
body: "Instant, personalised conversations on every channel." },
{ id: "close", step: "03 · Close", title: "Your team closes deals",
body: "Pre-qualified, warm handoffs with full context." },
]}
/>
A credential-style card showing a qualified lead ready for human handoff. Five industry variants via `data-role` — each shifts accent colour, role label, and qualification tags.
Card lifts in on reveal; name gets a coloured underline sweep; a "new" pulse indicates fresh handoff.
Taylor Simmons
Mortgage · Loan officer
Priya Ramanathan
Insurance · Agent
Marcus Kim
Tourism · Booking specialist
<!-- 1. Mortgage loan officer (default accent) -->
<article class="handoff-card reveal" data-role="mortgage-lo">
<span class="ho-pulse" aria-hidden="true"></span>
<div class="ho-band">
<span class="ho-av">TS</span>
<div class="ho-who">
<p class="name">Taylor Simmons</p>
<p class="role">Mortgage · Loan officer</p>
</div>
<span class="ho-stamp">09:49</span>
</div>
<div class="ho-tags">
<span class="ho-tag lead">Qualified</span>
<span class="ho-tag">$450k</span>
<span class="ho-tag">30-yr fixed</span>
<span class="ho-tag">Prefers SMS</span>
</div>
<div class="ho-facts">
<div class="ho-fact"><div class="k">Income</div><div class="v">$145k</div></div>
<div class="ho-fact"><div class="k">Timeline</div><div class="v"><em>4–8 weeks</em></div></div>
<div class="ho-fact"><div class="k">Credit band</div><div class="v">740+</div></div>
<div class="ho-fact"><div class="k">Intent score</div><div class="v">92</div></div>
</div>
<div class="ho-cta">
<button class="primary">Call now</button>
<button>Send rate</button>
<button>Notes</button>
</div>
</article>
<!-- 2. Insurance agent (green accent) -->
<article class="handoff-card reveal" data-role="insurance-agent">
<span class="ho-pulse" aria-hidden="true"></span>
<div class="ho-band">
<span class="ho-av">PR</span>
<div class="ho-who">
<p class="name">Priya Ramanathan</p>
<p class="role">Insurance · Agent</p>
</div>
<span class="ho-stamp">16:02</span>
</div>
<div class="ho-tags">
<span class="ho-tag lead">Qualified</span>
<span class="ho-tag">Auto + Home</span>
<span class="ho-tag">Bundling quote</span>
<span class="ho-tag">Prefers email</span>
</div>
<div class="ho-facts">
<div class="ho-fact"><div class="k">Current carrier</div><div class="v">State Farm</div></div>
<div class="ho-fact"><div class="k">Renewal date</div><div class="v">23 days</div></div>
<div class="ho-fact"><div class="k">Premium</div><div class="v">$2,140/yr</div></div>
<div class="ho-fact"><div class="k">Intent score</div><div class="v">81</div></div>
</div>
<div class="ho-cta">
<button class="primary">Send quote</button>
<button>Schedule</button>
</div>
</article>
<!-- 3. Booking specialist (warm-orange accent) -->
<article class="handoff-card reveal" data-role="booking-specialist">
<span class="ho-pulse" aria-hidden="true"></span>
<div class="ho-band">
<span class="ho-av">MK</span>
<div class="ho-who">
<p class="name">Marcus Kim</p>
<p class="role">Tourism · Booking specialist</p>
</div>
<span class="ho-stamp">08:14</span>
</div>
<div class="ho-tags">
<span class="ho-tag lead">Qualified</span>
<span class="ho-tag">Family of 4</span>
<span class="ho-tag">2 nights</span>
<span class="ho-tag">Dec 18</span>
</div>
<div class="ho-facts">
<div class="ho-fact"><div class="k">Party size</div><div class="v">4</div></div>
<div class="ho-fact"><div class="k">Budget</div><div class="v">$1,800</div></div>
<div class="ho-fact"><div class="k">Prior guest</div><div class="v"><em>yes</em></div></div>
<div class="ho-fact"><div class="k">Intent score</div><div class="v">96</div></div>
</div>
<div class="ho-cta">
<button class="primary">Confirm booking</button>
<button>Upgrade</button>
</div>
</article>
/* role colours via a single custom property */
.handoff-card { --ho-accent: var(--accent); }
.handoff-card[data-role="insurance-agent"] { --ho-accent: var(--green-700); }
.handoff-card[data-role="counsellor"] { --ho-accent: var(--blue-700); }
.handoff-card[data-role="front-desk"] { --ho-accent: var(--yellow-700); }
.handoff-card[data-role="booking-specialist"] { --ho-accent: #C77A3E; }
.handoff-card .ho-band { background: color-mix(in oklab, var(--ho-accent) 12%, var(--bg-paper)); }
.handoff-card.is-visible .ho-who .name::after { animation: ho-underline 700ms ease-out 300ms backwards; }
import { HandoffCard } from "@magicblocksai/ui";
// 1. Mortgage loan officer (default accent)
<HandoffCard
role="mortgage-lo"
initials="TS"
name="Taylor Simmons"
roleLabel="Mortgage · Loan officer"
stamp="09:49"
tags={["Qualified", "$450k", "30-yr fixed", "Prefers SMS"]}
facts={[
{ id: "income", k: "Income", v: "$145k" },
{ id: "timeline", k: "Timeline", v: <em>4–8 weeks</em> },
{ id: "credit", k: "Credit band", v: "740+" },
{ id: "score", k: "Intent score", v: "92" },
]}
actions={[
{ id: "call", label: "Call now", primary: true },
{ id: "rate", label: "Send rate" },
{ id: "notes", label: "Notes" },
]}
/>
// 2. Insurance agent (green accent)
<HandoffCard
role="insurance-agent"
initials="PR"
name="Priya Ramanathan"
roleLabel="Insurance · Agent"
stamp="16:02"
tags={["Qualified", "Auto + Home", "Bundling quote", "Prefers email"]}
facts={[
{ id: "carrier", k: "Current carrier", v: "State Farm" },
{ id: "renewal", k: "Renewal date", v: "23 days" },
{ id: "premium", k: "Premium", v: "$2,140/yr" },
{ id: "score", k: "Intent score", v: "81" },
]}
actions={[
{ id: "quote", label: "Send quote", primary: true },
{ id: "schedule", label: "Schedule" },
]}
/>
// 3. Booking specialist (warm-orange accent)
<HandoffCard
role="booking-specialist"
initials="MK"
name="Marcus Kim"
roleLabel="Tourism · Booking specialist"
stamp="08:14"
tags={["Qualified", "Family of 4", "2 nights", "Dec 18"]}
facts={[
{ id: "party", k: "Party size", v: "4" },
{ id: "budget", k: "Budget", v: "$1,800" },
{ id: "prior", k: "Prior guest", v: <em>yes</em> },
{ id: "score", k: "Intent score", v: "96" },
]}
actions={[
{ id: "confirm", label: "Confirm booking", primary: true },
{ id: "upgrade", label: "Upgrade" },
]}
/>
A card that visibly accretes memory as the AI learns more about a lead. Shows five progressive "ticks" of relationship memory. Secondary "dormant then reactivated" state illustrates the Reengage use case.
Auto-cycles when in view; respects reduced-motion by rendering the fully-grown state immediately.
Morgan Shaw
Jae Kim
Jae Kim
<!-- 1. Growing memory · auto-cycles 0 → 5 -->
<div class="profile-card reveal" data-step="0" data-max-step="5" data-interval="1100" data-auto-cycle>
<header class="pc-head">
<span class="pc-av">MS</span>
<div><p class="pc-name">Morgan Shaw</p><p class="pc-email">[email protected]</p></div>
</header>
<div class="pc-bars"><span class="pc-bar"></span>×5</div>
<div class="pc-rows">
<div class="pc-row"><span class="ico">✎</span><span>First message at 09:47 · SMS</span><span class="meta">Day 1</span></div>
<div class="pc-row"><span class="ico">!</span><span>Raised pricing concern · resolved</span><span class="meta">Day 1</span></div>
<div class="pc-row"><span class="ico">☎</span><span>Prefers SMS over email</span><span class="meta">Day 2</span></div>
<div class="pc-row"><span class="ico">◷</span><span>Mentioned "after holidays" timeline</span><span class="meta">Day 3</span></div>
<div class="pc-row"><span class="ico">✓</span><span>Intent score raised to 87</span><span class="meta">Day 4</span></div>
</div>
<div class="pc-status"><span>Active relationship</span><span>5 / 5 signals</span></div>
</div>
<!-- 2. Dormant · 3/5 bars filled, desaturated -->
<div class="profile-card reveal is-dormant">
<header class="pc-head">
<span class="pc-av">JK</span>
<div><p class="pc-name">Jae Kim</p><p class="pc-email">[email protected]</p></div>
</header>
<div class="pc-bars">
<span class="pc-bar" style="background: var(--accent);"></span>
<span class="pc-bar" style="background: var(--accent);"></span>
<span class="pc-bar" style="background: var(--accent);"></span>
<span class="pc-bar"></span>
<span class="pc-bar"></span>
</div>
<div class="pc-rows">
<div class="pc-row" style="opacity: 1; transform: none;"><span class="ico">✎</span><span>Requested rate quote · March</span><span class="meta">Day 1</span></div>
<div class="pc-row" style="opacity: 1; transform: none;"><span class="ico">◷</span><span>Went quiet after pre-approval</span><span class="meta">Day 4</span></div>
</div>
<div class="pc-status"><span>Dormant · 47 days</span><span>Queued for reactivation</span></div>
</div>
<!-- 3. Reactivated · all 5 bars + accent ring -->
<div class="profile-card reveal is-reactivated">
<header class="pc-head">
<span class="pc-av">JK</span>
<div><p class="pc-name">Jae Kim</p><p class="pc-email">[email protected]</p></div>
</header>
<div class="pc-bars">
<span class="pc-bar" style="background: var(--accent);"></span>
<span class="pc-bar" style="background: var(--accent);"></span>
<span class="pc-bar" style="background: var(--accent);"></span>
<span class="pc-bar" style="background: var(--accent);"></span>
<span class="pc-bar" style="background: var(--accent);"></span>
</div>
<div class="pc-rows">
<div class="pc-row" style="opacity: 1; transform: none;"><span class="ico">⚡</span><span>Rate-drop trigger · sent SMS</span><span class="meta">Day 48</span></div>
<div class="pc-row" style="opacity: 1; transform: none;"><span class="ico">✓</span><span>Replied within 6 min</span><span class="meta">Day 48</span></div>
<div class="pc-row" style="opacity: 1; transform: none;"><span class="ico">!</span><span>Scheduled recap call</span><span class="meta">Day 48</span></div>
</div>
<div class="pc-status"><span>Reactivated</span><span>Back in pipeline</span></div>
</div>
/* rows reveal based on [data-step] */
.profile-card .pc-row { opacity: 0; transform: translateY(4px); transition: opacity 300ms, transform 300ms; }
.profile-card[data-step="1"] .pc-row:nth-child(-n+1),
.profile-card[data-step="2"] .pc-row:nth-child(-n+2),
/* … */
.profile-card[data-step="5"] .pc-row { opacity: 1; transform: none; }
.profile-card.is-dormant { filter: grayscale(0.6); opacity: 0.7; }
.profile-card.is-reactivated { box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent); }
import { ProfileCard } from "@magicblocksai/ui";
// 1. Growing memory · auto-cycles 0 → 5 ticks
<ProfileCard
state="growing"
initials="MS"
name="Morgan Shaw"
email="[email protected]"
autoCycle
rows={[
{ id: "r1", icon: "✎", text: "First message at 09:47 · SMS", meta: "Day 1" },
{ id: "r2", icon: "!", text: "Raised pricing concern · resolved", meta: "Day 1" },
{ id: "r3", icon: "☎", text: "Prefers SMS over email", meta: "Day 2" },
{ id: "r4", icon: "◷", text: "Mentioned \"after holidays\" timeline", meta: "Day 3" },
{ id: "r5", icon: "✓", text: "Intent score raised to 87", meta: "Day 4" },
]}
statusLeft="Active relationship"
statusRight="5 / 5 signals"
/>
// 2. Dormant · 3/5 bars filled, desaturated
<ProfileCard
state="dormant"
initials="JK"
name="Jae Kim"
email="[email protected]"
filledBars={3}
rows={[
{ id: "r1", icon: "✎", text: "Requested rate quote · March", meta: "Day 1" },
{ id: "r2", icon: "◷", text: "Went quiet after pre-approval", meta: "Day 4" },
]}
statusLeft="Dormant · 47 days"
statusRight="Queued for reactivation"
/>
// 3. Reactivated · all 5 bars + accent ring
<ProfileCard
state="reactivated"
initials="JK"
name="Jae Kim"
email="[email protected]"
filledBars={5}
rows={[
{ id: "r1", icon: "⚡", text: "Rate-drop trigger · sent SMS", meta: "Day 48" },
{ id: "r2", icon: "✓", text: "Replied within 6 min", meta: "Day 48" },
{ id: "r3", icon: "!", text: "Scheduled recap call", meta: "Day 48" },
]}
statusLeft="Reactivated"
statusRight="Back in pipeline"
/>
Radial diagram — central MagicBlocks hub, integration chips arranged on a ring. Each chip is a branded pill rather than a real logo. Industry variants swap the chip set.
Chips are `.chip`-styled pills with mono labels. Spokes are dashed hairlines converging on the core. Motion = chips fade clockwise on reveal.
<div class="integration-hub reveal">
<svg class="spokes" viewBox="0 0 520 520" aria-hidden="true">
<line x1="260" y1="260" x2="260" y2="30"/>
<line x1="260" y1="260" x2="420" y2="95"/>
<line x1="260" y1="260" x2="500" y2="260"/>
<line x1="260" y1="260" x2="420" y2="425"/>
<line x1="260" y1="260" x2="260" y2="490"/>
<line x1="260" y1="260" x2="100" y2="425"/>
<line x1="260" y1="260" x2="20" y2="260"/>
<line x1="260" y1="260" x2="100" y2="95"/>
</svg>
<div class="hub-core">
<img src="/02-icon/svg/magicblocks-icon-mono-white.svg" alt="MagicBlocks" aria-label="MagicBlocks engine"/>
</div>
<div class="nodes">
<span class="node" style="top: 6%; left: 50%;">Salesforce</span>
<span class="node" style="top: 18%; left: 81%;">HubSpot</span>
<span class="node" style="top: 50%; left: 96%;">Twilio</span>
<span class="node" style="top: 82%; left: 81%;">Calendly</span>
<span class="node" style="top: 94%; left: 50%;">Zapier</span>
<span class="node" style="top: 82%; left: 19%;">Segment</span>
<span class="node" style="top: 50%; left: 4%;">Stripe</span>
<span class="node" style="top: 18%; left: 19%;">Slack</span>
</div>
</div>
.integration-hub { position: relative; aspect-ratio: 1; }
.integration-hub .hub-core { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--ink); color: var(--paper); }
.integration-hub .node { position: absolute; transform: translate(-50%, -50%); padding: 6px 10px; background: var(--bg-paper); border: 1px solid var(--hair); border-radius: 999px; font: 500 11px/1 var(--f-mono); }
.integration-hub .spokes line { stroke: var(--hair); stroke-dasharray: 2 4; }
import { IntegrationHub } from "@magicblocksai/ui";
<IntegrationHub
core={<img src="/02-icon/svg/magicblocks-icon-mono-white.svg" alt="MagicBlocks" />}
nodes={[
{ id: "salesforce", label: "Salesforce", top: "6%", left: "50%" },
{ id: "hubspot", label: "HubSpot", top: "18%", left: "81%" },
// …8 chips total at ring positions
]}
/>
Five conversation stages — Hook · Align · Personalise · Pitch · Action — anchored along a soft S-curve. Branches off each node show adaptive moves (objection routed to handler, etc.).
<div class="happa-arc reveal svg-draw" style="--draw-len: 1400;">
<svg viewBox="0 0 1200 320" role="img" aria-label="HAPPA arc">
<path class="arc-path draw" d="M 120 170 C …"/>
<g transform="translate(120 170)">
<circle class="node-ring" r="26"/>
<text class="node-letter">H</text>
<text class="node-label" y="62">Hook</text>
<text class="node-desc" y="82">Open with relevance</text>
</g>
<!-- A · P · P · A nodes at x = 360, 600, 840, 1080 -->
</svg>
</div>
.happa-arc {
position: relative;
width: 100%; max-width: 1080px;
/* v1.25.0 (Website Round W5-R018): centre by default. See `.scoreboard`. */
margin-inline: auto;
padding: 20px 0 60px;
}
.happa-arc svg { width: 100%; height: auto; display: block; }
.happa-arc .arc-path {
fill: none; stroke: var(--fg); /* dark-mode safe — flips to warm cream on dark */
stroke-width: 2;
stroke-linecap: round; stroke-dasharray: 6 8;
}
.happa-arc .node-ring { fill: var(--bg-paper); stroke: var(--accent); stroke-width: 2; }
.happa-arc .node-letter {
font: 700 22px/1 var(--f-display); fill: var(--accent); text-anchor: middle; dominant-baseline: central;
}
.happa-arc .node-label {
font: 600 16px/1 var(--f-display); fill: var(--fg); text-anchor: middle; letter-spacing: -0.005em;
}
.happa-arc .node-desc {
font: 400 14px/1.3 var(--f-body); fill: var(--fg-soft); text-anchor: middle;
}
.happa-arc .tracer-halo { fill: color-mix(in oklab, var(--accent) 30%, transparent); }
.happa-arc .tracer-dot { fill: var(--accent); }
@media (prefers-reduced-motion: reduce) {
.happa-arc .tracer { display: none; }
}
@media (max-width: 720px) {
/* Demo stage margins contract so every component has more horizontal room */
.ns-stage.pad { padding: var(--s-5) var(--s-4); }
/* Decay curve: shrink callout text & leader reach so they still fit at narrower widths */
.decay-curve .callout-title,
.decay-curve .callout-title.won { font-size: 12px; }
.decay-curve .callout-sub,
.decay-curve .callout-sub.lost { font-size: 9.5px; letter-spacing: 0.08em; }
/* Happa arc gets cramped — shrink node descriptions */
.happa-arc .node-desc { font-size: 10px; }
/* Integration hub: chips can overlap at small scale — shrink text */
.integration-hub .node { font-size: 9.5px; padding: 4px 7px; }
/* Race timeline body copy compacts */
.race-timeline .rt-body { font-size: 12.5px; }
.race-timeline .rt-tick .rt-body em { font-size: 11px; }
}
/* …additional rules trimmed for brevity — see _shared.css */
import { HappaArc } from "@magicblocksai/ui";
<HappaArc ariaLabel="HAPPA conversation arc: Hook, Align, Personalise, Pitch, Action" />
// Or with custom stage copy:
<HappaArc
stages={[
{ letter: "H", label: "Hook", description: "Acknowledge the rate" },
{ letter: "A", label: "Align", description: "Confirm timeline" },
{ letter: "P", label: "Personalise", description: "Use draft app" },
{ letter: "P", label: "Pitch", description: "Concrete monthly saving" },
{ letter: "A", label: "Action", description: "Two booking slots" },
]}
/>
One shield, one promise. Six universal compliance primitives — consent, privacy, encryption, retention, opt-out, audit — that apply across every vertical we serve. Industry-specific extras (TCPA, RESPA, HIPAA, etc.) configure in the product; the shield on the brand page stays universal.
Used on marketing surfaces — pricing, enterprise, trust/security pages — anywhere the question "are you safe to deploy?" lands. Draws in on reveal, shine sweeps on loop.
<div class="guardian-shield reveal svg-draw" style="--draw-len: 900;">
<svg viewBox="0 0 200 240" aria-label="Guardian shield">
<path class="sh-body draw" d="M 20 28 …Z"/>
<path class="sh-shine" d="M 20 28 …Z"/>
</svg>
<div class="sh-items">
<span class="sh-item">Consent</span>
<!-- Privacy · Encryption · Retention · Opt-out · Audit -->
</div>
<div class="sh-caption">Guardian · every lead, every vertical</div>
</div>
.guardian-shield {
position: relative;
width: 100%; max-width: 320px;
margin: 0 auto;
}
.guardian-shield svg { width: 100%; height: auto; display: block; }
.guardian-shield .sh-body {
fill: color-mix(in oklab, var(--ink) 94%, transparent);
stroke: var(--accent); stroke-width: 2.4;
}
.guardian-shield .sh-shine {
fill: url(#shShine); pointer-events: none;
transform-origin: center; transform-box: view-box;
animation: gs-shine 5.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.guardian-shield .sh-shine { animation: none; }
}
.guardian-shield .sh-items {
position: absolute;
/* Single-column stack fills the shield's tall shape naturally — the
old 2×3 grid left dead space above and below the centred items.
Bigger inset rings the stack with breathing room on all sides. */
inset: 20% 22% 22% 22%;
display: flex; flex-direction: column;
justify-content: center;
gap: 14px;
}
.guardian-shield .sh-item {
display: flex; align-items: center; gap: 10px;
min-width: 0;
font: 600 12.5px/1.15 var(--f-mono);
color: color-mix(in oklab, var(--paper) 94%, transparent);
letter-spacing: 0.08em; text-transform: uppercase;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.guardian-shield .sh-item::before {
content: ""; width: 14px; height: 14px; flex: 0 0 14px;
border-radius: 50%;
background: var(--accent);
display: inline-grid; place-items: center;
/* a tiny white ✓ baked into the bullet disc */
background-image:
linear-gradient(transparent, transparent),
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'><path d='M2 5l2 2 4-4' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
background-size: cover;
}
.guardian-shield .sh-caption {
text-align: center; margin-top: 12px;
font: 500 11.5px/1 var(--f-mono); color: var(--fg-soft);
letter-spacing: 0.14em; text-transform: uppercase;
}
.guardian-shield .sh-caption strong { color: var(--accent-text); font-weight: 600; }
/* …additional rules trimmed for brevity — see _shared.css */
import { GuardianShield } from "@magicblocksai/ui";
<GuardianShield caption={<>Guardian <strong>·</strong> every lead, every vertical</>} />
// Or with custom items:
<GuardianShield items={["TCPA", "GDPR", "HIPAA", "SOC 2"]} />
Horizontal branching flow with labelled connectors. One component, many variants — prequalification · follow-up · reactivation · insurance renewal · healthcare patient loop.
<div class="journey-map reveal">
<div class="jm-node start"><span class="jm-k">Start</span><span class="jm-t">Lead arrives</span></div>
<span class="jm-connector"><svg width="40" height="16" aria-hidden="true"><path class="draw" d="M 2 8 H 36 m-6-4 6 4-6 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
<div class="jm-node" style="--jm-delay: 0s"><span class="jm-k">01</span><span class="jm-t">Engage instantly</span></div>
<span class="jm-connector"><svg width="40" height="16"><path class="draw" d="M 2 8 H 36 m-6-4 6 4-6 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
<div class="jm-node" style="--jm-delay: 2s"><span class="jm-k">02</span><span class="jm-t">Collect context</span></div>
<span class="jm-connector"><svg width="40" height="16"><path class="draw" d="M 2 8 H 36 m-6-4 6 4-6 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
<div class="jm-branch">
<div class="jm-node end" style="--jm-delay: 4s"><span class="jm-k">Yes</span><span class="jm-t">Handoff to sales</span></div>
<div class="jm-node nurture" style="--jm-delay: 4.3s"><span class="jm-k">Not ready</span><span class="jm-t">Drop into nurture</span></div>
<div class="jm-node nurture" style="--jm-delay: 4.6s"><span class="jm-k">Dormant</span><span class="jm-t">Queue for reactivation</span></div>
</div>
</div>
.journey-map {
display: flex; flex-wrap: wrap; align-items: flex-start;
gap: 10px;
max-width: 1080px;
}
.journey-map .jm-node {
position: relative;
background: var(--bg-paper);
border: 1px solid var(--hair);
border-radius: var(--r-md);
padding: 10px 14px;
min-width: 140px;
display: flex; flex-direction: column; gap: 4px;
}
.journey-map .jm-node .jm-k { font: 500 10px/1 var(--f-mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--fg-faint); }
.journey-map .jm-node .jm-t { font: 500 13px/1.25 var(--f-body); color: var(--fg); }
.journey-map .jm-node.start {
background: var(--accent); color: var(--paper); border-color: transparent;
position: relative;
box-shadow: 0 0 0 0 color-mix(in oklab, var(--accent) 50%, transparent);
animation: jm-pulse 2.8s ease-out infinite;
}
.journey-map .jm-node.start .jm-k { color: color-mix(in oklab, var(--paper) 90%, transparent); }
.journey-map .jm-node.start .jm-t { color: var(--paper); }
@media (prefers-reduced-motion: reduce) {
.journey-map .jm-node.start { animation: none; }
}
.journey-map .jm-node.end { background: var(--warm-3); border-color: var(--warm-5); }
.journey-map .jm-node.nurture { border-style: dashed; }
.journey-map .jm-node:not(.start) { position: relative; }
.journey-map .jm-node:not(.start)::after {
content: "";
position: absolute; inset: -2px;
border: 2px solid var(--accent);
border-radius: inherit;
opacity: 0;
pointer-events: none;
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 18%, transparent),
0 8px 18px color-mix(in oklab, var(--accent) 22%, transparent);
animation: jm-stage-active 10s ease-in-out infinite;
animation-delay: var(--jm-delay, 0s);
}
@media (prefers-reduced-motion: reduce) {
.journey-map .jm-node:not(.start)::after { animation: none; opacity: 0; }
}
.journey-map .jm-connector {
display: inline-flex; align-items: center;
min-height: 40px;
}
.journey-map .jm-connector svg { display: block; }
.journey-map .jm-label {
font: 500 10px/1 var(--f-mono); color: var(--fg-faint);
letter-spacing: 0.08em; text-transform: uppercase;
align-self: center;
padding: 0 4px;
}
.journey-map .jm-branch {
display: grid; gap: 6px; padding: 4px 0;
}
/* …additional rules trimmed for brevity — see _shared.css */
import { JourneyMap } from "@magicblocksai/ui";
<JourneyMap
items={[
{ kind: "node", node: { id: "start", k: "Start", t: "Lead arrives", variant: "start" } },
{ kind: "node", node: { id: "01", k: "01", t: "Engage instantly" } },
{ kind: "node", node: { id: "02", k: "02", t: "Collect context" } },
{ kind: "branch", nodes: [
{ id: "yes", k: "Yes", t: "Handoff to sales", variant: "end" },
{ id: "nurture", k: "Not ready", t: "Drop into nurture", variant: "nurture" },
{ id: "dormant", k: "Dormant", t: "Queue for reactivation", variant: "nurture" },
] },
]}
/>
YOU sit at the centre. The edges scatter around you at the distances they actually are. Every cycle a pulse races from each edge towards you — the fastest arrival wins, gets ringed in green, and the latency badge appears next to it. The scene then transitions to the next region (US-WEST → US-EAST → EUROPE → ASIA → OCEANIA, looping). Tells the proximity story directly: the closest server always wins, in single-digit milliseconds, wherever the customer is.
<div class="edge-race reveal" data-edge-race role="img">
<div class="er-region" data-er-region>US WEST</div>
<div class="er-ring r1"></div>
<div class="er-ring r2"></div>
<div class="er-ring r3"></div>
<svg class="er-lines" viewBox="0 0 100 100" preserveAspectRatio="none" data-er-lines aria-hidden="true"></svg>
<div class="er-halo"></div>
<div class="er-halo" style="animation-delay: 1.5s;"></div>
<div class="er-you"></div>
<div class="er-you-label">
<span class="kicker">You in</span>
<span class="name" data-er-username>Oceanside, CA</span>
</div>
<!-- JS appends server pins (.er-server) at angle/distance per region -->
<div data-er-servers></div>
<div class="er-badge" data-er-badge><span class="num">8</span> ms</div>
<!-- JS appends pager dots (.er-pager-dot), one per region -->
<div class="er-pager" data-er-pager></div>
</div>
.edge-race {
position: relative;
width: 100%;
max-width: 540px;
aspect-ratio: 1 / 1;
/* Vertical breathing room — the pager dots sit at bottom: -28px and the
region pill at top: -8px, so account for both plus a comfortable gap. */
margin: var(--s-6) auto var(--s-7);
}
.edge-race .er-ring {
position: absolute; left: 50%; top: 50%;
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
border: 1px dashed color-mix(in oklab, var(--accent) 22%, transparent);
}
.edge-race .er-ring.r1 { width: 50%; height: 50%; }
.edge-race .er-ring.r2 { width: 80%; height: 80%; }
.edge-race .er-ring.r3 {
width: 100%; height: 100%;
border-style: solid;
border-color: color-mix(in oklab, var(--accent) 12%, transparent);
}
.edge-race .er-region {
position: absolute;
top: -8px; left: -8px;
font: 600 10.5px/1 var(--f-mono);
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--accent-text);
background: var(--bg-paper);
border: 1px solid color-mix(in oklab, var(--accent) 30%, var(--hair));
padding: 5px 11px;
border-radius: 4px;
white-space: nowrap;
z-index: 7;
transition: opacity 0.4s;
}
.edge-race .er-you {
position: absolute;
left: 50%; top: 50%;
width: 22px; height: 22px;
margin: -11px 0 0 -11px;
border-radius: 50%;
background: var(--ink);
box-shadow:
0 0 0 4px var(--bg-paper),
0 0 0 5.5px color-mix(in oklab, var(--ink) 18%, transparent),
0 4px 14px color-mix(in oklab, var(--ink) 25%, transparent);
z-index: 5;
}
.edge-race .er-you::before {
content: '';
position: absolute; inset: 6px;
border-radius: 50%;
background: var(--bg-paper);
}
.edge-race .er-you-label {
position: absolute;
left: 50%; top: calc(50% + 22px);
transform: translateX(-50%);
text-align: center;
z-index: 5;
transition: opacity 0.4s;
}
.edge-race .er-you-label .kicker {
font: 600 9.5px/1 var(--f-mono);
letter-spacing: 0.2em;
color: var(--fg-soft);
text-transform: uppercase;
}
.edge-race .er-you-label .name {
display: block; margin-top: 6px;
font: 600 14px/1.2 var(--f-display);
color: var(--fg);
letter-spacing: -0.01em;
}
/* …additional rules trimmed for brevity — see _shared.css */
import { EdgeRace } from "@magicblocksai/ui";
<EdgeRace
regions={[
{
id: "us-west", name: "US WEST", userCity: "Oceanside, CA",
servers: [
{ id: "lax", city: "Los Angeles", distanceKm: 130, angleDeg: 10, latencyMs: 8 },
{ id: "sfo", city: "San Francisco", distanceKm: 740, angleDeg: 350, latencyMs: 24 },
// …more cities
],
},
// …4 more regions: us-east · europe · asia · oceania
]}
/>
Three concentric rings — engine at centre, today's products in the middle ring, tomorrow's vision on the outer ring. For the About page vision section.
<div class="ecosystem-rings reveal">
<svg viewBox="0 0 540 540">
<circle class="er-ring outer" cx="270" cy="270" r="228"/>
<circle class="er-ring" cx="270" cy="270" r="150"/>
<circle class="er-core" cx="270" cy="270" r="70"/>
</svg>
<div class="er-core-label">
<span class="kicker">The engine</span>
<span class="name">MagicBlocks</span>
</div>
<span class="er-label" style="top: 22%; left: 50%;">Engage</span>
<!-- Qualify · Follow up · Reengage on the middle ring; outer-ring labels for vision -->
</div>
.ecosystem-rings {
position: relative;
width: 100%; max-width: 560px; aspect-ratio: 1;
margin: 0 auto;
}
.ecosystem-rings svg { width: 100%; height: 100%; display: block; }
.ecosystem-rings .er-ring {
fill: none;
/* v1.28.0 (Website Round W6-R023): bumped stroke width 1 → 1.6 and
pinned the stroke colour to a 22%-ink tint instead of the
default `--hair` (which can resolve to a near-invisible value
against cream / paper marketing surfaces). The dashed connector
reads as "the engine connects everything" — at 1px / hair tint
it was barely visible at 1× zoom on typical laptop screens. */
stroke: color-mix(in oklab, var(--ink) 22%, transparent);
stroke-width: 1.6;
stroke-dasharray: 2 5;
transform-origin: center; transform-box: fill-box;
animation: er-spin 120s linear infinite;
}
.ecosystem-rings .er-ring.outer {
/* v1.28.0 (W6-R023): bumped accent tint 32% → 55% so the outer
ring reads as the brand-pink anchor of the visual centre. */
stroke: color-mix(in oklab, var(--accent) 55%, transparent);
animation: er-spin-rev 80s linear infinite;
}
.ecosystem-rings .er-core { fill: var(--ink); }
@media (prefers-reduced-motion: reduce) {
.ecosystem-rings .er-ring { animation: none; }
}
.ecosystem-rings .er-core-label {
position: absolute;
left: 50%; top: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: var(--paper);
pointer-events: none;
z-index: 3;
}
.ecosystem-rings .er-core-label .kicker {
display: block;
font: 500 9.5px/1 var(--f-mono); letter-spacing: 0.14em; text-transform: uppercase;
color: color-mix(in oklab, var(--paper) 62%, transparent);
margin-bottom: 4px;
}
.ecosystem-rings .er-core-label .name {
display: block;
font: 700 15px/1.15 var(--f-display); letter-spacing: -0.01em;
}
.ecosystem-rings .er-label {
position: absolute;
transform: translate(-50%, -50%);
font: 600 10.5px/1 var(--f-mono); color: var(--fg);
letter-spacing: 0.1em; text-transform: uppercase;
white-space: nowrap;
padding: 5px 10px;
background: var(--bg-paper);
border: 1px solid var(--hair);
border-radius: 999px;
z-index: 2;
}
.ecosystem-rings .er-label.outer {
color: var(--accent-text);
border-color: color-mix(in oklab, var(--accent) 30%, transparent);
background: color-mix(in oklab, var(--accent) 7%, var(--bg-paper));
}
/* …additional rules trimmed for brevity — see _shared.css */
import { EcosystemRings } from "@magicblocksai/ui";
<EcosystemRings coreLabel="The engine" coreName="MagicBlocks" />
Two modes in one widget. Activation models converting more inbound leads (leads × conversion lift × deal value vs plan cost). Reactivation models re-engaging the aged database (dbSize × conversion uplift vs outreach + worked-lead pricing). Ink surface + radial pink glow in the output panel; hero number gets a green halo, ROI gets a pink halo.
Plan-aware pricing (Core $1K / Scale $4K / Enterprise $15K) picks the cheapest tier automatically based on lead volume. Slider values are double-click-to-edit for precision. All maths runs client-side via inputmode="numeric" fields; no server round-trip.
Revenue calculator
Plug in your numbers. See the opportunity. This isn’t a marketing exercise — it’s the math your CFO would run.
Your numbers
New rate: 4%
Your results
Current monthly revenue
—
Projected revenue at 4% conversion
—
Additional revenue per month
—
MagicBlocks cost
—
Return on investment
—
Payback period
—
Your database
Typical for aged leads without re-engagement
AI-driven reactivation typically achieves 3–8%
Your results
Current revenue at 1% conversion
—
Revenue with MagicBlocks at 2%
—
Additional revenue unlocked
—
MagicBlocks cost
—
Return on investment
—
Cost per reactivated deal
—
<div class="roi-calc" data-roi-calc>
<p class="roi-calc-eyebrow">Revenue calculator</p>
<h2 class="roi-calc-title" data-roi-title>How much revenue could MagicBlocks <em>unlock</em>?</h2>
<p class="roi-calc-subtitle" data-roi-subtitle>Plug in your numbers. See the opportunity. This isn't a marketing exercise — it's the math your CFO would run.</p>
<div class="roi-tabs" role="tablist">
<button class="roi-tab-btn is-active" type="button" role="tab" aria-selected="true" data-roi-tab="activation"><span class="ic">⚡</span>Lead activation<span class="sub">Convert more inbound</span></button>
<button class="roi-tab-btn" type="button" role="tab" aria-selected="false" data-roi-tab="reactivation"><span class="ic">↺</span>Lead reactivation<span class="sub">Re-engage aged leads</span></button>
</div>
<div class="roi-card">
<!-- ACTIVATION PANEL -->
<div class="roi-panel is-active" data-roi-panel="activation">
<div class="roi-inputs">
<p class="roi-inputs-label">Your numbers</p>
<div class="roi-field">
<label for="roi-a-leads">Monthly lead volume</label>
<div class="input-wrap"><input type="text" id="roi-a-leads" value="250" inputmode="numeric" data-roi-input="activation"></div>
</div>
<div class="roi-field">
<label for="roi-a-cpl">Average cost per lead</label>
<div class="input-wrap"><span class="roi-prefix">$</span><input type="text" id="roi-a-cpl" class="has-prefix" value="50" inputmode="numeric" data-roi-input="activation"></div>
</div>
<div class="roi-field">
<label for="roi-a-conv">Current conversion rate</label>
<div class="roi-slider-row">
<input type="range" id="roi-a-conv" min="0.5" max="15" step="0.5" value="3" data-roi-input="activation">
<span class="roi-slider-val" data-roi-val="roi-a-conv" data-roi-suffix="%">3%</span>
</div>
</div>
<div class="roi-field">
<label for="roi-a-lift">Expected conversion rate lift</label>
<div class="roi-slider-row">
<input type="range" id="roi-a-lift" min="0.5" max="10" step="0.5" value="1" data-roi-input="activation">
<span class="roi-slider-val" data-roi-val="roi-a-lift" data-roi-prefix="+" data-roi-suffix="%">+1%</span>
</div>
<p class="roi-field-hint" data-roi-lift-hint>New rate: <strong>4%</strong></p>
</div>
<div class="roi-field">
<label for="roi-a-deal">Average deal value</label>
<div class="input-wrap"><span class="roi-prefix">$</span><input type="text" id="roi-a-deal" class="has-prefix" value="3,000" inputmode="numeric" data-roi-input="activation"></div>
</div>
<div class="roi-plan-note" data-roi-plan-note="activation"></div>
</div>
<div class="roi-outputs">
<p class="roi-outputs-label">Your results</p>
<div class="roi-result"><p class="roi-result-label">Current monthly revenue</p><p class="roi-result-value" data-roi-out="activation.current">—</p></div>
<div class="roi-result"><p class="roi-result-label" data-roi-out-label="activation.projected">Projected revenue at 4% conversion</p><p class="roi-result-value" data-roi-out="activation.projected">—</p></div>
<div class="roi-result is-hero"><p class="roi-result-label">Additional revenue per month</p><p class="roi-result-value" data-roi-out="activation.additional">—</p></div>
<div class="roi-result is-cost"><p class="roi-result-label">MagicBlocks cost</p><p class="roi-result-value" data-roi-out="activation.cost">—</p></div>
<div class="roi-result is-roi"><p class="roi-result-label">Return on investment</p><p class="roi-result-value" data-roi-out="activation.roi">—</p></div>
<div class="roi-result is-payback"><p class="roi-result-label">Payback period</p><p class="roi-result-value" data-roi-out="activation.payback">—</p></div>
</div>
</div>
<!-- REACTIVATION PANEL -->
<div class="roi-panel" data-roi-panel="reactivation">
<div class="roi-inputs">
<p class="roi-inputs-label">Your database</p>
<div class="roi-field">
<label for="roi-r-dbsize">Aged leads in your database</label>
<div class="input-wrap"><input type="text" id="roi-r-dbsize" value="5,000" inputmode="numeric" data-roi-input="reactivation"></div>
</div>
<div class="roi-field">
<label for="roi-r-conv-current">Current conversion rate</label>
<div class="roi-slider-row">
<input type="range" id="roi-r-conv-current" min="0.5" max="10" step="0.5" value="1" data-roi-input="reactivation">
<span class="roi-slider-val" data-roi-val="roi-r-conv-current" data-roi-suffix="%">1%</span>
</div>
</div>
<div class="roi-field">
<label for="roi-r-conv-new">Conversion rate with MagicBlocks</label>
<div class="roi-slider-row">
<input type="range" id="roi-r-conv-new" min="0.5" max="15" step="0.5" value="2" data-roi-input="reactivation">
<span class="roi-slider-val" data-roi-val="roi-r-conv-new" data-roi-suffix="%">2%</span>
</div>
</div>
<div class="roi-field">
<label for="roi-r-deal">Average deal value</label>
<div class="input-wrap"><span class="roi-prefix">$</span><input type="text" id="roi-r-deal" class="has-prefix" value="3,000" inputmode="numeric" data-roi-input="reactivation"></div>
</div>
<div class="roi-plan-note" data-roi-plan-note="reactivation"></div>
</div>
<div class="roi-outputs">
<p class="roi-outputs-label">Your results</p>
<div class="roi-result"><p class="roi-result-label" data-roi-out-label="reactivation.current">Current revenue at 1% conversion</p><p class="roi-result-value" data-roi-out="reactivation.current">—</p></div>
<div class="roi-result"><p class="roi-result-label" data-roi-out-label="reactivation.projected">Revenue with MagicBlocks at 2%</p><p class="roi-result-value" data-roi-out="reactivation.projected">—</p></div>
<div class="roi-result is-hero"><p class="roi-result-label">Additional revenue unlocked</p><p class="roi-result-value" data-roi-out="reactivation.additional">—</p></div>
<div class="roi-result is-cost"><p class="roi-result-label">MagicBlocks cost</p><p class="roi-result-value" data-roi-out="reactivation.cost">—</p></div>
<div class="roi-result is-roi"><p class="roi-result-label">Return on investment</p><p class="roi-result-value" data-roi-out="reactivation.roi">—</p></div>
<div class="roi-result is-payback"><p class="roi-result-label">Cost per reactivated deal</p><p class="roi-result-value" data-roi-out="reactivation.cpa">—</p></div>
</div>
</div>
</div>
</div>
.roi-card { background: var(--ink); border-radius: var(--r-lg); box-shadow: 0 30px 60px -24px color-mix(in oklab, var(--ink) 60%, transparent); }
.roi-panel.is-active { display: grid; grid-template-columns: 1fr 1fr; }
/* Output column: darker + pink radial glow for pop */
.roi-outputs {
background:
radial-gradient(80% 65% at 92% -10%, color-mix(in oklab, var(--accent) 14%, transparent), transparent 70%),
color-mix(in oklab, var(--ink) 88%, #000);
}
/* Hero revenue gets a green glow, ROI gets a pink glow */
.roi-result.is-hero .roi-result-value { color: var(--green-500); text-shadow: 0 0 32px color-mix(in oklab, var(--green-500) 38%, transparent); }
.roi-result.is-roi .roi-result-value { color: var(--accent-text); text-shadow: 0 0 28px color-mix(in oklab, var(--accent) 34%, transparent); }
/* Mode-prop documentation table — defaults per page/industry. */
.roi-mode-table {
width: 100%; border-collapse: collapse;
font: 400 13px/1.4 var(--f-body);
background: var(--bg-paper);
border: 1px solid var(--hair);
border-radius: var(--r-md);
overflow: hidden;
}
.roi-mode-table thead {
background: var(--bg-sunk);
font: 600 10.5px/1 var(--f-mono);
text-transform: uppercase; letter-spacing: 0.08em;
color: var(--fg-soft);
}
.roi-mode-table th, .roi-mode-table td {
padding: 10px 12px;
text-align: left;
vertical-align: top;
border-bottom: 1px solid var(--hair);
}
.roi-mode-table tbody th {
font: 500 12px/1.3 var(--f-mono);
color: var(--accent-text);
white-space: nowrap;
}
.roi-mode-table tbody td:nth-of-type(2) {
font-size: 12.5px;
font-variant-numeric: tabular-nums;
color: var(--fg-soft);
}
.roi-mode-table tbody td:last-child {
font: 500 12.5px/1.3 var(--f-body);
color: var(--fg);
}
.roi-mode-table tbody tr:last-child th,
.roi-mode-table tbody tr:last-child td { border-bottom: 0; }
.roi-mode-table tbody tr:hover { background: color-mix(in oklab, var(--accent-soft) 60%, transparent); }
@media (max-width: 720px) {
.roi-mode-table thead { display: none; }
.roi-mode-table tr { display: grid; grid-template-columns: 1fr; gap: 4px; padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--hair); }
.roi-mode-table th, .roi-mode-table td { border: 0; padding: 0; }
.roi-mode-table tbody th { color: var(--accent-text); }
}
@media (max-width: 720px) { .roi-panel.is-active { grid-template-columns: 1fr; } }
import { RevenueCalculator } from "@magicblocksai/ui";
// Default — homepage / pricing
<RevenueCalculator />
// With mode preset — pre-fills industry defaults
<RevenueCalculator mode="industry:mortgage" />
<RevenueCalculator mode="industry:solar" initialTab="reactivation" />
// All maths runs client-side; plan-aware pricing picks Core/Scale/Enterprise.
Same widget. Same maths. The data-mode prop only swaps the default input values and the output framing label per destination page — it does not change the calculation. Use the table below as the source of truth for what each mode pre-populates. The website pre-fills inputs on render; users can still edit any field.
| data-mode | Pages | Defaults (leads · CPL · conv · deal) | Output label |
|---|---|---|---|
| neutral | Homepage §14, Pricing §7 | 1,200 · $45 · 4.5% · $2,400 | Lost revenue per month |
| engage | Engage New Leads §10 | 1,200 · $45 · 4.5% · $2,400 | Revenue leaking per month |
| prequalify | Prequalify Leads §10 | 8 reps · 20 calls · 35% qualified · $8,400 | Rep hours/week wasted on bad-fit |
| followup | Follow Up Leads §11 | 1,200 · 2.3 touches · 18% completion · $2,400 | Deals lost to silence per month |
| reengage | Reengage Aged Leads §12 | 25,000 dormant · $45 · 8% reactivation · $2,400 | Sunk acquisition cost + Revenue recoverable |
| industry:mortgage | Mortgage industry hub, Beeline case | 1,200 · $45 · 4.5% · $2,400 | Revenue leaking per month |
| industry:insurance | Insurance industry hub | 800 · $35 · 6% · $1,800 | Commission leaking per month |
| industry:auto | Auto industry hub | 2,000 · $25 · 8% · $1,200 | Gross leaking per month |
| industry:home-services | Home Services industry hub | 1,500 · $40 · 5% · $3,500 | Revenue leaking per month |
| industry:solar | Solar industry hub | 600 · $80 · 3% · $9,500 | Commission leaking per month |
| industry:fintech | Fintech industry hub | 2,500 · $30 · 5% · $800 | Revenue leaking per month |
| industry:tourism | Tourism industry hub, Waterbom case | 5,000 · $12 · 6% · $480 | Booking revenue leaking per month |
<!-- pick the mode for the destination page; rest of markup is identical -->
<div class="roi-calc" data-roi-calc data-mode="industry:solar">
<p class="roi-calc-eyebrow">Revenue calculator</p>
<h2 class="roi-calc-title" data-roi-title>How much revenue could MagicBlocks <em>unlock</em>?</h2>
<p class="roi-calc-subtitle" data-roi-subtitle>Plug in your numbers. See the opportunity. This isn't a marketing exercise — it's the math your CFO would run.</p>
<div class="roi-tabs" role="tablist">
<button class="roi-tab-btn is-active" type="button" role="tab" aria-selected="true" data-roi-tab="activation"><span class="ic">⚡</span>Lead activation<span class="sub">Convert more inbound</span></button>
<button class="roi-tab-btn" type="button" role="tab" aria-selected="false" data-roi-tab="reactivation"><span class="ic">↺</span>Lead reactivation<span class="sub">Re-engage aged leads</span></button>
</div>
<div class="roi-card">
<!-- Activation + reactivation panels — same .roi-inputs / .roi-outputs structure as the default;
only the seeded values from MB_ROI_MODES["industry:solar"] differ. -->
</div>
</div>
.roi-calc {
width: 100%;
max-width: min(960px, 100%);
margin: 0 auto;
}
.roi-calc :where(p, h2, h3) { margin: 0; }
.roi-calc-eyebrow {
font: 600 11px/1 var(--f-mono);
text-transform: uppercase; letter-spacing: 0.16em;
color: var(--fg-dim);
text-align: center;
margin-bottom: var(--s-2);
}
.roi-calc-title {
font: 700 clamp(22px, 3.2vw, 32px)/1.2 var(--f-display);
letter-spacing: -0.02em;
color: var(--fg);
text-align: center;
margin: 0 0 var(--s-2);
text-wrap: balance;
}
.roi-calc-title em {
font-family: var(--f-italic);
font-style: italic; font-weight: 500;
color: var(--accent-text);
font-variation-settings: "SOFT" 80;
}
.roi-calc-subtitle {
font: 400 15px/1.55 var(--f-body);
color: var(--fg-soft);
text-align: center;
max-width: 560px;
margin: 0 auto var(--s-6);
}
// Mode → defaults map. Calculation logic in the existing JS is
// unchanged; this only seeds the inputs on render.
window.MB_ROI_MODES = {
"neutral": { leads: 1200, cpl: 45, conv: 4.5, deal: 2400, outputLabel: "Lost revenue per month" },
"engage": { leads: 1200, cpl: 45, conv: 4.5, deal: 2400, outputLabel: "Revenue leaking per month" },
"prequalify": { reps: 8, callsPerRep: 20, qualifiedRate: 35, deal: 8400, outputLabel: "Rep hours/week wasted on bad-fit" },
"followup": { leads: 1200, touches: 2.3, completion: 18, deal: 2400, outputLabel: "Deals lost to silence per month" },
"reengage": { dormant: 25000, originalCpl: 45, reactivation: 8, deal: 2400, outputLabel: "Sunk acquisition cost + Revenue recoverable" },
"industry:mortgage": { leads: 1200, cpl: 45, conv: 4.5, deal: 2400, outputLabel: "Revenue leaking per month" },
"industry:insurance": { leads: 800, cpl: 35, conv: 6, deal: 1800, outputLabel: "Commission leaking per month" },
"industry:auto": { leads: 2000, cpl: 25, conv: 8, deal: 1200, outputLabel: "Gross leaking per month" },
"industry:home-services": { leads: 1500, cpl: 40, conv: 5, deal: 3500, outputLabel: "Revenue leaking per month" },
"industry:solar": { leads: 600, cpl: 80, conv: 3, deal: 9500, outputLabel: "Commission leaking per month" },
"industry:fintech": { leads: 2500, cpl: 30, conv: 5, deal: 800, outputLabel: "Revenue leaking per month" },
"industry:tourism": { leads: 5000, cpl: 12, conv: 6, deal: 480, outputLabel: "Booking revenue leaking per month" }
};
// Wiring (existing init): on mount, look up the mode and seed the inputs.
// All four canonical formulas (neutral / engage / followup / industry:*),
// the prequalify hours formula, and the reengage recovery formula are
// already implemented in the calculator's existing IIFE — modes only
// pick which input set + output label to show.
import { RevenueCalculator } from "@magicblocksai/ui";
// `mode` seeds different defaults per destination page:
// "neutral" · "engage" · "prequalify" · "followup" · "reengage"
// "industry:mortgage" · "industry:insurance" · "industry:auto"
// "industry:home-services" · "industry:solar" · "industry:fintech"
// "industry:tourism"
export default function Demo() {
return <RevenueCalculator mode="industry:solar" />;
}
Horizontal milestone ticks. Used for the About page origin story ($200M → AI mortgage team → MagicBlocks) and for industry-specific multi-year timelines (insurance renewal cadence, etc.).
2016 – 2022
$200M+ in leads
Six years running lead-driven businesses across mortgage, solar, insurance and home services.
2023
World’s first AI mortgage team
Built the first live AI sales team in mortgage — agents that engage, qualify and book inside regulated conversation.
2026
MagicBlocks
The conversion engine, productised. Same methodology, every vertical, deployed in days.
<div class="brand-timeline reveal" style="--bt-n: 3;">
<div class="bt-ticks">
<div class="bt-tick">
<p class="bt-date">2016 – 2022</p>
<span class="bt-dot"></span>
<p class="bt-title">$200M+ in leads</p>
<p class="bt-sub">Six years running lead-driven businesses…</p>
</div>
<!-- two more milestones -->
</div>
</div>
.brand-timeline {
position: relative;
max-width: 900px;
padding: 20px 0 20px;
}
.brand-timeline .bt-ticks {
display: grid;
grid-template-columns: repeat(var(--bt-n, 3), 1fr);
gap: var(--s-3);
position: relative;
}
.brand-timeline .bt-ticks::before {
content: "";
position: absolute;
left: 8%; right: 8%;
top: 42px; /* aligns with the centre of each dot */
height: 2px;
background: linear-gradient(90deg, var(--hair), var(--accent), var(--hair));
border-radius: 2px;
z-index: 0;
}
.brand-timeline .bt-tick {
text-align: center;
display: flex; flex-direction: column; align-items: center;
position: relative;
padding: 0 var(--s-3);
z-index: 1;
}
.brand-timeline .bt-date {
font: 500 11px/1 var(--f-mono); color: var(--fg-faint);
letter-spacing: 0.12em; text-transform: uppercase;
margin: 0 0 10px;
height: 16px; /* lock date row height → dot alignment stays consistent */
display: flex; align-items: center;
}
.brand-timeline .bt-dot {
width: 14px; height: 14px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 4px var(--bg-paper);
flex: 0 0 auto;
margin-bottom: 18px;
position: relative;
}
.brand-timeline .bt-tick:last-child .bt-dot::after {
content: ""; position: absolute; inset: -3px; border-radius: 50%;
border: 2px solid var(--accent);
opacity: 0;
animation: bt-ping 2.4s ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.brand-timeline .bt-tick:last-child .bt-dot::after { animation: none; opacity: 0; }
}
.brand-timeline .bt-title {
font: 600 16px/1.25 var(--f-display); color: var(--fg); margin: 0;
max-width: 24ch;
}
.brand-timeline .bt-title em { font-family: var(--f-serif); font-style: italic; font-weight: 400; color: var(--accent); }
.brand-timeline .bt-sub {
font: 400 12.5px/1.45 var(--f-body); color: var(--fg-soft);
margin: 6px 0 0; max-width: 30ch;
}
/* …additional rules trimmed for brevity — see _shared.css */
import { BrandTimeline } from "@magicblocksai/ui";
<BrandTimeline
entries={[
{ id: "2016-22", date: "2016 – 2022", title: "$200M+ in leads",
sub: "Six years running lead-driven businesses across mortgage, solar, insurance and home services." },
{ id: "2023", date: "2023", title: <>World's first AI <em>mortgage team</em></>,
sub: "Built the first live AI sales team in mortgage." },
{ id: "2026", date: "2026", title: "MagicBlocks",
sub: "The conversion engine, productised." },
]}
/>
A large ambient SVG backdrop for hero sections. Three soft radial gradients, slow drift, optional drifting motes. Variants: warm · war-room · industry (driven by --industry-accent).
<!-- 1. Warm variant (default · pink/blue glows + drifting motes) -->
<div class="hero-bloom-canvas" data-variant="warm">
<div class="hbc-content">
<div class="kicker">Warm variant</div>
<h4>Every lead. <em>Every time.</em></h4>
</div>
<div class="motes"><span class="mote"></span></div>
</div>
<!-- 2. War-room variant (dark surface, paper-on-ink content) -->
<div class="hero-bloom-canvas" data-variant="war-room">
<div class="hbc-content">
<div class="kicker">War-room variant</div>
<h4>The engine <em>under the hood.</em></h4>
</div>
</div>
<!-- 3. Industry variant — per-page colour wash via --industry-accent -->
<div class="hero-bloom-canvas" data-variant="industry" style="--industry-accent: #47DDB2;">
<div class="hbc-content">
<div class="kicker">Industry variant · Insurance</div>
<h4>Complex qualification. <em>Handled instantly.</em></h4>
</div>
</div>
.hero-bloom-canvas::before {
background:
radial-gradient(60% 60% at 85% 12%, color-mix(in oklab, var(--accent) 28%, transparent), transparent 70%),
radial-gradient(40% 50% at 12% 80%, color-mix(in oklab, var(--blue-500) 18%, transparent), transparent 70%);
animation: hb-drift 40s ease-in-out infinite alternate;
}
.hero-bloom-canvas[data-variant="industry"] { background: color-mix(in oklab, var(--industry-accent) 6%, var(--warm-3)); }
import { HeroBloomCanvas } from "@magicblocksai/ui";
// Warm variant
<HeroBloomCanvas
variant="warm"
kicker="Warm variant"
heading={<>Every lead. <em>Every time.</em></>}
motes
/>
// War-room variant
<HeroBloomCanvas variant="war-room" kicker="War-room" heading={<>The engine <em>under the hood.</em></>} />
// Industry variant — pass --industry-accent via style
<HeroBloomCanvas
variant="industry"
kicker="Industry · Insurance"
heading={<>Complex qualification. <em>Handled instantly.</em></>}
style={{ "--industry-accent": "#47DDB2" } as React.CSSProperties}
/>
The homepage above-the-fold flagship: a laptop frame running a looping mortgage-scenario conversation. Response-timer overlay proves the speed claim as you watch; the status badge cycles Inbound → Engaging → Qualified → Booked. Rate figures use live April-2026 benchmarks (6.12% vs 6.83% a year ago — a real ~70bps delta). Industry-swappable via data-scenario.
Starts on load and loops continuously every 20s. Timer ticks in 100ms increments until the agent's first reply (~4.2s) then freezes and pinkifies. Status chip's final state pulses. Play / Pause / Restart controls are available for viewers who want to drive it directly.
<div class="hero-live-demo" data-hero-live-demo data-scenario="mortgage">
<div class="hld-frame">
<div class="hld-screen">
<div class="hld-tabs" aria-hidden="true">
<div class="hld-dots"><span></span><span></span><span></span></div>
<div class="hld-url">magicblocks.com / agent / live</div>
</div>
<div class="hld-app">
<div class="hld-timer" data-hld-timer aria-live="off">
<div>
<div class="hld-timer-label">Response time</div>
<div class="hld-timer-value" data-hld-timer-value>0.0s</div>
</div>
</div>
<div class="hld-status" data-hld-status aria-live="polite">
<span class="hld-status-dot"></span>
<span data-hld-status-label>—</span>
</div>
<div class="hld-thread" data-hld-thread>
<div class="hld-msg in" data-hld-step="m1">Hey, what rates are you guys seeing right now?</div>
<div class="hld-msg out" data-hld-step="m2">30-year fixed is at <em>6.12%</em> this morning — down from <em>6.83%</em> a year ago. If you locked in 12 months ago, you're probably ~70bps high. Want me to pull what a refi would save you?</div>
<div class="hld-msg in" data-hld-step="m3">Sure</div>
<div class="hld-msg out" data-hld-step="m4">I just need two things — current balance and rate. Or I can pull from the CRM: are you <strong>Mike Chen</strong> at [email protected]?</div>
<div class="hld-msg in" data-hld-step="m5">yeah that's me</div>
<div class="hld-msg out" data-hld-step="m6">Got it — <em>$582K</em> at 6.83%. At today's rate you'd save ~<strong>$271/mo</strong> (~$3,250/yr). Want a 15-min rate review?</div>
<div class="hld-msg in" data-hld-step="m7">sounds good</div>
<div class="hld-msg out" data-hld-step="m8">Two slots: <em>Thu 10:30</em> or <em>Thu 2:45</em>. ✓ Booked either way — pick one.</div>
<div class="hld-typing" data-hld-typing hidden>
<span></span><span></span><span></span>
</div>
</div>
</div>
</div>
</div>
<div class="hld-controls">
<div>
<button type="button" class="hld-btn" data-hld-action="restart">↻ Restart</button>
<button type="button" class="hld-btn" data-hld-action="pause">⏸ Pause</button>
<button type="button" class="hld-btn" data-hld-action="play">▶ Play</button>
</div>
<div class="hld-scenario">Scenario · Mortgage</div>
</div>
</div>
/* Laptop frame, continuous-loop. Status badge's state-colour comes
from semantic tokens (inbound=info, engaging=warning, qualified=
green-500, booked=accent). Timer freezes + pinkifies on first reply. */
.hero-live-demo { max-width: min(900px, 100%); margin: 0 auto; position: relative; }
.hld-frame { padding: 14px 14px 0; background: color-mix(in oklab, var(--ink) 95%, #999); border-radius: 18px 18px 4px 4px; }
.hld-screen { aspect-ratio: 16/10; overflow: hidden; position: relative; }
.hld-timer { position: absolute; top: var(--s-4); right: var(--s-4); background: rgba(25,30,50,.82); color: var(--paper); }
.hld-timer.is-frozen { background: var(--accent); }
.hld-status { position: absolute; bottom: var(--s-4); left: var(--s-4); }
.hld-status[data-state="qualified"] .hld-status-dot { background: var(--green-500); animation: hld-pulse 1s ease-out 1; }
.hld-status[data-state="booked"] .hld-status-dot { background: var(--accent); animation: hld-pulse 1.4s ease-out 1; }
@media (prefers-reduced-motion: reduce) {
.hld-msg, .hld-timer, .hld-status { opacity: 1 !important; }
.hld-typing { display: none !important; }
}
import { HeroLiveDemo } from "@magicblocksai/ui";
<HeroLiveDemo
scenario="mortgage"
url="magicblocks.com / agent / live"
messages={[
{ step: "m1", from: "in", text: "Hey, what rates are you guys seeing right now?" },
{ step: "m2", from: "out", text: <>30-year fixed is at <em>6.12%</em> this morning…</> },
// …m3–m8
]}
timerValue="0.0s"
statusLabel="Inbound"
statusState="inbound"
/>
// With mode prop — overlay-emphasis variant for a destination page:
<HeroLiveDemo mode="speed-forward" scenario="mortgage" messages={…} />
<HeroLiveDemo mode="qualification-forward" scenario="insurance" messages={…} />
Same conversation engine, four overlay-emphasis modes for the four leak families. The data-mode prop chooses which overlay set is foregrounded; the conversation is supplied via JSON (see data contract below). Each mode pairs with its destination pages: speed-forward → Engage / Mortgage / Auto / Home Services / SMS · qualification-forward → Prequalify / Insurance / Solar · cadence-forward → Follow Up / Tourism · reactivation-forward → Reengage Aged.
<!-- One mode per destination page. Each mode foregrounds a different overlay
(timer · qualification score · cadence track · reactivation row).
The shared chrome (frame · tabs · status · thread) is identical. -->
<!-- 01 · speed-forward — Engage / Mortgage / Auto / Home Services / SMS -->
<div class="hero-live-demo" data-hero-live-demo
data-mode="speed-forward"
data-scenario="mortgage"
data-script="engage-mortgage-v1">
<div class="hld-frame"><div class="hld-screen">
<div class="hld-tabs" aria-hidden="true">
<div class="hld-dots"><span></span><span></span><span></span></div>
<div class="hld-url">magicblocks.com / speed</div>
</div>
<div class="hld-app">
<div class="hld-timer is-frozen">
<div class="hld-timer-label">First reply</div>
<div class="hld-timer-value">4.2s</div>
</div>
<div class="hld-status" data-state="booked"><span class="hld-status-dot"></span>Booked</div>
<div class="hld-thread" data-hld-thread>
<div class="hld-msg in">A/C just died. House is 92°. Two kids. Need someone today.</div>
<div class="hld-msg out">North Dallas + 12-year system + click-then-silence usually points to a failed capacitor. <em>12:30–2:30 PM</em> with Aaron Ramirez — diagnostic $89, applied to repair. ✓</div>
</div>
</div>
</div></div>
</div>
<!-- 02 · qualification-forward — Prequalify / Insurance / Solar -->
<div class="hero-live-demo" data-hero-live-demo
data-mode="qualification-forward"
data-scenario="insurance"
data-script="qualify-insurance-v1">
<div class="hld-frame"><div class="hld-screen">
<div class="hld-tabs" aria-hidden="true">
<div class="hld-dots"><span></span><span></span><span></span></div>
<div class="hld-url">magicblocks.com / qualify</div>
</div>
<div class="hld-app">
<div class="hld-qual-score" style="--qs-pct: 86%;">
<div class="qs-label">Qualification score</div>
<div class="qs-row"><div class="qs-num">86</div><div class="qs-bar"><span></span></div></div>
</div>
<div class="hld-thread" data-hld-thread>
<div class="hld-msg in">Auto + home, current with Allstate, renewal in 23 days.</div>
<div class="hld-msg out">Quick three: any tickets in the last 36 mo? Garaged or street? Any teen drivers on the policy?</div>
</div>
</div>
</div></div>
</div>
<!-- 03 · cadence-forward — Follow Up / Tourism -->
<div class="hero-live-demo" data-hero-live-demo
data-mode="cadence-forward"
data-scenario="mortgage"
data-script="follow-up-mortgage-v1">
<div class="hld-frame"><div class="hld-screen">
<div class="hld-tabs" aria-hidden="true">
<div class="hld-dots"><span></span><span></span><span></span></div>
<div class="hld-url">magicblocks.com / cadence</div>
</div>
<div class="hld-app">
<div class="hld-thread" data-hld-thread>
<div class="hld-msg out">Hey Mike — circling back on the rate review. You mentioned closing in 45d; we're now <em>22 days out</em>. Slot tomorrow 2pm?</div>
<div class="hld-msg in">Yeah let's do tomorrow.</div>
</div>
<div class="hld-cadence" style="--cd-progress: 65%;">
<span class="cd-label">Touch 5/7</span>
<div class="cd-track">
<span class="cd-touch is-fired" style="left: 5%;"></span>
<span class="cd-touch is-fired" style="left: 20%;"></span>
<span class="cd-touch is-fired" style="left: 35%;"></span>
<span class="cd-touch is-fired" style="left: 50%;"></span>
<span class="cd-touch is-current" style="left: 65%;"></span>
<span class="cd-touch" style="left: 80%;"></span>
<span class="cd-touch" style="left: 95%;"></span>
</div>
<span class="cd-label">Day 22 / 30</span>
</div>
</div>
</div></div>
</div>
<!-- 04 · reactivation-forward — Reengage Aged -->
<div class="hero-live-demo" data-hero-live-demo
data-mode="reactivation-forward"
data-scenario="mortgage"
data-script="reengage-mortgage-v1">
<div class="hld-frame"><div class="hld-screen">
<div class="hld-tabs" aria-hidden="true">
<div class="hld-dots"><span></span><span></span><span></span></div>
<div class="hld-url">magicblocks.com / reactivate</div>
</div>
<div class="hld-app">
<div class="hld-reactivation">
<div class="rx-row is-dormant"><span class="rx-dot"></span>Last contact · 147 days ago</div>
<div class="rx-row is-active"><span class="rx-dot"></span>Re-engaged today</div>
</div>
<div class="hld-thread" data-hld-thread>
<div class="hld-msg out">Hey Sarah — saw rates dropped 60bps since we last spoke back in November. On your old quote, that's about <em>$340/mo</em> back. Want a fresh look?</div>
<div class="hld-msg in">Wow yeah send me what you've got.</div>
</div>
</div>
</div></div>
</div>
.hero-live-demo {
width: 100%;
max-width: min(900px, 100%);
margin: 0 auto;
position: relative;
}
.hero-live-demo[data-mode="speed-forward"] .hld-timer {
padding: 12px 18px;
font-size: 13px;
}
.hero-live-demo[data-mode="speed-forward"] .hld-timer-value {
font-size: 22px; font-weight: 700;
}
.hero-live-demo[data-mode="speed-forward"] .hld-timer.is-frozen {
box-shadow: 0 8px 28px -8px color-mix(in oklab, var(--accent) 70%, transparent),
0 0 0 4px color-mix(in oklab, var(--accent) 18%, transparent);
}
.hero-live-demo[data-mode="speed-forward"] .hld-status { opacity: 0.78; transform: scale(0.92); transform-origin: bottom left; }
.hero-live-demo[data-mode="speed-forward"] .hld-status.is-visible { opacity: 0.78; transform: scale(0.92); }
.hero-live-demo[data-mode="qualification-forward"] .hld-status { display: none; }
.hero-live-demo[data-mode="cadence-forward"] .hld-status { display: none; }
.hero-live-demo[data-mode="reactivation-forward"] .hld-status { display: none; }
// JSON data contract — register scripts before the page hydrates.
// The component reads window.MB_HLD_SCRIPTS[scriptKey] on load.
window.MB_HLD_SCRIPTS = {
"engage-mortgage-v1": {
"industry": "mortgage",
"page": "engage",
"leadContext": "Mortgage refi inquiry, credit mid-700s.",
"messages": [
{ "role": "lead", "timestamp": "9:47:04 AM", "text": "Hey — saw your rate quote page…", "delay": 200, "typingDuration": 800 },
{ "role": "engine", "timestamp": "9:47:08 AM", "text": "Hey! Welcome…", "delay": 100, "typingDuration": 1200 }
],
"qualificationCheckpoints": ["balance", "value", "timeline"],
"finalStatus": "Booked",
"disclosureCaption": "Illustrative conversation. Rates and availability vary…"
}
};
// Token rules
// • [range] placeholders resolve at render from per-industry rate tables
// • Timestamps render in the same wall-clock format across messages
// • One green dot 🟢 at the end of the engine's last message only
// • Disclosure caption renders in small type beneath the frame
import { HeroLiveDemo } from "@magicblocksai/ui";
// `mode` controls which overlay set is foregrounded:
// "speed-forward" · "qualification-forward" · "cadence-forward" · "reactivation-forward"
// `scenario` swaps the industry copy + status badge.
// 01 · speed-forward — Engage / Mortgage / Auto / Home Services / SMS
<HeroLiveDemo
mode="speed-forward"
scenario="mortgage"
timerValue="4.2s"
timerFrozen
statusLabel="Booked"
statusState="booked"
messages={[
{ step: "m1", from: "in", text: "A/C just died. House is 92°. Two kids. Need someone today." },
{ step: "m2", from: "out", text: "North Dallas + 12-year system points to a failed capacitor. 12:30–2:30 PM with Aaron Ramirez. ✓" },
]}
/>
// 02 · qualification-forward — Prequalify / Insurance / Solar
<HeroLiveDemo
mode="qualification-forward"
scenario="insurance"
qualScore={86}
messages={[
{ step: "m1", from: "in", text: "Commercial property, 6 locations AZ + NV, $8M revenue, prior kitchen fire 2022." },
{ step: "m2", from: "out", text: "Strong profile — fits our commercial broker bench. Marcus Ruiz Thu 10:30?" },
]}
/>
// 03 · cadence-forward — Follow Up / Tourism
<HeroLiveDemo
mode="cadence-forward"
scenario="mortgage"
cadence={{ touch: "5/7", progress: "65%", day: "22 / 30" }}
messages={[
{ step: "m1", from: "out", text: "Hey Mike — circling back on the rate review. Slot tomorrow 2pm?" },
{ step: "m2", from: "in", text: "Yeah let's do tomorrow." },
]}
/>
// 04 · reactivation-forward — Reengage Aged
<HeroLiveDemo
mode="reactivation-forward"
scenario="mortgage"
reactivation={{ dormantDays: 147, reEngagedToday: true }}
messages={[
{ step: "m1", from: "out", text: "Hey Sarah — rates dropped 60bps since November. About $340/mo back." },
{ step: "m2", from: "in", text: "Wow yeah send me what you've got." },
]}
/>
Four paired tiles naming each leak in the funnel. Every stat is sourced research, not invented: Engage → Hatch 2024, Prequalify → BANT consensus, Follow Up → Marketing Donut, Reengage → CRM reactivation research. Each card is a single click target; the sparkline re-draws on hover. 2×2 grid desktop, stacked mobile.
Recombines existing atoms (.stat-badge-like pill, .spark-style mini-chart, card surface) with leak-specific accent via the --lk-accent CSS custom property — one base class, four variants, zero rule duplication.
88% of inbound leads don't get a sub-five-minute reply. Five-minute responders are 100× more likely to make contact and 21× more likely to qualify. Hatch 2024 · MIT / HBR
Fix Leak #1 → 67%67% of reps burn hours on leads that were never going to close. Reps spend just 34% of their time actually selling — the rest is admin plus chasing bad-fit prospects. BANT research · S2W Media · Salesforce
Fix Leak #2 → 44%44% of salespeople give up after one attempt. Only 8% follow up more than five times — yet 80% of sales require exactly that. Marketing Donut · widely cited 2024–2026
Fix Leak #3 → 84%84% of CRM leads go untouched after 30 days. Reactivating dormant contacts costs 5–10× less than chasing new ones — and converts 3–4× higher. VisQuanta · BoldTrail · Mixed Media Ventures
Fix Leak #4 →<div class="leak-grid">
<!-- 1. Engage new leads (pink) -->
<a class="leak-card" data-leak="1" href="/use-cases/engage-new-leads">
<span class="lk-icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>
</span>
<span class="lk-stat" aria-label="Key statistic: 88 percent">88%</span>
<h3 class="lk-title">Engage new leads</h3>
<p class="lk-body">
<strong>88%</strong> of inbound leads don't get a sub-five-minute reply. Five-minute responders are <strong>100×</strong> more likely to make contact and <strong>21×</strong> more likely to qualify.
<cite>Hatch 2024 · MIT / HBR</cite>
</p>
<span class="lk-spark" aria-hidden="true" style="--lk-len: 180;">
<svg viewBox="0 0 180 36" preserveAspectRatio="none"><path d="M 2 4 C 24 4 32 28 52 32 L 178 32"/></svg>
</span>
<span class="lk-cta">Fix Leak #1 <span class="arrow">→</span></span>
</a>
<!-- 2. Prequalify leads (yellow) -->
<a class="leak-card" data-leak="2" href="/use-cases/prequalify">
<span class="lk-icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M3 5h18L14 14v6l-4-2v-4L3 5z"/></svg>
</span>
<span class="lk-stat" aria-label="Key statistic: 67 percent">67%</span>
<h3 class="lk-title">Prequalify leads</h3>
<p class="lk-body">
<strong>67%</strong> of reps burn hours on leads that were never going to close. Reps spend just <strong>34%</strong> of their time actually selling — the rest is admin plus chasing bad-fit prospects.
<cite>BANT research · S2W Media · Salesforce</cite>
</p>
<span class="lk-spark" aria-hidden="true" style="--lk-len: 200;">
<svg viewBox="0 0 180 36" preserveAspectRatio="none"><path d="M 2 20 L 22 8 L 38 24 L 56 12 L 72 26 L 90 14 L 108 28 L 126 20 L 178 32"/></svg>
</span>
<span class="lk-cta">Fix Leak #2 <span class="arrow">→</span></span>
</a>
<!-- 3. Follow up (green) -->
<a class="leak-card" data-leak="3" href="/use-cases/follow-up">
<span class="lk-icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M4 18c4 0 4-6 8-6s4 6 8 6"/><circle cx="4" cy="18" r="1.6"/><circle cx="20" cy="18" r="1.6"/><circle cx="12" cy="12" r="1.6"/></svg>
</span>
<span class="lk-stat" aria-label="Key statistic: 44 percent">44%</span>
<h3 class="lk-title">Follow up</h3>
<p class="lk-body">
<strong>44%</strong> of salespeople give up after one attempt. Only <strong>8%</strong> follow up more than five times — yet <strong>80%</strong> of sales require exactly that.
<cite>Marketing Donut · widely cited 2024–2026</cite>
</p>
<span class="lk-spark" aria-hidden="true" style="--lk-len: 190;">
<svg viewBox="0 0 180 36" preserveAspectRatio="none">
<path d="M 4 28 L 178 28" opacity="0.18"/>
<path d="M 14 18 L 14 36" stroke-width="2.6"/>
<path d="M 46 24 L 46 32" opacity="0.32"/>
<path d="M 78 24 L 78 32" opacity="0.32"/>
<path d="M 110 24 L 110 32" opacity="0.32"/>
<path d="M 142 24 L 142 32" opacity="0.32"/>
</svg>
</span>
<span class="lk-cta">Fix Leak #3 <span class="arrow">→</span></span>
</a>
<!-- 4. Reengage aged leads (blue) -->
<a class="leak-card" data-leak="4" href="/use-cases/reengage">
<span class="lk-icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M3 7h18v4H3zM5 11v9h14v-9M10 16h4"/></svg>
</span>
<span class="lk-stat" aria-label="Key statistic: 84 percent">84%</span>
<h3 class="lk-title">Reengage aged leads</h3>
<p class="lk-body">
<strong>84%</strong> of CRM leads go untouched after 30 days. Reactivating dormant contacts costs <strong>5–10×</strong> less than chasing new ones — and converts <strong>3–4×</strong> higher.
<cite>VisQuanta · BoldTrail · Mixed Media Ventures</cite>
</p>
<span class="lk-spark" aria-hidden="true" style="--lk-len: 180;">
<svg viewBox="0 0 180 36" preserveAspectRatio="none">
<path d="M 4 30 L 84 30" opacity="0.28"/>
<path d="M 14 26 L 14 34" opacity="0.42"/>
<path d="M 30 26 L 30 34" opacity="0.42"/>
<path d="M 46 26 L 46 34" opacity="0.42"/>
<path d="M 62 26 L 62 34" opacity="0.42"/>
<path d="M 100 30 L 118 14 L 140 22 L 158 10 L 178 8" stroke-width="2.2"/>
</svg>
</span>
<span class="lk-cta">Fix Leak #4 <span class="arrow">→</span></span>
</a>
</div>
/* One base class, four variants. --lk-accent is the vivid tone (rail,
icon, sparkline); --lk-accent-text is its AA-safe partner for the CTA
text. data-leak selects both. */
.leak-card { --lk-accent: var(--accent); --lk-accent-soft: var(--accent-soft); --lk-accent-text: var(--accent-text-strong); }
.leak-card[data-leak="1"] { --lk-accent: var(--pink-500); }
.leak-card[data-leak="2"] { --lk-accent: var(--yellow-500); --lk-accent-text: var(--warning-text); }
.leak-card[data-leak="3"] { --lk-accent: var(--green-500); --lk-accent-text: var(--success-text); }
.leak-card[data-leak="4"] { --lk-accent: var(--blue-500); --lk-accent-text: var(--info-text); }
.leak-card {
display: grid;
grid-template-areas: "icon stat" "title title" "body body" "spark spark" "cta cta";
padding: var(--s-6); background: var(--bg-paper);
border: 1px solid var(--hair); border-top: 3px solid var(--lk-accent);
border-radius: var(--r-lg); box-shadow: var(--sh-1);
transition: transform var(--dur-2), box-shadow var(--dur-2), border-color var(--dur-2);
}
.leak-card:hover { transform: translateY(-4px); box-shadow: var(--sh-3); }
.leak-card .lk-stat { background: var(--lk-accent-soft); color: var(--lk-accent); }
.leak-card .lk-cta { color: var(--lk-accent-text); } /* AA-safe; not the vivid --lk-accent */
.leak-card:hover .lk-spark path { animation: lk-spark-draw 1.4s var(--ease) forwards; }
import { LeakCard } from "@magicblocksai/ui";
// All four leaks rendered from a single source array.
const leaks = [
{
leak: 1,
href: "/use-cases/engage-new-leads",
icon: <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>,
stat: "88%",
statAriaLabel: "Key statistic: 88 percent",
title: "Engage new leads",
body: <><strong>88%</strong> of inbound leads don't get a sub-five-minute reply. <cite>Hatch 2024 · MIT / HBR</cite></>,
sparkline: <svg viewBox="0 0 180 36" preserveAspectRatio="none"><path d="M 2 4 C 24 4 32 28 52 32 L 178 32"/></svg>,
cta: "Fix Leak #1",
},
{
leak: 2,
href: "/use-cases/prequalify",
icon: <svg viewBox="0 0 24 24"><path d="M3 5h18L14 14v6l-4-2v-4L3 5z"/></svg>,
stat: "67%",
statAriaLabel: "Key statistic: 67 percent",
title: "Prequalify leads",
body: <><strong>67%</strong> of reps burn hours on leads that were never going to close. <cite>BANT research · S2W Media · Salesforce</cite></>,
sparkline: <svg viewBox="0 0 180 36" preserveAspectRatio="none"><path d="M 2 20 L 22 8 L 38 24 L 56 12 L 72 26 L 90 14 L 108 28 L 126 20 L 178 32"/></svg>,
cta: "Fix Leak #2",
},
{
leak: 3,
href: "/use-cases/follow-up",
icon: <svg viewBox="0 0 24 24"><path d="M4 18c4 0 4-6 8-6s4 6 8 6"/><circle cx="4" cy="18" r="1.6"/><circle cx="20" cy="18" r="1.6"/><circle cx="12" cy="12" r="1.6"/></svg>,
stat: "44%",
statAriaLabel: "Key statistic: 44 percent",
title: "Follow up",
body: <><strong>44%</strong> of salespeople give up after one attempt — yet <strong>80%</strong> of sales require five+ touches. <cite>Marketing Donut</cite></>,
sparkline: <svg viewBox="0 0 180 36" preserveAspectRatio="none"><path d="M 4 28 L 178 28" opacity="0.18"/><path d="M 14 18 L 14 36" strokeWidth="2.6"/></svg>,
cta: "Fix Leak #3",
},
{
leak: 4,
href: "/use-cases/reengage",
icon: <svg viewBox="0 0 24 24"><path d="M3 7h18v4H3zM5 11v9h14v-9M10 16h4"/></svg>,
stat: "84%",
statAriaLabel: "Key statistic: 84 percent",
title: "Reengage aged leads",
body: <><strong>84%</strong> of CRM leads go untouched after 30 days. Reactivating costs <strong>5–10×</strong> less. <cite>VisQuanta · BoldTrail</cite></>,
sparkline: <svg viewBox="0 0 180 36" preserveAspectRatio="none"><path d="M 4 30 L 84 30" opacity="0.28"/><path d="M 100 30 L 118 14 L 140 22 L 158 10 L 178 8" strokeWidth="2.2"/></svg>,
cta: "Fix Leak #4",
},
];
<div className="leak-grid">
{leaks.map((l) => <LeakCard key={l.leak} {...l} />)}
</div>
Same inbound lead message. Same question. Watched live, side by side: a generic off-the-shelf chatbot on the left (desaturated, dead-ends at "thanks for your interest"), MagicBlocks on the right (pulls real rates, adapts, closes the booking). The asymmetry IS the message. Industry-swappable via data-scenario; Mortgage uses live April-2026 numbers.
Both threads start empty, receive the same lead message, diverge at the first reply. Left stalls. Right qualifies, proposes two slots, books. Loops continuously on page load (3s rest between cycles). Status chip on the right pulses when the final state lands. Industry chip swap resets both columns and replays with the new script.
<div class="chat-comparison" data-chat-comparison data-scenario="mortgage">
<div class="cc-head">
<h4>Same lead. Same question. Watch <em>the difference</em>.</h4>
<div class="cc-toggle" role="radiogroup">
<button data-cc-scenario="mortgage" class="is-active">Mortgage</button>
<!-- insurance / solar / home-services / auto / fintech -->
</div>
</div>
<div class="cc-cols">
<div class="cc-col generic" data-cc-side="generic">
<div class="cc-col-head">…Generic chatbot…</div>
<div class="cc-thread" data-cc-thread><!-- JS-populated --></div>
</div>
<div class="cc-col mb" data-cc-side="mb">
<div class="cc-col-head">…MagicBlocks…</div>
<div class="cc-thread" data-cc-thread><!-- JS-populated --></div>
</div>
</div>
<div class="cc-controls">
<button data-cc-action="restart">Restart</button>
<button data-cc-action="pause">Pause</button>
<button data-cc-action="play">Play</button>
</div>
</div>
/* Left column is desaturated via filter + warm-tinted surface; the
asymmetry with the right column carries the entire message. */
.cc-cols { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-5); }
.cc-col { background: var(--bg-paper); border: 1px solid var(--hair); border-radius: var(--r-lg); }
.cc-col.generic { filter: saturate(0.55); background: color-mix(in oklab, var(--bg-paper) 92%, var(--warm-3)); }
/* MagicBlocks (right) column reads as the calm green alternative,
not pink — pink-on-pink against a competitor felt aggressive/red. */
.cc-col.mb .cc-msg.out { background: var(--green-500); color: var(--paper); }
.cc-msg.is-visible { opacity: 1; transform: translateY(0); }
.cc-msg.dead-end { opacity: 0.55; font-style: italic; align-self: center; border: 1px dashed var(--hair); }
@media (max-width: 720px) { .cc-cols { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) {
.cc-msg { opacity: 1 !important; transform: none !important; }
.cc-typing { display: none !important; }
}
import { ChatCompare } from "@magicblocksai/ui";
<ChatCompare
variant="generic-chatbot"
scenario="mortgage"
heading={<>Same lead. Same question. Watch <em>the difference</em>.</>}
generic={{
name: "Generic chatbot",
status: "—",
messages: [
{ from: "in", text: "Hey, what rates are you offering on a 30-year fixed?" },
{ from: "out", text: "Thanks for your interest! A loan specialist will reach out." },
{ from: "out", text: "[link dump]", deadEnd: true },
],
}}
mb={{
name: "MagicBlocks",
status: "Booked",
messages: [
{ from: "in", text: "Hey, what rates are you offering on a 30-year fixed?" },
{ from: "out", text: <>30-year fixed at <em>6.75–7.0%</em> this week. 15-min call tomorrow at 10:30?</> },
{ from: "in", text: "10:30 works." },
{ from: "out", text: "Done. Sarah Chen will call. ✓" },
],
}}
/>
The left-column label + script swap by destination page. generic-chatbot (default) hard-stops with a link dump. closebot-style echoes back what the lead said and ends in a logged inquiry. chatbase-style answers the FAQ accurately but never converts. The right column always carries the MagicBlocks multi-turn close. Conversation scripts are page-specific (locked in the commission doc, Appendix B); the code-tabs here show the labelling contract.
<!-- The shared shell — same .cc-cols / .cc-col / .cc-thread structure for every variant.
data-cc-variant swaps the left-column label + dialog template; the right column
always carries the MagicBlocks multi-turn close. -->
<!-- 01 · generic-chatbot (homepage / vs-chatbots) — left dead-ends with a link dump -->
<div class="chat-comparison" data-chat-comparison
data-cc-variant="generic-chatbot"
data-scenario="mortgage">
<div class="cc-cols">
<div class="cc-col generic" data-cc-side="generic">
<div class="cc-col-head">
<div class="cc-col-name"><span class="dot"></span> Generic chatbot</div>
<div class="cc-col-status">—</div>
</div>
<div class="cc-thread">…dead-end script: "Thanks for your interest" + link dump…</div>
</div>
<div class="cc-col mb" data-cc-side="mb">
<div class="cc-col-head"><div class="cc-col-name"><span class="dot"></span> MagicBlocks</div><div class="cc-col-status">Booked</div></div>
<div class="cc-thread">…MB script — quotes rate, books slot…</div>
</div>
</div>
</div>
<!-- 02 · closebot-style (/compare/closebot) — left echoes back, ends with logged inquiry -->
<div class="chat-comparison" data-chat-comparison
data-cc-variant="closebot-style"
data-scenario="mortgage">
<div class="cc-cols">
<div class="cc-col generic" data-cc-side="generic">
<div class="cc-col-head">
<div class="cc-col-name"><span class="dot"></span> CLOSEBOT-style workflow AI</div>
<div class="cc-col-status">Logged</div>
</div>
<div class="cc-thread">…echo-back loop: confirms details, ends with "I've logged your inquiry"…</div>
</div>
<div class="cc-col mb" data-cc-side="mb">
<div class="cc-col-head"><div class="cc-col-name"><span class="dot"></span> MagicBlocks</div><div class="cc-col-status">Booked</div></div>
<div class="cc-thread">…MB script — qualifies + books in one turn…</div>
</div>
</div>
</div>
<!-- 03 · chatbase-style (/compare/chatbase) — left answers FAQ accurately, never converts -->
<div class="chat-comparison" data-chat-comparison
data-cc-variant="chatbase-style"
data-scenario="mortgage">
<div class="cc-cols">
<div class="cc-col generic" data-cc-side="generic">
<div class="cc-col-head">
<div class="cc-col-name"><span class="dot"></span> CHATBASE-style FAQ AI</div>
<div class="cc-col-status">Answered</div>
</div>
<div class="cc-thread">…FAQ + handoff: rates page link, then "loan officers Mon–Fri"…</div>
</div>
<div class="cc-col mb" data-cc-side="mb">
<div class="cc-col-head"><div class="cc-col-name"><span class="dot"></span> MagicBlocks</div><div class="cc-col-status">Booked</div></div>
<div class="cc-thread">…MB script — proposes review, books Sarah Chen for 10:30…</div>
</div>
</div>
</div>
<!-- destination-page mapping -->
<!-- / → data-cc-variant="generic-chatbot" data-scenario="mortgage" -->
<!-- /compare/chatbots → data-cc-variant="generic-chatbot" data-scenario="mortgage" -->
<!-- /compare/closebot → data-cc-variant="closebot-style" data-scenario="mortgage" -->
<!-- /compare/chatbase → data-cc-variant="chatbase-style" data-scenario="mortgage" -->
.chat-comparison {
width: 100%;
max-width: min(980px, 100%);
margin: 0 auto;
display: flex; flex-direction: column; gap: var(--s-5);
}
// JSON data contract — register scripts once, the component picks
// the right pair based on data-cc-variant + data-scenario.
window.MB_CC_SCRIPTS = {
"generic-chatbot": {
"mortgage": { "left": [/* dead-end script */], "right": [/* MB script */] },
"insurance": { "left": [...], "right": [...] }
/* solar | home-services | auto | fintech */
},
"closebot-style": { "mortgage": { "left": [/* echo-back loop */], "right": [...] } },
"chatbase-style": { "mortgage": { "left": [/* FAQ + handoff */], "right": [...] } }
};
// Disclosure captions (rendered beneath each comparison)
window.MB_CC_DISCLOSURES = {
"generic-chatbot": "Illustrative comparison. Rates shown are market ranges, not offers. Actual rates are determined by licensed loan officers based on full application.",
"closebot-style": "Illustrative comparison. Actual Closebot output varies. Both systems are real AI sales agent platforms; the comparison highlights architectural differences.",
"chatbase-style": "Illustrative comparison. Actual Chatbase output varies. Both systems are real conversational AI products in different categories."
};
import { ChatCompare } from "@magicblocksai/ui";
// `variant` swaps the left-column dialog template:
// "generic-chatbot" · "closebot-style" · "chatbase-style"
// `scenario` swaps the industry copy across both columns.
// The right (MagicBlocks) column always carries the multi-turn close.
// 01 · generic-chatbot — homepage / vs-chatbots — left dead-ends with a link dump
<ChatCompare
variant="generic-chatbot"
scenario="mortgage"
generic={{
name: "Generic chatbot",
status: "—",
messages: [
{ from: "in", text: "Hey, what rates are you offering on a 30-year fixed right now?" },
{ from: "out", text: "Thanks for your interest! A loan specialist will reach out during business hours." },
{ from: "out", text: "[link dump]", deadEnd: true },
],
}}
mb={{
name: "MagicBlocks",
status: "Booked",
messages: [
{ from: "in", text: "Hey, what rates are you offering on a 30-year fixed right now?" },
{ from: "out", text: <>30-year fixed at <em>6.75–7.0%</em>. 15-min review tomorrow at 10:30?</> },
{ from: "in", text: "10:30 works." },
{ from: "out", text: "Done. Sarah Chen will call. ✓" },
],
}}
/>
// 02 · closebot-style — /compare/closebot — left echoes back, ends with logged inquiry
<ChatCompare
variant="closebot-style"
scenario="mortgage"
generic={{
name: "CLOSEBOT-style workflow AI",
status: "Logged",
messages: [
{ from: "in", text: "Hey, what rates are you offering on a 30-year fixed right now?" },
{ from: "out", text: "Happy to help! What's your estimated credit score and loan amount?" },
{ from: "in", text: "Mid-700s, $420K." },
{ from: "out", text: "Confirming: 30-year fixed for $420K, mid-700s credit. Is that correct?" },
{ from: "in", text: "Yes." },
{ from: "out", text: "Got it — I've logged your inquiry.", deadEnd: true },
],
}}
mb={{
name: "MagicBlocks",
status: "Booked",
messages: [
{ from: "in", text: "Hey, what rates are you offering on a 30-year fixed right now?" },
{ from: "out", text: <>30-year fixed at <em>6.75–7.0%</em> for strong credit. Refi or purchase, rough loan amount, credit range?</> },
{ from: "in", text: "Refi, $420K, mid-700s." },
{ from: "out", text: <>Strong profile — Sarah Chen <em>tomorrow 10:30</em>?</> },
{ from: "in", text: "10:30." },
{ from: "out", text: "Done. ✓" },
],
}}
/>
// 03 · chatbase-style — /compare/chatbase — left answers FAQ, never converts
<ChatCompare
variant="chatbase-style"
scenario="mortgage"
generic={{
name: "CHATBASE-style FAQ AI",
status: "Answered",
messages: [
{ from: "in", text: "Hey, what rates are you offering on a 30-year fixed right now?" },
{ from: "out", text: "Great question! Current 30-year fixed rates vary by credit, loan amount, and other factors. See our rates page: [link]" },
{ from: "in", text: "Can I talk to someone?" },
{ from: "out", text: "Loan officers are available Mon–Fri, 9 AM–5 PM at [number].", deadEnd: true },
],
}}
mb={{
name: "MagicBlocks",
status: "Booked",
messages: [
{ from: "in", text: "Hey, what rates are you offering on a 30-year fixed right now?" },
{ from: "out", text: <>30-year fixed at <em>6.75–7.0%</em> for strong credit. Want a 15-min rate review with one of our LOs?</> },
{ from: "in", text: "Yes." },
{ from: "out", text: "Tomorrow 10:30 or 2:45?" },
{ from: "in", text: "10:30." },
{ from: "out", text: "Done. Sarah Chen will call. ✓" },
],
}}
/>
Four tiny pieces that slot into the bigger components: the compliance trust row, a spinning channel orbit, an ignition spark, and an editorial stat badge.
Rotates 40s per revolution. Pairs with the engine block; can stand alone as decoration.
An 8-ray ignition glyph. Use inline where copy says "engine fires" or "moment arrives."
Editorial big-number callout. Swap integer → Fraunces-italic suffix for a brand-signature beat.
<span class="stat-badge">
<span class="eyebrow">Avg. response</span>
<span class="num">5<sup>s</sup></span>
<span class="cap">Every lead, every time</span>
</span>
<!-- with the [data-count-to] count-up -->
<span class="stat-badge">
<span class="eyebrow">Task completion</span>
<span class="num" data-count-to="97.5" data-decimals="1" data-suffix="%">97.5%</span>
<span class="cap">vs 59% single-prompt</span>
</span>
.stat-badge {
display: inline-flex; flex-direction: column; gap: 6px;
padding: 14px 18px;
background: var(--bg-paper);
border: 1px solid var(--hair);
border-radius: var(--r-md);
max-width: 200px;
}
.stat-badge .eyebrow {
font: 500 10.5px/1 var(--f-mono); color: var(--accent-text);
letter-spacing: 0.14em; text-transform: uppercase;
}
.stat-badge .num {
font: 700 40px/1 var(--f-display); color: var(--fg);
letter-spacing: -0.025em; font-variant-numeric: tabular-nums;
}
.stat-badge .num em { font-family: var(--f-serif); font-style: italic; font-weight: 400; color: var(--accent); }
.stat-badge .num sup { font-size: 0.55em; font-weight: 500; vertical-align: top; color: var(--fg-soft); margin-left: 2px; }
.stat-badge .cap {
font: 500 11.5px/1.3 var(--f-body); color: var(--fg-soft);
}
import { StatBadge } from "@magicblocksai/ui";
<StatBadge
eyebrow="Avg. response"
num={<>5<sup>s</sup></>}
caption="Every lead, every time"
/>
// With count-up animation:
<StatBadge
eyebrow="Task completion"
num="97.5%"
countTo={97.5}
decimals={1}
suffix="%"
caption="vs 59% single-prompt"
/>
Tells the pricing argument as a three-beat story: a clear headline pins the meaning, two bars show the dramatic gap (acquisition cost vs MagicBlocks per lead), and a punchline callout pulls out the percentage so the conclusion lands without squinting. Lives on the Pricing hero.
<div class="cost-compare" aria-label="Acquisition cost vs MagicBlocks per lead">
<div class="cc-eyebrow">Per lead in your funnel</div>
<div class="cc-headline">
You already paid to acquire the lead.
<em>Working it costs a fraction more.</em>
</div>
<div class="cc-bars">
<div class="cc-bar" style="--cc-pct: 100%;">
<div class="cc-bar-meta">
<div class="cc-bar-label">Acquisition</div>
<div class="cc-bar-sub">Ads · SDRs · list buys · content</div>
</div>
<div class="cc-bar-track"><div class="cc-bar-fill"></div></div>
<div class="cc-bar-amount">$30 – $80</div>
</div>
<div class="cc-bar is-engine" style="--cc-pct: 6%;">
<div class="cc-bar-meta">
<div class="cc-bar-label">MagicBlocks</div>
<div class="cc-bar-sub">Every channel, every attempt</div>
</div>
<div class="cc-bar-track"><div class="cc-bar-fill"></div></div>
<div class="cc-bar-amount">$2.50 – $4</div>
</div>
</div>
<div class="cc-callout">
<span class="cc-callout-pct">~7%</span>
<span class="cc-callout-cap">of what you've already spent — and the lead gets <strong>worked</strong>.</span>
</div>
</div>
.cost-compare .cc-bar-fill { background: var(--warm-7); width: var(--cc-pct, 100%); }
.cost-compare .cc-bar.is-engine .cc-bar-fill {
background: var(--accent);
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 30%, transparent),
0 4px 14px -6px color-mix(in oklab, var(--accent) 60%, transparent);
}
// CostCompare is a chapter-only narrative shape — render with the kit's CSS classes.
<div className="cost-compare" aria-label="Acquisition cost vs MagicBlocks per lead">
<div className="cc-eyebrow">Per lead in your funnel</div>
<div className="cc-headline">
You already paid to acquire the lead. <em>Working it costs a fraction more.</em>
</div>
<div className="cc-bars">
<div className="cc-bar" style={{ "--cc-pct": "100%" } as React.CSSProperties}>
<div className="cc-bar-meta">
<div className="cc-bar-label">Acquisition</div>
<div className="cc-bar-sub">Ads · SDRs · list buys · content</div>
</div>
<div className="cc-bar-track"><div className="cc-bar-fill" /></div>
<div className="cc-bar-amount">$30 – $80</div>
</div>
<div className="cc-bar is-engine" style={{ "--cc-pct": "6%" } as React.CSSProperties}>
<div className="cc-bar-meta">
<div className="cc-bar-label">MagicBlocks</div>
<div className="cc-bar-sub">Every channel, every attempt, qualified</div>
</div>
<div className="cc-bar-track"><div className="cc-bar-fill" /></div>
<div className="cc-bar-amount">$2.50 – $4</div>
</div>
</div>
<div className="cc-callout">
<span className="cc-callout-pct">~7%</span>
<span className="cc-callout-cap">
of what you've already spent — and the lead gets <strong>worked</strong>, not left to rot in a CRM.
</span>
</div>
</div>
Oversized stat-pair (97.5% vs 59%) dominating the visual, paired with two animated dot-rows that tick on left-to-right in a continuous 8s loop — each dot is one lead arriving. The single-prompt row's right tail fades out as completions drop; MagicBlocks stays solid. Ink surface for max contrast. Honours prefers-reduced-motion. Built to live on Built for Production §4.
<div class="stress-scoreboard" aria-label="Production-scale completion">
<div class="ss-pair">
<div class="ss-stat is-pass">
<div class="ss-stat-eyebrow">MagicBlocks</div>
<div class="ss-stat-num">97.5%</div>
<div class="ss-stat-cap">complete every lead, end-to-end…</div>
</div>
<div class="ss-vs">vs</div>
<div class="ss-stat is-fail">
<div class="ss-stat-eyebrow">Single-prompt AI</div>
<div class="ss-stat-num">59%</div>
<div class="ss-stat-cap">drops the rest…</div>
</div>
</div>
<div>
<div class="ss-cap">100 leads through each system · live trace</div>
<div class="ss-rows" data-stress-rows aria-hidden="true">
<div class="ss-row is-fail" data-row-label="Single-prompt"></div>
<div class="ss-row is-pass" data-row-label="MagicBlocks"></div>
</div>
</div>
</div>
<script>
// 25 dots per row; CSS animation does the wave + loop.
document.querySelectorAll("[data-stress-rows] .ss-row").forEach(row => {
const total = 25;
const isFail = row.classList.contains("is-fail");
const solid = isFail ? 15 : 24;
for (let i = 0; i < total; i++) {
const s = document.createElement("span");
s.style.setProperty("--i", i);
// Per-dot state drives the green / red colour. Failed dots get
// a fading tail opacity for visual weight (0.6 → 0.1 across them).
s.dataset.state = i < solid ? "pass" : "fail";
s.style.setProperty("--final-op",
i < solid ? "0.95"
: Math.max(0.1, 0.6 - ((i - solid) / (total - solid)) * 0.5).toFixed(2));
row.appendChild(s);
}
});
</script>
.stress-scoreboard .ss-row {
display: grid; grid-template-columns: repeat(25, 1fr); gap: 8px;
}
.stress-scoreboard .ss-row span {
aspect-ratio: 1; border-radius: 50%;
background: var(--green-500); /* default; per-dot data-state overrides */
opacity: 0; transform: scale(0.3);
/* 8s loop: pop in (0-3%), hold (to 72%), fade (to 90%), pause (to 100%).
animation-delay staggers each dot left-to-right. */
animation: ss-pop 8s linear infinite;
animation-delay: calc(var(--i, 0) * 0.05s);
}
.stress-scoreboard .ss-row span[data-state="pass"] { background: var(--green-500); }
.stress-scoreboard .ss-row span[data-state="fail"] { background: var(--error); }
@keyframes ss-pop {
0% { opacity: 0; transform: scale(0.3); }
3% { opacity: var(--final-op, 0.95); transform: scale(1); }
72% { opacity: var(--final-op, 0.95); transform: scale(1); }
90% { opacity: 0; transform: scale(0.85); }
100% { opacity: 0; transform: scale(0.3); }
}
@media (prefers-reduced-motion: reduce) {
.stress-scoreboard .ss-row span { animation: none; opacity: var(--final-op, 0.95); transform: none; }
}
/* Failure dots use --error (red), not --accent (pink). Pass dots stay
green. The brand pink would conflict with the bright pass-state and
read as "neutral/decorative" rather than "this stress test failed". */
import { useEffect, useRef } from "react";
// StressScoreboard is a chapter-only narrative shape — render with the kit's CSS classes.
// Two rows of 25 dots; CSS animation handles the wave. Each dot gets --i (delay) and
// --final-op (held opacity) so the failure tail fades out while MagicBlocks stays solid.
function StressScoreboard() {
const rowsRef = useRef<HTMLDivElement>(null);
useEffect(() => {
rowsRef.current?.querySelectorAll(".ss-row").forEach((row) => {
if (row.childElementCount > 0) return;
const total = 25;
const isFail = row.classList.contains("is-fail");
const solid = isFail ? 15 : 24;
for (let i = 0; i < total; i++) {
const s = document.createElement("span");
s.style.setProperty("--i", String(i));
s.dataset.state = i < solid ? "pass" : "fail";
s.style.setProperty(
"--final-op",
i < solid
? "0.95"
: Math.max(0.1, 0.6 - ((i - solid) / (total - solid)) * 0.5).toFixed(2),
);
row.appendChild(s);
}
});
}, []);
return (
<div className="stress-scoreboard" aria-label="Production-scale completion rate">
<div className="ss-pair">
<div className="ss-stat is-pass">
<div className="ss-stat-eyebrow">MagicBlocks</div>
<div className="ss-stat-num">97.5<span style={{ fontSize: "0.4em", verticalAlign: "top" }}>%</span></div>
<div className="ss-stat-cap">complete every lead, end-to-end, at production scale.</div>
</div>
<div className="ss-vs">vs</div>
<div className="ss-stat is-fail">
<div className="ss-stat-eyebrow">Single-prompt AI</div>
<div className="ss-stat-num">59<span style={{ fontSize: "0.4em", verticalAlign: "top" }}>%</span></div>
<div className="ss-stat-cap">drops the rest somewhere between turn 3 and turn 30.</div>
</div>
</div>
<div>
<div className="ss-cap" style={{ marginBottom: "var(--s-4)" }}>
100 leads through each system · live trace
</div>
<div className="ss-rows" data-stress-rows aria-hidden="true" ref={rowsRef}>
<div className="ss-row is-fail" data-row-label="Single-prompt" />
<div className="ss-row is-pass" data-row-label="MagicBlocks" />
</div>
</div>
<div className="ss-dot-legend">
<span><i className="is-pass" />MagicBlocks · ~24/25 complete</span>
<span><i className="is-fail" />Single-prompt · ~15/25 complete, tail drops</span>
</div>
</div>
);
}
Five composite illustrations that live inline on specific page sections rather than as canonical components. They're hand-composed from brand-kit primitives (typography, tokens, SVG, conversation UI) — no shared API. Use them straight from this page, customise per destination.
Single monolithic prompt vs modular multi-prompt stack. Used on How It Works §4 + Built for Production §3 + §9.
Inward-converging diagram: knowledge sources → engine → conversational outputs. Used on How It Works §6.
Side-by-side conversation pair: a polished demo turn (left) vs an unscripted production exchange (right). Used on Built for Production hero.
Admin UI mockup for the guardrails configuration screen — composed from Chapter 04 (Forms & Inputs) + Chapter 10 (Dashboard shell). Used on Built for Production §6.
Animated waveform paired with transcript bubbles. Used on Channel · Voice hero.
How the narrative-systems chapter hangs together. Three shared primitives do most of the work; individual components only add what's unique to them.
--i: N → transition-delay: calc(var(--i) * 60ms).
Every chapter-11 component opts in with a single class..reveal. Any <path class="draw"> (or every path) gets a
stroke-dasharray → stroke-dashoffset transition.
Tune per component with --draw-len.data-decimals and
data-prefix / data-suffix. Used by stat badges and the ROI calculator._shared.js reveal loader also bails out, marking every element visible immediately.--accent, --ink, --warm-3,
--hair, --industry-accent. The dark variants flip cleanly; industry
colour-washes happen at the page level.Animated “scroll for more” indicator harvested from the research-report landing page. Mono uppercase label above a vertical hairline that pulses on a 2.4s loop. Position with the consumer's wrapper — the canonical placement is position: absolute; bottom: var(--s-5); left: 50%; transform: translateX(-50%) inside a position: relative hero. Honours prefers-reduced-motion; the line stays static, no pulse.
<div class="scroll-cue">
<span>Scroll for the data</span>
<span class="scroll-cue-line" aria-hidden="true"></span>
</div>
.scroll-cue {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 10px;
font-family: var(--f-mono);
font-size: 11px;
letter-spacing: 0.10em;
text-transform: uppercase;
color: var(--fg-dim);
}
.scroll-cue-line {
width: 1px;
height: 38px;
background: currentColor;
opacity: 0.4;
transform-origin: top;
animation: scroll-cue-pulse 2.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.scroll-cue-line { animation: none; opacity: 0.5; }
}
import { ScrollCue } from "@magicblocksai/ui";
<ScrollCue>Scroll for the data</ScrollCue>
Two-column conversation comparison for “our agent vs theirs”, “old script vs new”, before/after behaviour stories. Each column has a header (who · tag · status), a stack of message bubbles alternating from="us" and from="them", and an optional outcome banner pinned at the bottom. Tones drive the top stripe and the is-us bubble fill: win is accent pink, loss is warm grey, neutral has no stripe. Collapses to a single column below 720px.
The flagship comparison from the research-report landing page: same prompt, two agents. OnePrompt forgets it never got the phone number and moves on; MagicBlocks refuses to progress past Contact until the field is collected.
phone_number was collected.
<div class="dialogue-contrast">
<article class="dialogue-column" data-tone="loss">
<header class="dialogue-head">
<span class="dialogue-who">OnePrompt</span>
<span class="dialogue-tag">· run #400</span>
<span class="dialogue-status"><span class="badge badge-danger">Failed</span></span>
</header>
<div class="dialogue-msgs">
<div class="dialogue-msg is-us">
<span class="dialogue-avatar">AI</span>
<span class="dialogue-bubble">What is your phone number?</span>
</div>
<!-- … more <.dialogue-msg> rows -->
</div>
<div class="dialogue-outcome" data-tone="bad">
<span class="dialogue-outcome-label">Outcome</span>
<span>The agent <strong>forgot it never got the phone number</strong> and moved on.</span>
</div>
</article>
<article class="dialogue-column" data-tone="win">… mirror structure …</article>
</div>
.dialogue-contrast {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--s-5);
margin-top: var(--s-5);
}
@media (max-width: 720px) {
.dialogue-contrast { grid-template-columns: 1fr; }
}
import {
DialogueContrast,
DialogueColumn,
DialogueMessage,
DialogueEllip,
Badge,
} from "@magicblocksai/ui";
<DialogueContrast>
<DialogueColumn
tone="loss"
who="OnePrompt"
tag="· run #400"
status={<Badge tone="danger">Failed</Badge>}
outcome={{
tone: "bad",
text: <>The agent <strong>forgot it never got the phone number</strong> and moved on.</>,
}}
>
<DialogueMessage from="us" avatar="AI">What is your phone number?</DialogueMessage>
<DialogueMessage from="them" avatar="C">Why do you need that?</DialogueMessage>
<DialogueMessage from="us" avatar="AI">
<DialogueEllip>[explains privacy policy for 3 long paragraphs]</DialogueEllip>
</DialogueMessage>
<DialogueMessage from="them" avatar="C">Okay. Are rates going up next week?</DialogueMessage>
</DialogueColumn>
<DialogueColumn
tone="win"
who="MagicBlocks"
tag="· run #400"
status={<Badge tone="success">Completed</Badge>}
outcome={{
tone: "good",
text: <>The agent <strong>refused to progress past Contact</strong> until <code>phone_number</code> was collected.</>,
}}
>
<DialogueMessage from="us" avatar="AI">What is your phone number?</DialogueMessage>
<DialogueMessage from="them" avatar="C">Why do you need that?</DialogueMessage>
<DialogueMessage from="us" avatar="AI">I still need your phone number to continue your application.</DialogueMessage>
</DialogueColumn>
</DialogueContrast>
One side of a .dialogue-contrast. Useful on its own when the comparison is implicit — e.g. a single-column run log under a screenshot. The data-tone attribute drives the top-stripe colour and the is-us bubble fill: omit it for the neutral (no-stripe, dark-bubble) treatment.
<article class="dialogue-column">
<header class="dialogue-head">
<span class="dialogue-who">Charlie</span>
<span class="dialogue-tag">· transcript</span>
</header>
<div class="dialogue-msgs">
<div class="dialogue-msg is-us">
<span class="dialogue-avatar">AI</span>
<span class="dialogue-bubble">Are you still looking to refinance the property on Maple?</span>
</div>
<div class="dialogue-msg is-them">
<span class="dialogue-avatar">C</span>
<span class="dialogue-bubble">Yes — if rates are under 6 still.</span>
</div>
</div>
</article>
.dialogue-column {
background: var(--bg-paper);
border: 1px solid var(--hair);
border-radius: var(--r-lg);
padding: var(--s-5);
box-shadow: var(--sh-1);
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.dialogue-column[data-tone="win"] { border-top: 3px solid var(--accent); }
.dialogue-column[data-tone="loss"] { border-top: 3px solid var(--warm-7); }
.dialogue-column[data-tone="win"] .dialogue-msg.is-us .dialogue-bubble { background: var(--accent); }
import { DialogueColumn, DialogueMessage } from "@magicblocksai/ui";
<DialogueColumn who="Charlie" tag="· transcript">
<DialogueMessage from="us" avatar="AI">
Are you still looking to refinance the property on Maple?
</DialogueMessage>
<DialogueMessage from="them" avatar="C">
Yes — if rates are under 6 still.
</DialogueMessage>
</DialogueColumn>
One row inside a column. from="us" renders right-aligned with the column-tone bubble fill; from="them" renders left-aligned with the warm bubble fill. The .dialogue-avatar is a 28px round with the speaker's initial; the .dialogue-bubble takes the body content.
<div class="dialogue-msg is-us">
<span class="dialogue-avatar">AI</span>
<span class="dialogue-bubble">What is your phone number?</span>
</div>
<div class="dialogue-msg is-them">
<span class="dialogue-avatar">C</span>
<span class="dialogue-bubble">Why do you need that?</span>
</div>
.dialogue-msg {
display: flex;
gap: 10px;
max-width: 92%;
}
.dialogue-msg.is-them { align-self: flex-start; }
.dialogue-msg.is-us { align-self: flex-end; flex-direction: row-reverse; }
.dialogue-msg.is-us .dialogue-avatar { background: var(--ink); color: var(--paper); }
.dialogue-msg.is-them .dialogue-bubble { border-radius: 14px 14px 14px 4px; }
.dialogue-msg.is-us .dialogue-bubble {
background: var(--ink);
color: var(--paper);
border-radius: 14px 14px 4px 14px;
}
.dialogue-column[data-tone="win"] .dialogue-msg.is-us .dialogue-bubble { background: var(--accent); }
import { DialogueMessage } from "@magicblocksai/ui";
<DialogueMessage from="us" avatar="AI">
What is your phone number?
</DialogueMessage>
<DialogueMessage from="them" avatar="C">
Why do you need that?
</DialogueMessage>
Soft italic ellipsised span for stage-direction text inside a .dialogue-bubble — signals “the agent went on for a while” without quoting it verbatim. Used for “[explains privacy policy at length]”, “[tries to reroute to a sales rep]”, anything where the length is the point.
<div class="dialogue-msg is-us">
<span class="dialogue-avatar">AI</span>
<span class="dialogue-bubble">
<span class="dialogue-bubble-ellip">[explains privacy policy for 3 long paragraphs]</span>
</span>
</div>
.dialogue-bubble {
padding: 10px 14px;
border-radius: 14px;
font-size: 14px;
line-height: 1.5;
background: var(--bg-warm);
color: var(--fg);
}
.dialogue-msg.is-them .dialogue-bubble { border-radius: 14px 14px 14px 4px; }
.dialogue-msg.is-us .dialogue-bubble {
background: var(--ink);
color: var(--paper);
border-radius: 14px 14px 4px 14px;
}
.dialogue-column[data-tone="win"] .dialogue-msg.is-us .dialogue-bubble { background: var(--accent); }
.dialogue-bubble-ellip {
color: color-mix(in oklab, currentColor 60%, transparent);
font-style: italic;
font-size: 12.5px;
}
import { DialogueMessage, DialogueEllip } from "@magicblocksai/ui";
<DialogueMessage from="us" avatar="AI">
<DialogueEllip>[explains privacy policy for 3 long paragraphs]</DialogueEllip>
</DialogueMessage>
Proportion visualisation — a grid of dots where a subset is highlighted to communicate “out of N, M did X”. The canonical default is a 1,000-dot 40×25 grid (one dot per lead in a 1k-lead sample). Resize via total / cols for other ratios. Distribution modes: scatter (default) evenly spaces the highlights so the proportion reads visually; head / tail cluster them; random uses a deterministic PRNG so SSR + client agree on the layout.
The flagship comparison from the Reliability Gap landing page: ~25 leads lost to agent errors on MagicBlocks vs ~410 on OnePrompt, at the same 1,000-lead sample.
~25 lost to agent errors.
~410 lost to agent errors.
<div class="dot-matrix" role="img"
aria-label="25 of 1,000 leads lost"
style="--dm-cols: 40;">
<!-- 1,000 <span class="dot-matrix-cell"> cells; scatter the
25 highlighted ones (.is-highlight) evenly through the grid -->
</div>
.dot-matrix {
display: grid;
grid-template-columns: repeat(var(--dm-cols, 40), 1fr);
gap: var(--dm-gap, 3px);
margin-top: var(--s-4);
}
.dot-matrix-cell {
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 2px;
background: var(--dm-base, var(--bg-warm));
opacity: 0.45;
}
.dot-matrix-cell.is-highlight {
background: var(--dm-highlight, var(--accent));
opacity: 1;
}
import { DotMatrix } from "@magicblocksai/ui";
<DotMatrix
total={1000}
highlight={25}
highlightColor="var(--accent)"
aria-label="25 of 1,000 leads lost to agent errors"
/>
<DotMatrix
total={1000}
highlight={410}
highlightColor="var(--accent)"
aria-label="410 of 1,000 leads lost to agent errors"
/>