Created
February 20, 2026 02:05
-
-
Save kbouw/eb3c1d138d95ab0dbf290105c6dfdf8d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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