Skip to content

Instantly share code, notes, and snippets.

@kbouw
Created February 20, 2026 02:05
Show Gist options
  • Select an option

  • Save kbouw/eb3c1d138d95ab0dbf290105c6dfdf8d to your computer and use it in GitHub Desktop.

Select an option

Save kbouw/eb3c1d138d95ab0dbf290105c6dfdf8d to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>How NATS Decouples Your Services</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body, #root { height: 100%; }
body { background: #111216; }
</style>
</head>
<body>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script>
var h = React.createElement;
var useState = React.useState;
var useEffect = React.useEffect;
var useRef = React.useRef;
var useCallback = React.useCallback;
// ─── Color palette ─────────────────────────────────────────
var COLORS = {
bg: "#111216",
surface: "rgba(255,255,255,0.015)",
surfaceHover: "rgba(255,255,255,0.04)",
border: "rgba(255,255,255,0.04)",
text: "#e8e6e3",
textMuted: "#6a6e76",
textDim: "#4a4e56",
pub: "#8cb4ff",
pubGlow: "rgba(140,180,255,0.15)",
sub: "#f0c674",
subGlow: "rgba(240,198,116,0.15)",
nats: "#4ade80",
natsGlow: "rgba(74,222,128,0.2)",
connector: "#3a3e46",
connectorActive: "#8a8f98",
danger: "#f07474",
dangerGlow: "rgba(240,116,116,0.1)",
wildcard: "#a78bfa",
wildcardGlow: "rgba(167,139,250,0.15)",
queue: "#f0c674",
jet: "#60a5fa",
jetGlow: "rgba(96,165,250,0.15)",
};
var FONTS = {
body: "'IBM Plex Sans', -apple-system, sans-serif",
mono: "'JetBrains Mono', monospace",
};
var SERVICES_L = ["Order API", "User API", "Payment", "Inventory"];
var SERVICES_R = ["Email", "Analytics", "Billing", "Search", "Audit"];
// ─── InsightCard ───────────────────────────────────────────
function InsightCard(props) {
return h("div", {
style: Object.assign({
background: "rgba(255,255,255,0.02)",
border: "1px solid rgba(255,255,255,0.04)",
borderRadius: 8,
padding: "12px 16px",
fontFamily: FONTS.mono,
fontSize: 12,
color: COLORS.textMuted,
lineHeight: 1.6,
}, props.style || {})
}, props.children);
}
// ═══════════════════════════════════════════════════════════
// PANEL 1: The Coupling Problem
// ═══════════════════════════════════════════════════════════
function ProblemPanel() {
var ref = useState(null), hoveredPair = ref[0], setHoveredPair = ref[1];
var ref2 = useState(false), animate = ref2[0], setAnimate = ref2[1];
useEffect(function() {
var t = setTimeout(function() { setAnimate(true); }, 300);
return function() { clearTimeout(t); };
}, []);
function leftY(i) { return 30 + i * 68; }
function rightY(i) { return 14 + i * 56; }
var connectorCount = SERVICES_L.length * SERVICES_R.length;
var lines = [];
SERVICES_L.forEach(function(c, ci) {
SERVICES_R.forEach(function(s, si) {
var isHovered = hoveredPair && hoveredPair[0] === ci && hoveredPair[1] === si;
var anyHovered = hoveredPair !== null;
lines.push(h("line", {
key: ci + "-" + si,
x1: 160, y1: leftY(ci) + 14,
x2: 440, y2: rightY(si) + 14,
stroke: isHovered ? COLORS.danger : COLORS.connector,
strokeWidth: isHovered ? 2 : 1,
opacity: animate ? (anyHovered ? (isHovered ? 1 : 0.15) : 0.4) : 0,
style: { transition: "opacity 0.6s ease-out, stroke 0.2s, stroke-width 0.2s" }
}));
});
});
var leftNodes = SERVICES_L.map(function(c, i) {
return h("g", { key: "l-" + i, style: { cursor: "default" } },
h("rect", {
x: 12, y: leftY(i), width: 140, height: 28, rx: 6,
fill: COLORS.pubGlow, stroke: COLORS.pub + "40", strokeWidth: 1,
opacity: animate ? 1 : 0,
style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" },
onMouseEnter: function() { setHoveredPair([i, -1]); },
onMouseLeave: function() { setHoveredPair(null); }
}),
h("circle", { cx: 28, cy: leftY(i) + 14, r: 4, fill: COLORS.pub, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" } }),
h("text", { x: 40, y: leftY(i) + 18, fontFamily: FONTS.mono, fontSize: 12, fill: COLORS.pub, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" } }, c)
);
});
var rightNodes = SERVICES_R.map(function(s, i) {
return h("g", { key: "r-" + i },
h("rect", {
x: 448, y: rightY(i), width: 140, height: 28, rx: 6,
fill: COLORS.subGlow, stroke: COLORS.sub + "40", strokeWidth: 1,
opacity: animate ? 1 : 0,
style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" },
onMouseEnter: function() { setHoveredPair([-1, i]); },
onMouseLeave: function() { setHoveredPair(null); }
}),
h("circle", { cx: 464, cy: rightY(i) + 14, r: 4, fill: COLORS.sub, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" } }),
h("text", { x: 476, y: rightY(i) + 18, fontFamily: FONTS.mono, fontSize: 12, fill: COLORS.sub, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" } }, s)
);
});
return h("div", null,
h("div", { style: { marginBottom: 20 } },
h("h3", { style: { fontFamily: FONTS.body, fontSize: 18, fontWeight: 400, color: COLORS.text, margin: "0 0 6px" } }, "Without a message broker"),
h("p", { style: { fontFamily: FONTS.body, fontSize: 14, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "Every service calls every other service directly. Each connection is a custom HTTP integration with its own retry logic, timeouts, and failure modes.")
),
h("div", { style: { position: "relative", width: "100%", minHeight: 300 } },
h("svg", { width: "100%", height: 300, viewBox: "0 0 600 300", style: { overflow: "visible" } },
lines, leftNodes, rightNodes
)
),
h(InsightCard, { style: { marginTop: 12 } },
h("span", { style: { color: COLORS.danger, fontWeight: 600 } }, connectorCount + " direct integrations"),
" for " + SERVICES_L.length + " producers × " + SERVICES_R.length + " consumers. Adding one new service means writing integrations with every service it touches."
)
);
}
// ═══════════════════════════════════════════════════════════
// PANEL 2: Pub/Sub with NATS
// ═══════════════════════════════════════════════════════════
function PubSubPanel() {
var ref = useState(false), animate = ref[0], setAnimate = ref[1];
var ref2 = useState(null), activeMsg = ref2[0], setActiveMsg = ref2[1];
var ref3 = useState([]), particles = ref3[0], setParticles = ref3[1];
var particleId = useRef(0);
useEffect(function() {
var t = setTimeout(function() { setAnimate(true); }, 200);
return function() { clearTimeout(t); };
}, []);
var subjects = [
{ name: "orders.created", from: 0, to: [0, 1, 4], color: COLORS.nats },
{ name: "users.updated", from: 1, to: [1, 3], color: COLORS.pub },
{ name: "payment.processed", from: 2, to: [0, 2, 4], color: COLORS.queue },
{ name: "inventory.low", from: 3, to: [1, 2], color: COLORS.wildcard },
];
function leftY(i) { return 30 + i * 68; }
function rightY(i) { return 14 + i * 56; }
var natsX = 270;
function fireMessage(idx) {
setActiveMsg(idx);
var msg = subjects[idx];
// Create particles for publisher → NATS
var id1 = ++particleId.current;
setParticles(function(prev) {
return prev.concat([{ id: id1, x1: 160, y1: leftY(msg.from) + 14, x2: natsX, y2: 150, color: msg.color, progress: 0, phase: "in" }]);
});
// After delay, create particles NATS → each subscriber
setTimeout(function() {
var newParticles = msg.to.map(function(si) {
var id2 = ++particleId.current;
return { id: id2, x1: natsX + 60, y1: 150, x2: 440, y2: rightY(si) + 14, color: msg.color, progress: 0, phase: "out" };
});
setParticles(function(prev) { return prev.concat(newParticles); });
}, 600);
setTimeout(function() { setActiveMsg(null); }, 1800);
}
// Animate particles
useEffect(function() {
if (particles.length === 0) return;
var frame;
function tick() {
setParticles(function(prev) {
var next = prev.map(function(p) {
return Object.assign({}, p, { progress: Math.min(1, p.progress + 0.04) });
}).filter(function(p) { return p.progress < 1; });
return next;
});
frame = requestAnimationFrame(tick);
}
frame = requestAnimationFrame(tick);
return function() { cancelAnimationFrame(frame); };
}, [particles.length > 0]);
var leftLines = SERVICES_L.map(function(_, ci) {
return h("line", {
key: "ll-" + ci,
x1: 152, y1: leftY(ci) + 14,
x2: natsX, y2: 150,
stroke: COLORS.pub, strokeWidth: 1.5,
opacity: animate ? 0.25 : 0,
style: { transition: "opacity 0.4s ease-out " + (0.3 + ci * 0.06) + "s" }
});
});
var rightLines = SERVICES_R.map(function(_, si) {
return h("line", {
key: "rl-" + si,
x1: natsX + 60, y1: 150,
x2: 448, y2: rightY(si) + 14,
stroke: COLORS.sub, strokeWidth: 1.5,
opacity: animate ? 0.25 : 0,
style: { transition: "opacity 0.4s ease-out " + (0.3 + si * 0.06) + "s" }
});
});
var leftNodes = SERVICES_L.map(function(c, i) {
var isSource = activeMsg !== null && subjects[activeMsg].from === i;
return h("g", { key: "l-" + i },
h("rect", {
x: 12, y: leftY(i), width: 140, height: 28, rx: 6,
fill: isSource ? COLORS.natsGlow : COLORS.pubGlow,
stroke: isSource ? COLORS.nats + "60" : COLORS.pub + "40", strokeWidth: 1,
opacity: animate ? 1 : 0,
style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s, fill 0.3s, stroke 0.3s" }
}),
h("circle", { cx: 28, cy: leftY(i) + 14, r: 4, fill: COLORS.pub, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }),
h("text", { x: 40, y: leftY(i) + 18, fontFamily: FONTS.mono, fontSize: 12, fill: COLORS.pub, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }, c)
);
});
var rightNodes = SERVICES_R.map(function(s, i) {
var isTarget = activeMsg !== null && subjects[activeMsg].to.indexOf(i) !== -1;
return h("g", { key: "r-" + i },
h("rect", {
x: 448, y: rightY(i), width: 140, height: 28, rx: 6,
fill: isTarget ? COLORS.natsGlow : COLORS.subGlow,
stroke: isTarget ? COLORS.nats + "60" : COLORS.sub + "40", strokeWidth: 1,
opacity: animate ? 1 : 0,
style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s, fill 0.3s, stroke 0.3s" }
}),
h("circle", { cx: 464, cy: rightY(i) + 14, r: 4, fill: COLORS.sub, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }),
h("text", { x: 476, y: rightY(i) + 18, fontFamily: FONTS.mono, fontSize: 12, fill: COLORS.sub, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }, s)
);
});
// Render particles
var particleEls = particles.map(function(p) {
var cx = p.x1 + (p.x2 - p.x1) * p.progress;
var cy = p.y1 + (p.y2 - p.y1) * p.progress;
return h("circle", {
key: p.id, cx: cx, cy: cy, r: 5,
fill: p.color, opacity: 1 - p.progress * 0.3,
style: { filter: "drop-shadow(0 0 6px " + p.color + ")" }
});
});
// Subject buttons
var subjectBtns = subjects.map(function(sub, i) {
var isActive = activeMsg === i;
return h("button", {
key: sub.name,
onClick: function() { if (activeMsg === null) fireMessage(i); },
style: {
padding: "7px 14px", fontSize: 11, borderRadius: 6, cursor: activeMsg === null ? "pointer" : "default",
fontFamily: FONTS.mono,
background: isActive ? sub.color + "18" : "rgba(255,255,255,0.02)",
border: "1px solid " + (isActive ? sub.color + "40" : "rgba(255,255,255,0.04)"),
color: isActive ? sub.color : COLORS.textMuted,
transition: "all 0.2s", whiteSpace: "nowrap",
}
}, sub.name);
});
return h("div", null,
h("div", { style: { marginBottom: 20 } },
h("h3", { style: { fontFamily: FONTS.body, fontSize: 18, fontWeight: 400, color: COLORS.text, margin: "0 0 6px" } }, "With NATS"),
h("p", { style: { fontFamily: FONTS.body, fontSize: 14, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "Publishers send to a subject. Subscribers listen on subjects. NATS routes between them.")
),
h("div", { style: { display: "flex", gap: 6, marginBottom: 16, flexWrap: "wrap" } },
h("span", { style: { fontFamily: FONTS.mono, fontSize: 10, color: COLORS.textDim, alignSelf: "center", marginRight: 4 } }, "publish →"),
subjectBtns
),
h("div", { style: { position: "relative", width: "100%", minHeight: 300 } },
h("svg", { width: "100%", height: 300, viewBox: "0 0 600 300", style: { overflow: "visible" } },
// NATS server block
h("rect", { x: natsX, y: 20, width: 60, height: 260, rx: 8, fill: COLORS.natsGlow, stroke: COLORS.nats + "30", strokeWidth: 1, opacity: animate ? 1 : 0, style: { transition: "opacity 0.5s ease-out 0.1s" } }),
h("text", { x: natsX + 30, y: 155, fontFamily: FONTS.mono, fontSize: 13, fontWeight: 600, fill: COLORS.nats, textAnchor: "middle", opacity: animate ? 1 : 0, style: { transition: "opacity 0.5s ease-out 0.3s" } }, "NATS"),
leftLines, rightLines, leftNodes, rightNodes, particleEls
)
),
h(InsightCard, { style: { marginTop: 12 } },
h("span", { style: { color: COLORS.nats, fontWeight: 600 } }, (SERVICES_L.length + SERVICES_R.length) + " connections"),
" instead of " + (SERVICES_L.length * SERVICES_R.length) + ". Each service connects to NATS once. Click a subject above to watch a message route to its subscribers."
)
);
}
// ═══════════════════════════════════════════════════════════
// PANEL 3: Subject Wildcards
// ═══════════════════════════════════════════════════════════
function WildcardPanel() {
var allSubjects = [
"orders.us.created",
"orders.us.shipped",
"orders.eu.created",
"orders.eu.cancelled",
"users.signup",
"users.updated",
"payment.us.processed",
"payment.eu.processed",
];
var patterns = [
{ pattern: "orders.us.created", desc: "Exact match — one specific subject", color: COLORS.pub },
{ pattern: "orders.us.*", desc: "Single wildcard — matches one token", color: COLORS.wildcard },
{ pattern: "orders.>", desc: "Multi wildcard — matches one or more tokens", color: COLORS.nats },
{ pattern: "*.us.*", desc: "Wildcard in any position", color: COLORS.queue },
{ pattern: ">", desc: "Everything — all subjects in the system", color: COLORS.danger },
];
var ref = useState(0), activePattern = ref[0], setActivePattern = ref[1];
var currentPattern = patterns[activePattern].pattern;
function matchesPattern(subject, pattern) {
var subParts = subject.split(".");
var patParts = pattern.split(".");
if (pattern === ">") return true;
function matchHelper(si, pi) {
if (pi >= patParts.length && si >= subParts.length) return true;
if (pi >= patParts.length) return false;
if (patParts[pi] === ">") return si < subParts.length;
if (si >= subParts.length) return false;
if (patParts[pi] === "*" || patParts[pi] === subParts[si]) {
return matchHelper(si + 1, pi + 1);
}
return false;
}
return matchHelper(0, 0);
}
var matchCount = allSubjects.filter(function(s) { return matchesPattern(s, currentPattern); }).length;
var subjectRows = allSubjects.map(function(sub) {
var matches = matchesPattern(sub, currentPattern);
var pColor = patterns[activePattern].color;
return h("div", {
key: sub,
style: {
display: "flex", alignItems: "center", gap: 12, padding: "8px 12px",
borderRadius: 6,
background: matches ? pColor + "0a" : "transparent",
border: "1px solid " + (matches ? pColor + "25" : "rgba(255,255,255,0.03)"),
transition: "all 0.25s ease-out",
}
},
h("div", {
style: {
width: 8, height: 8, borderRadius: 2,
background: matches ? pColor : "rgba(255,255,255,0.06)",
boxShadow: matches ? "0 0 8px " + pColor + "40" : "none",
transition: "all 0.25s ease-out", flexShrink: 0,
}
}),
h("span", {
style: {
fontFamily: FONTS.mono, fontSize: 13,
color: matches ? COLORS.text : COLORS.textDim,
transition: "color 0.25s",
}
}, sub),
matches ? h("span", {
style: { marginLeft: "auto", fontFamily: FONTS.mono, fontSize: 10, color: pColor, opacity: 0.7 }
}, "✓ match") : null
);
});
var patternBtns = patterns.map(function(p, i) {
var isActive = activePattern === i;
return h("button", {
key: p.pattern,
onClick: function() { setActivePattern(i); },
style: {
display: "flex", flexDirection: "column", alignItems: "flex-start",
padding: "10px 14px", borderRadius: 8, cursor: "pointer",
background: isActive ? p.color + "0c" : "rgba(255,255,255,0.02)",
border: "1px solid " + (isActive ? p.color + "40" : "rgba(255,255,255,0.04)"),
transition: "all 0.2s ease-out", textAlign: "left",
}
},
h("span", { style: { fontFamily: FONTS.mono, fontSize: 13, fontWeight: 600, color: isActive ? p.color : COLORS.textMuted } }, p.pattern),
h("span", { style: { fontFamily: FONTS.body, fontSize: 11, color: COLORS.textDim, marginTop: 2 } }, p.desc)
);
});
return h("div", null,
h("div", { style: { marginBottom: 20 } },
h("h3", { style: { fontFamily: FONTS.body, fontSize: 18, fontWeight: 400, color: COLORS.text, margin: "0 0 6px" } }, "Subject wildcards"),
h("p", { style: { fontFamily: FONTS.body, fontSize: 14, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "NATS subjects are dot-separated hierarchies. Wildcards let subscribers match broad or narrow slices of traffic.")
),
h("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginBottom: 20 } }, patternBtns),
h("div", { style: { display: "flex", flexDirection: "column", gap: 4 } }, subjectRows),
h(InsightCard, { style: { marginTop: 16 } },
h("span", { style: { color: COLORS.nats, fontWeight: 600 } }, "sub " + currentPattern),
" → " + matchCount + " of " + allSubjects.length + " subjects. ",
h("span", { style: { color: COLORS.wildcard } }, "*"),
" matches exactly one token. ",
h("span", { style: { color: COLORS.nats } }, ">"),
" matches one or more tokens at the tail. A monitoring service subscribing to ",
h("span", { style: { color: COLORS.danger } }, ">"),
" sees every message in the system."
)
);
}
// ═══════════════════════════════════════════════════════════
// PANEL 4: Delivery Patterns
// ═══════════════════════════════════════════════════════════
function DeliveryPanel() {
var ref = useState(0), activeMode = ref[0], setActiveMode = ref[1];
var modes = [
{
id: "pubsub",
label: "Pub/Sub",
color: COLORS.nats,
steps: [
{ label: "Publisher sends to subject", detail: 'NATS.publish("orders.created", orderData)', side: "left", color: COLORS.pub },
{ label: "NATS matches subscribers", detail: "Subject lookup → 3 active subscriptions found", side: "center", color: COLORS.nats },
{ label: "All subscribers receive the message", detail: "Fan-out: Email, Analytics, and Audit each get a copy", side: "right", color: COLORS.sub },
],
insight: "Core pub/sub is at-most-once delivery. If a subscriber is offline, the message is gone. This keeps the server stateless and fast."
},
{
id: "reqreply",
label: "Request / Reply",
color: COLORS.pub,
steps: [
{ label: "Client publishes request with reply inbox", detail: 'NATS.request("api.users.get", {id: 42}) → reply: _INBOX.x9k3', side: "left", color: COLORS.pub },
{ label: "NATS routes to a handler", detail: "Subject api.users.get → UserService subscriber", side: "center", color: COLORS.nats },
{ label: "Handler processes and responds", detail: "UserService queries DB, publishes result to _INBOX.x9k3", side: "right", color: COLORS.sub },
{ label: "Response routes back to caller", detail: 'Client receives: {name: "Alice", role: "admin"}', side: "left", color: COLORS.pub },
],
insight: "Looks like a function call, but the caller doesn't know which service handles it. Swap implementations without changing a line of client code."
},
{
id: "queue",
label: "Queue Groups",
color: COLORS.queue,
steps: [
{ label: "3 worker instances subscribe", detail: 'sub("orders.process", queue: "workers") × 3 instances', side: "right", color: COLORS.sub },
{ label: "Publisher sends a message", detail: 'NATS.publish("orders.process", order)', side: "left", color: COLORS.pub },
{ label: "NATS delivers to exactly one worker", detail: "Round-robin within queue group → Worker 2 receives it", side: "center", color: COLORS.nats },
],
insight: "Queue groups give you load balancing without a load balancer. Scale by adding instances. NATS distributes messages across the group automatically."
},
{
id: "jetstream",
label: "JetStream",
color: COLORS.jet,
steps: [
{ label: "Stream captures messages by subject", detail: 'Stream "ORDERS" stores all orders.> messages to disk', side: "center", color: COLORS.jet },
{ label: "Publisher sends as normal", detail: 'NATS.publish("orders.created", order) → stored in stream', side: "left", color: COLORS.pub },
{ label: "Consumer reads at its own pace", detail: "Consumer pulls messages, ACKs after processing", side: "right", color: COLORS.sub },
{ label: "Replay and persistence", detail: "New consumer can replay from the start. No data loss.", side: "center", color: COLORS.nats },
],
insight: "JetStream adds persistence on top of the same subjects and pub/sub model. At-least-once and exactly-once delivery, message replay, and durable consumers."
},
];
var mode = modes[activeMode];
var ref2 = useState(-1), activeStep = ref2[0], setActiveStep = ref2[1];
var ref3 = useState(false), isPlaying = ref3[0], setIsPlaying = ref3[1];
var timerRef = useRef(null);
var play = useCallback(function() {
setActiveStep(-1);
setIsPlaying(true);
var step = 0;
function advance() {
if (step >= mode.steps.length) { setIsPlaying(false); return; }
setActiveStep(step);
step++;
timerRef.current = setTimeout(advance, 1400);
}
timerRef.current = setTimeout(advance, 400);
}, [mode]);
useEffect(function() {
play();
return function() { clearTimeout(timerRef.current); };
}, [activeMode]);
function stepToClick(i) {
clearTimeout(timerRef.current);
setIsPlaying(false);
setActiveStep(i);
}
var modeTabs = modes.map(function(m, i) {
var isActive = activeMode === i;
return h("button", {
key: m.id,
onClick: function() { setActiveMode(i); },
style: {
flex: 1, padding: "8px 6px", fontSize: 11, borderRadius: 6, cursor: "pointer",
fontFamily: FONTS.mono,
background: isActive ? m.color + "12" : "rgba(255,255,255,0.02)",
border: "1px solid " + (isActive ? m.color + "30" : "rgba(255,255,255,0.04)"),
color: isActive ? m.color : COLORS.textDim,
transition: "all 0.2s", whiteSpace: "nowrap",
}
}, m.label);
});
var laneLabels = h("div", {
style: { display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 8, marginBottom: 12, paddingBottom: 12, borderBottom: "1px solid rgba(255,255,255,0.04)" }
},
h("div", { style: { fontFamily: FONTS.mono, fontSize: 10, color: COLORS.pub, textTransform: "uppercase", letterSpacing: "0.1em" } }, "● Publisher"),
h("div", { style: { fontFamily: FONTS.mono, fontSize: 10, color: COLORS.nats, textTransform: "uppercase", letterSpacing: "0.1em", textAlign: "center" } }, "● NATS"),
h("div", { style: { fontFamily: FONTS.mono, fontSize: 10, color: COLORS.sub, textTransform: "uppercase", letterSpacing: "0.1em", textAlign: "right" } }, "● Subscriber")
);
var steps = mode.steps.map(function(step, i) {
var isActive = i <= activeStep;
var isCurrent = i === activeStep;
var alignment = step.side === "left" ? "flex-start" : step.side === "right" ? "flex-end" : "center";
return h("div", {
key: i,
onClick: function() { stepToClick(i); },
style: { display: "flex", flexDirection: "column", alignItems: alignment, cursor: "pointer", opacity: isActive ? 1 : 0.2, transition: "opacity 0.4s ease-out" }
},
h("div", {
style: {
background: isCurrent ? step.color + "12" : "rgba(255,255,255,0.02)",
border: "1px solid " + (isCurrent ? step.color + "40" : isActive ? "rgba(255,255,255,0.04)" : "transparent"),
borderRadius: 8, padding: "10px 14px", maxWidth: "80%", transition: "all 0.3s ease-out"
}
},
h("div", { style: { fontFamily: FONTS.body, fontSize: 13, color: isActive ? step.color : COLORS.textDim, fontWeight: 500, marginBottom: 4, transition: "color 0.3s" } },
h("span", { style: { fontFamily: FONTS.mono, fontSize: 10, marginRight: 8, opacity: 0.5 } }, i + 1),
step.label
),
h("div", { style: { fontFamily: FONTS.mono, fontSize: 11, color: COLORS.textDim, lineHeight: 1.5, wordBreak: "break-word" } }, step.detail)
)
);
});
return h("div", null,
h("div", { style: { marginBottom: 20, display: "flex", alignItems: "flex-start", justifyContent: "space-between" } },
h("div", null,
h("h3", { style: { fontFamily: FONTS.body, fontSize: 18, fontWeight: 400, color: COLORS.text, margin: "0 0 6px" } }, "Delivery patterns"),
h("p", { style: { fontFamily: FONTS.body, fontSize: 14, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "NATS supports multiple messaging patterns on the same infrastructure.")
),
h("button", {
onClick: play,
style: {
fontFamily: FONTS.mono, fontSize: 11, padding: "6px 14px", borderRadius: 6,
background: isPlaying ? mode.color + "15" : "rgba(255,255,255,0.03)",
border: "1px solid " + (isPlaying ? mode.color + "40" : "rgba(255,255,255,0.04)"),
color: isPlaying ? mode.color : COLORS.textMuted,
cursor: "pointer", transition: "all 0.2s", whiteSpace: "nowrap", flexShrink: 0, marginLeft: 16
}
}, isPlaying ? "playing..." : "▶ replay")
),
h("div", { style: { display: "flex", gap: 4, marginBottom: 16 } }, modeTabs),
laneLabels,
h("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, steps),
h(InsightCard, { style: { marginTop: 16 } }, mode.insight)
);
}
// ═══════════════════════════════════════════════════════════
// MAIN
// ═══════════════════════════════════════════════════════════
var PANELS = [
{ id: "problem", label: "The Problem", Component: ProblemPanel },
{ id: "pubsub", label: "Pub/Sub", Component: PubSubPanel },
{ id: "wildcards", label: "Wildcards", Component: WildcardPanel },
{ id: "delivery", label: "Delivery", Component: DeliveryPanel },
];
function NATSVisual() {
var ref = useState(0), activePanel = ref[0], setActivePanel = ref[1];
var tabs = PANELS.map(function(p, i) {
return h("button", {
key: p.id,
onClick: function() { setActivePanel(i); },
style: {
flex: 1, fontFamily: FONTS.mono, fontSize: 12,
fontWeight: activePanel === i ? 500 : 400,
color: activePanel === i ? COLORS.text : COLORS.textDim,
background: activePanel === i ? "rgba(255,255,255,0.05)" : "transparent",
border: "none", borderRadius: 7, padding: "10px 8px",
cursor: "pointer", transition: "all 0.2s ease-out", letterSpacing: "0.01em"
}
},
h("span", { style: { display: "inline-block", width: 16, fontFamily: FONTS.mono, fontSize: 10, opacity: 0.4, marginRight: 4 } }, i + 1),
p.label
);
});
var ActiveComponent = PANELS[activePanel].Component;
return h("div", {
style: {
background: COLORS.bg, color: COLORS.text, fontFamily: FONTS.body,
minHeight: "100vh", display: "flex", flexDirection: "column", alignItems: "center", padding: "40px 16px"
}
},
// Header
h("div", { style: { maxWidth: 640, width: "100%", marginBottom: 32 } },
h("div", { style: { fontFamily: FONTS.mono, fontSize: 11, color: COLORS.nats, textTransform: "uppercase", letterSpacing: "0.12em", marginBottom: 8 } }, "Interactive Explainer"),
h("h1", { style: { fontFamily: FONTS.body, fontSize: 28, fontWeight: 300, color: COLORS.text, margin: "0 0 8px", lineHeight: 1.3, letterSpacing: "-0.01em" } }, "How NATS Decouples Your Services"),
h("p", { style: { fontFamily: FONTS.body, fontSize: 15, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "A messaging system that replaces point-to-point integrations with subject-based routing.")
),
// Tab bar
h("div", { style: { maxWidth: 640, width: "100%", display: "flex", gap: 4, marginBottom: 24, background: "rgba(255,255,255,0.015)", borderRadius: 10, padding: 4, border: "1px solid rgba(255,255,255,0.04)" } }, tabs),
// Active panel
h("div", { style: { maxWidth: 640, width: "100%", background: "rgba(255,255,255,0.015)", border: "1px solid rgba(255,255,255,0.04)", borderRadius: 12, padding: "28px 24px", minHeight: 420 } },
h(ActiveComponent, { key: activePanel })
),
// Nav arrows
h("div", { style: { maxWidth: 640, width: "100%", display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 16 } },
h("button", {
onClick: function() { setActivePanel(Math.max(0, activePanel - 1)); },
disabled: activePanel === 0,
style: { fontFamily: FONTS.mono, fontSize: 12, color: activePanel === 0 ? COLORS.textDim : COLORS.textMuted, background: "none", border: "1px solid " + (activePanel === 0 ? "transparent" : "rgba(255,255,255,0.04)"), borderRadius: 6, padding: "8px 16px", cursor: activePanel === 0 ? "default" : "pointer", transition: "all 0.2s" }
}, "← prev"),
h("span", { style: { fontFamily: FONTS.mono, fontSize: 11, color: COLORS.textDim } }, (activePanel + 1) + " / " + PANELS.length),
h("button", {
onClick: function() { setActivePanel(Math.min(PANELS.length - 1, activePanel + 1)); },
disabled: activePanel === PANELS.length - 1,
style: { fontFamily: FONTS.mono, fontSize: 12, color: activePanel === PANELS.length - 1 ? COLORS.textDim : COLORS.textMuted, background: "none", border: "1px solid " + (activePanel === PANELS.length - 1 ? "transparent" : "rgba(255,255,255,0.04)"), borderRadius: 6, padding: "8px 16px", cursor: activePanel === PANELS.length - 1 ? "default" : "pointer", transition: "all 0.2s" }
}, "next →")
)
);
}
ReactDOM.render(h(NATSVisual), document.getElementById("root"));
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment