Last active
February 16, 2026 04:41
-
-
Save kbouw/449a91b6e3bc06e656c5a5b6e501a8a7 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 MCP Connects AI to Everything</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; | |
| // ─── Distillation-style 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", | |
| client: "#8cb4ff", | |
| clientGlow: "rgba(140,180,255,0.15)", | |
| server: "#f0c674", | |
| serverGlow: "rgba(240,198,116,0.15)", | |
| mcp: "#4ade80", | |
| mcpGlow: "rgba(74,222,128,0.2)", | |
| connector: "#3a3e46", | |
| connectorActive: "#8a8f98", | |
| danger: "#f07474", | |
| dangerGlow: "rgba(240,116,116,0.1)", | |
| tool: "#a78bfa", | |
| resource: "#4ade80", | |
| prompt: "#f0c674", | |
| }; | |
| var FONTS = { | |
| body: "'IBM Plex Sans', -apple-system, sans-serif", | |
| mono: "'JetBrains Mono', monospace", | |
| }; | |
| var CLIENTS = ["Claude", "ChatGPT", "Cursor", "Your Agent"]; | |
| var SERVERS = ["GitHub", "Postgres", "Slack", "Filesystem", "Web Search"]; | |
| var PRIMITIVES = [ | |
| { name: "Tools", color: COLORS.tool, desc: "Functions the AI can call", examples: ["run_query", "create_issue", "send_message"] }, | |
| { name: "Resources", color: COLORS.resource, desc: "Data the AI can read", examples: ["file_contents", "db_records", "api_response"] }, | |
| { name: "Prompts", color: COLORS.prompt, desc: "Reusable interaction templates", examples: ["summarize_code", "review_pr", "explain_error"] }, | |
| ]; | |
| var FLOW_STEPS = [ | |
| { label: "You ask a question", detail: '"What issues are assigned to me on GitHub?"', side: "left", color: COLORS.text }, | |
| { label: "AI discovers available tools", detail: "tools/list \u2192 [get_issues, create_issue, ...]", side: "center", color: COLORS.mcp }, | |
| { label: "AI sends a JSON-RPC request", detail: '{"method": "tools/call", "params": {"name": "get_issues", "arguments": {"assignee": "me"}}}', side: "right", color: COLORS.server }, | |
| { label: "Server executes the operation", detail: "GitHub API \u2192 fetches assigned issues", side: "right", color: COLORS.server }, | |
| { label: "Server returns structured content", detail: '{"content": [{"type": "text", "text": "3 open issues: ..."}]}', side: "center", color: COLORS.mcp }, | |
| { label: "AI incorporates the result", detail: "You have 3 open issues. The highest priority is...", side: "left", color: COLORS.client }, | |
| ]; | |
| 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 N×M 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 clientY(i) { return 40 + i * 68; } | |
| function serverY(i) { return 18 + i * 56; } | |
| var connectorCount = CLIENTS.length * SERVERS.length; | |
| var lines = []; | |
| CLIENTS.forEach(function(c, ci) { | |
| SERVERS.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: clientY(ci) + 14, | |
| x2: 440, y2: serverY(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 clientNodes = CLIENTS.map(function(c, i) { | |
| return h("g", { key: "c-" + i, style: { cursor: "default" } }, | |
| h("rect", { | |
| x: 12, y: clientY(i), width: 140, height: 28, rx: 6, | |
| fill: COLORS.clientGlow, stroke: COLORS.client + "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: clientY(i) + 14, r: 4, fill: COLORS.client, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" } }), | |
| h("text", { x: 40, y: clientY(i) + 18, fontFamily: FONTS.mono, fontSize: 12, fill: COLORS.client, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" } }, c) | |
| ); | |
| }); | |
| var serverNodes = SERVERS.map(function(s, i) { | |
| return h("g", { key: "s-" + i }, | |
| h("rect", { | |
| x: 448, y: serverY(i), width: 140, height: 28, rx: 6, | |
| fill: COLORS.serverGlow, stroke: COLORS.server + "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: serverY(i) + 14, r: 4, fill: COLORS.server, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.08) + "s" } }), | |
| h("text", { x: 476, y: serverY(i) + 18, fontFamily: FONTS.mono, fontSize: 12, fill: COLORS.server, 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 standard protocol"), | |
| h("p", { style: { fontFamily: FONTS.body, fontSize: 14, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "Every AI application needs a custom connector for every external service.") | |
| ), | |
| h("div", { style: { position: "relative", width: "100%", minHeight: 300 } }, | |
| h("svg", { width: "100%", height: 300, viewBox: "0 0 600 300", style: { overflow: "visible" } }, | |
| lines, clientNodes, serverNodes | |
| ) | |
| ), | |
| h(InsightCard, { style: { marginTop: 12 } }, | |
| h("span", { style: { color: COLORS.danger, fontWeight: 600 } }, connectorCount + " custom connectors"), | |
| " needed for " + CLIENTS.length + " clients \u00d7 " + SERVERS.length + " servers. Add one more service, and every client needs another integration." | |
| ) | |
| ); | |
| } | |
| // ─── Panel 2: MCP as Standard Layer ─────────────────────────── | |
| function SolutionPanel() { | |
| var ref = useState(false), animate = ref[0], setAnimate = ref[1]; | |
| useEffect(function() { | |
| var t = setTimeout(function() { setAnimate(true); }, 200); | |
| return function() { clearTimeout(t); }; | |
| }, []); | |
| function clientY(i) { return 40 + i * 68; } | |
| function serverY(i) { return 18 + i * 56; } | |
| var mcpY = 135; | |
| var clientLines = CLIENTS.map(function(_, ci) { | |
| return h("line", { | |
| key: "cl-" + ci, | |
| x1: 152, y1: clientY(ci) + 14, | |
| x2: 260, y2: mcpY + 14, | |
| stroke: COLORS.client, strokeWidth: 1.5, | |
| opacity: animate ? 0.5 : 0, | |
| style: { transition: "opacity 0.4s ease-out " + (0.3 + ci * 0.06) + "s" } | |
| }); | |
| }); | |
| var serverLines = SERVERS.map(function(_, si) { | |
| return h("line", { | |
| key: "sl-" + si, | |
| x1: 340, y1: mcpY + 14, | |
| x2: 448, y2: serverY(si) + 14, | |
| stroke: COLORS.server, strokeWidth: 1.5, | |
| opacity: animate ? 0.5 : 0, | |
| style: { transition: "opacity 0.4s ease-out " + (0.3 + si * 0.06) + "s" } | |
| }); | |
| }); | |
| var clientNodes = CLIENTS.map(function(c, i) { | |
| return h("g", { key: "c-" + i }, | |
| h("rect", { x: 12, y: clientY(i), width: 140, height: 28, rx: 6, fill: COLORS.clientGlow, stroke: COLORS.client + "40", strokeWidth: 1, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }), | |
| h("circle", { cx: 28, cy: clientY(i) + 14, r: 4, fill: COLORS.client, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }), | |
| h("text", { x: 40, y: clientY(i) + 18, fontFamily: FONTS.mono, fontSize: 12, fill: COLORS.client, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }, c) | |
| ); | |
| }); | |
| var serverNodes = SERVERS.map(function(s, i) { | |
| return h("g", { key: "s-" + i }, | |
| h("rect", { x: 448, y: serverY(i), width: 140, height: 28, rx: 6, fill: COLORS.serverGlow, stroke: COLORS.server + "40", strokeWidth: 1, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }), | |
| h("circle", { cx: 464, cy: serverY(i) + 14, r: 4, fill: COLORS.server, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "s" } }), | |
| h("text", { x: 476, y: serverY(i) + 18, fontFamily: FONTS.mono, fontSize: 12, fill: COLORS.server, opacity: animate ? 1 : 0, style: { transition: "opacity 0.4s ease-out " + (i * 0.06) + "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" } }, "With MCP"), | |
| h("p", { style: { fontFamily: FONTS.body, fontSize: 14, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "Each side implements the protocol once. Any client talks to any server.") | |
| ), | |
| h("div", { style: { position: "relative", width: "100%", minHeight: 300 } }, | |
| h("svg", { width: "100%", height: 300, viewBox: "0 0 600 300", style: { overflow: "visible" } }, | |
| h("rect", { x: 260, y: 20, width: 80, height: 260, rx: 8, fill: COLORS.mcpGlow, stroke: COLORS.mcp + "30", strokeWidth: 1, opacity: animate ? 1 : 0, style: { transition: "opacity 0.5s ease-out 0.1s" } }), | |
| h("text", { x: 300, y: 155, fontFamily: FONTS.mono, fontSize: 13, fontWeight: 600, fill: COLORS.mcp, textAnchor: "middle", opacity: animate ? 1 : 0, style: { transition: "opacity 0.5s ease-out 0.3s" } }, "MCP"), | |
| clientLines, serverLines, clientNodes, serverNodes | |
| ) | |
| ), | |
| h(InsightCard, { style: { marginTop: 12 } }, | |
| h("span", { style: { color: COLORS.mcp, fontWeight: 600 } }, (CLIENTS.length + SERVERS.length) + " implementations"), | |
| " instead of " + (CLIENTS.length * SERVERS.length) + ". Each side builds to the protocol once." | |
| ) | |
| ); | |
| } | |
| // ─── Panel 3: The Three Primitives ───────────────────────────── | |
| function PrimitivesPanel() { | |
| var ref = useState(null), expanded = ref[0], setExpanded = ref[1]; | |
| var items = PRIMITIVES.map(function(p, i) { | |
| var isExpanded = expanded === i; | |
| var exampleTags = null; | |
| if (isExpanded) { | |
| var tags = [h("span", { key: "label", style: { fontFamily: FONTS.mono, fontSize: 11, color: COLORS.textDim, marginRight: 4, alignSelf: "center" } }, p.name.toLowerCase() + "/list \u2192")]; | |
| p.examples.forEach(function(ex) { | |
| tags.push(h("span", { key: ex, style: { fontFamily: FONTS.mono, fontSize: 11, padding: "4px 10px", borderRadius: 4, background: p.color + "12", color: p.color, border: "1px solid " + p.color + "25" } }, ex)); | |
| }); | |
| exampleTags = h("div", { style: { marginTop: 14, paddingTop: 14, borderTop: "1px solid rgba(255,255,255,0.04)", display: "flex", gap: 8, flexWrap: "wrap" } }, tags); | |
| } | |
| return h("div", { | |
| key: p.name, | |
| onClick: function() { setExpanded(isExpanded ? null : i); }, | |
| style: { | |
| background: isExpanded ? p.color + "08" : "rgba(255,255,255,0.02)", | |
| border: "1px solid " + (isExpanded ? p.color + "40" : "rgba(255,255,255,0.04)"), | |
| borderRadius: 10, padding: "16px 20px", cursor: "pointer", | |
| transition: "all 0.25s ease-out" | |
| } | |
| }, | |
| h("div", { style: { display: "flex", alignItems: "center", gap: 12 } }, | |
| h("span", { style: { width: 10, height: 10, borderRadius: 3, background: p.color, flexShrink: 0 } }), | |
| h("span", { style: { fontFamily: FONTS.mono, fontSize: 14, fontWeight: 600, color: p.color, flex: 1 } }, p.name), | |
| h("span", { style: { fontFamily: FONTS.body, fontSize: 13, color: COLORS.textMuted } }, p.desc), | |
| h("span", { style: { fontFamily: FONTS.mono, fontSize: 11, color: COLORS.textDim, transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)", transition: "transform 0.2s ease-out" } }, "\u25b8") | |
| ), | |
| exampleTags | |
| ); | |
| }); | |
| 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" } }, "What a server exposes"), | |
| h("p", { style: { fontFamily: FONTS.body, fontSize: 14, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "Every MCP server offers up to three types of capability. A client discovers them via list methods.") | |
| ), | |
| h("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, items), | |
| h(InsightCard, { style: { marginTop: 16 } }, | |
| "A client calls ", | |
| h("span", { style: { color: COLORS.mcp, fontWeight: 600 } }, "tools/list"), | |
| ", ", | |
| h("span", { style: { color: COLORS.mcp, fontWeight: 600 } }, "resources/list"), | |
| ", or ", | |
| h("span", { style: { color: COLORS.mcp, fontWeight: 600 } }, "prompts/list"), | |
| " to discover what a server offers, then invokes them by name. Tap each primitive to see examples." | |
| ) | |
| ); | |
| } | |
| // ─── Panel 4: Protocol Flow ──────────────────────────────────── | |
| function FlowPanel() { | |
| var ref1 = useState(-1), activeStep = ref1[0], setActiveStep = ref1[1]; | |
| var ref2 = useState(false), isPlaying = ref2[0], setIsPlaying = ref2[1]; | |
| var timerRef = useRef(null); | |
| var play = useCallback(function() { | |
| setActiveStep(-1); | |
| setIsPlaying(true); | |
| var step = 0; | |
| function advance() { | |
| if (step >= FLOW_STEPS.length) { setIsPlaying(false); return; } | |
| setActiveStep(step); | |
| step++; | |
| timerRef.current = setTimeout(advance, 1200); | |
| } | |
| timerRef.current = setTimeout(advance, 400); | |
| }, []); | |
| useEffect(function() { | |
| play(); | |
| return function() { clearTimeout(timerRef.current); }; | |
| }, []); | |
| function stepToClick(i) { | |
| clearTimeout(timerRef.current); | |
| setIsPlaying(false); | |
| setActiveStep(i); | |
| } | |
| 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.client, textTransform: "uppercase", letterSpacing: "0.1em" } }, "\u25cf Client (AI)"), | |
| h("div", { style: { fontFamily: FONTS.mono, fontSize: 10, color: COLORS.mcp, textTransform: "uppercase", letterSpacing: "0.1em", textAlign: "center" } }, "\u25cf Protocol"), | |
| h("div", { style: { fontFamily: FONTS.mono, fontSize: 10, color: COLORS.server, textTransform: "uppercase", letterSpacing: "0.1em", textAlign: "right" } }, "\u25cf Server") | |
| ); | |
| var steps = FLOW_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" } }, "A request in motion"), | |
| h("p", { style: { fontFamily: FONTS.body, fontSize: 14, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "One question, from you through the protocol and back.") | |
| ), | |
| h("button", { | |
| onClick: play, | |
| style: { | |
| fontFamily: FONTS.mono, fontSize: 11, padding: "6px 14px", borderRadius: 6, | |
| background: isPlaying ? COLORS.mcp + "15" : "rgba(255,255,255,0.03)", | |
| border: "1px solid " + (isPlaying ? COLORS.mcp + "40" : "rgba(255,255,255,0.04)"), | |
| color: isPlaying ? COLORS.mcp : COLORS.textMuted, | |
| cursor: "pointer", transition: "all 0.2s", whiteSpace: "nowrap", flexShrink: 0, marginLeft: 16 | |
| } | |
| }, isPlaying ? "playing..." : "\u25b6 replay") | |
| ), | |
| laneLabels, | |
| h("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, steps), | |
| h(InsightCard, { style: { marginTop: 16 } }, "The same six-step pattern works for every client-server pair. The wire format is always JSON-RPC 2.0. Click any step to inspect it, or hit replay to watch the sequence again.") | |
| ); | |
| } | |
| // ─── Main Component ──────────────────────────────────────────── | |
| var PANELS = [ | |
| { id: "problem", label: "The Problem", Component: ProblemPanel }, | |
| { id: "solution", label: "The Protocol", Component: SolutionPanel }, | |
| { id: "primitives", label: "Primitives", Component: PrimitivesPanel }, | |
| { id: "flow", label: "Request Flow", Component: FlowPanel }, | |
| ]; | |
| function MCPVisual() { | |
| 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 — left-aligned with label, title, subtext | |
| h("div", { style: { maxWidth: 640, width: "100%", marginBottom: 32 } }, | |
| h("div", { style: { fontFamily: FONTS.mono, fontSize: 11, color: COLORS.mcp, 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 MCP Connects AI to Everything"), | |
| h("p", { style: { fontFamily: FONTS.body, fontSize: 15, color: COLORS.textMuted, margin: 0, lineHeight: 1.5 } }, "The Model Context Protocol replaces custom integrations with a single standard.") | |
| ), | |
| // 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" } | |
| }, "\u2190 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 \u2192") | |
| ) | |
| ); | |
| } | |
| ReactDOM.render(h(MCPVisual), document.getElementById("root")); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment