Skip to content

Instantly share code, notes, and snippets.

@kbouw
Created February 21, 2026 03:24
Show Gist options
  • Select an option

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

Select an option

Save kbouw/ffc4d794ba2d5bf8ea9028e932a142e8 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>When Your Containers Outgrow One Server</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;
var C = {
bg:"#111216",surface:"rgba(255,255,255,0.015)",border:"rgba(255,255,255,0.04)",
text:"#e8e6e3",textMuted:"#6a6e76",textDim:"#4a4e56",
accent:"#4ade80",accentGlow:"rgba(74,222,128,0.2)",
blue:"#8cb4ff",blueGlow:"rgba(140,180,255,0.15)",
yellow:"#f0c674",yellowGlow:"rgba(240,198,116,0.15)",
red:"#f07474",redGlow:"rgba(240,116,116,0.1)",
purple:"#a78bfa",purpleGlow:"rgba(167,139,250,0.15)",
cyan:"#67e8f9",cyanGlow:"rgba(103,232,249,0.15)",
orange:"#fb923c",orangeGlow:"rgba(251,146,60,0.15)"
};
var F = {body:"'IBM Plex Sans', -apple-system, sans-serif",mono:"'JetBrains Mono', monospace"};
function Insight(props) {
return h("div",{style:Object.assign({background:"rgba(255,255,255,0.02)",border:"1px solid "+C.border,borderRadius:8,padding:"12px 16px",fontFamily:F.mono,fontSize:12,color:C.textMuted,lineHeight:1.6},props.style||{})},props.children);
}
// ═══════════════════════════════════════════════════════════════
// PANEL 1: WHAT BREAKS AT SCALE
// ═══════════════════════════════════════════════════════════════
var SCALE_STAGES = [
{n:1,label:"Side project",containers:2,problems:[],approach:"Run it on one machine. Docker Compose is perfect. You don't need orchestration.",pain:0,color:C.accent},
{n:2,label:"Real product",containers:12,problems:["A deploy takes down all traffic for 30 seconds","You can't push an update without downtime"],approach:"Still manageable. You might add a load balancer and a second server. Blue-green deploys help with downtime.",pain:1,color:C.accent},
{n:3,label:"Growing team",containers:40,problems:["Server 3 ran out of memory at 2 AM and nobody noticed for an hour","Two teams shipped conflicting port configurations","Recovery from last Friday's outage took 4 hours of manual SSH"],approach:"You start writing scripts to automate placement. You build a health-check bot. You're spending more time on infrastructure than product.",pain:2,color:C.yellow},
{n:4,label:"Scaling up",containers:200,problems:["Black Friday traffic spike needs 5\u00d7 capacity in 10 minutes","A misconfigured deploy wiped the config on 12 servers","Three engineers spend all week managing infrastructure"],approach:"Your homegrown scripts break in new ways every month. You need self-healing, auto-scaling, rolling deploys, and service discovery as a foundation, not features you build yourself.",pain:3,color:C.red}
];
function ScalePanel() {
var ref = useState(0), stage = ref[0], setStage = ref[1];
var s = SCALE_STAGES[stage];
var cols = Math.ceil(Math.sqrt(s.containers * 2));
var dots = [];
for (var i = 0; i < s.containers; i++) {
var x = (i % cols) * 14 + 8;
var y = Math.floor(i / cols) * 14 + 8;
dots.push(h("rect",{key:i,x:x,y:y,width:10,height:10,rx:2,fill:s.color+"50",stroke:s.color+"30",strokeWidth:0.5,style:{opacity:1,transition:"opacity 0.3s "+i*0.01+"s"}}));
}
var svgH = Math.floor(s.containers / cols) * 14 + 24;
var stageButtons = SCALE_STAGES.map(function(_,i) {
return h("button",{key:i,onClick:function(){setStage(i);},style:{
flex:1,height:32,borderRadius:6,cursor:"pointer",border:"none",transition:"all 0.25s",
background:i<=stage?SCALE_STAGES[i].color+"20":"rgba(255,255,255,0.02)",
borderBottom:i===stage?"2px solid "+SCALE_STAGES[i].color:"2px solid transparent"
}},h("div",{style:{fontFamily:F.mono,fontSize:10,color:i<=stage?SCALE_STAGES[i].color:C.textDim}},SCALE_STAGES[i].containers));
});
var problemEls = s.problems.map(function(p,i) {
return h("div",{key:i,style:{
padding:"10px 14px",borderRadius:6,
background:s.pain>=3?C.redGlow:s.pain>=2?C.yellowGlow:"rgba(255,255,255,0.02)",
border:"1px solid "+(s.pain>=3?C.red+"20":s.pain>=2?C.yellow+"20":C.border),
borderLeft:"3px solid "+(s.pain>=3?C.red+"60":s.pain>=2?C.yellow+"60":C.textDim+"40")
}},h("div",{style:{fontFamily:F.mono,fontSize:12,color:s.pain>=3?C.red:s.pain>=2?C.yellow:C.textMuted,lineHeight:1.5}},p));
});
var insightText;
if (stage < 2) insightText = [
"At this scale, simpler tools work fine. ",
h("span",{key:"a",style:{color:C.accent,fontWeight:600}},"Not everything needs Kubernetes."),
" The goal is to understand where the breaking point is."
];
else if (stage < 3) insightText = "This is the gray zone. You could keep scripting your way through it, but every new incident is a custom problem. The question becomes: build your own platform, or adopt one?";
else insightText = "At this point you need automated placement, self-healing, rolling deploys, service discovery, config management, and autoscaling. You can build each of these yourself, or use a system that provides all of them as a coherent whole.";
return h("div",null,
h("div",{style:{marginBottom:20}},
h("h3",{style:{fontFamily:F.body,fontSize:18,fontWeight:400,color:C.text,margin:"0 0 6px"}},"Where the pain starts"),
h("p",{style:{fontFamily:F.body,fontSize:14,color:C.textMuted,margin:0,lineHeight:1.5}},"Most systems don't start with Kubernetes. They grow into needing it. Drag the slider to scale up.")
),
h("div",{style:{marginBottom:20}},
h("div",{style:{display:"flex",justifyContent:"space-between",marginBottom:8}},
h("span",{style:{fontSize:12,color:C.textMuted,fontFamily:F.mono}},s.label),
h("span",{style:{fontSize:12,color:s.color,fontFamily:F.mono}},s.containers+" containers")
),
h("div",{style:{display:"flex",gap:4}},stageButtons),
h("div",{style:{display:"flex",justifyContent:"space-between",marginTop:4}},
h("span",{style:{fontSize:9,color:C.textDim,fontFamily:F.mono}},"laptop"),
h("span",{style:{fontSize:9,color:C.textDim,fontFamily:F.mono}},"production fleet")
)
),
h("div",{style:{background:"rgba(0,0,0,0.2)",borderRadius:8,padding:12,border:"1px solid "+C.border,marginBottom:16}},
h("div",{style:{fontFamily:F.mono,fontSize:10,color:C.textDim,marginBottom:8,textTransform:"uppercase",letterSpacing:"0.08em"}},"your containers"),
h("svg",{width:"100%",height:Math.max(svgH,30),viewBox:"0 0 "+(cols*14+8)+" "+Math.max(svgH,30),style:{display:"block"}},dots)
),
h("div",{style:{padding:"14px 16px",background:C.surface,borderRadius:8,border:"1px solid "+C.border,marginBottom:12}},
h("div",{style:{fontFamily:F.body,fontSize:13,color:C.textMuted,lineHeight:1.6}},s.approach)
),
s.problems.length > 0 ? h("div",{style:{display:"flex",flexDirection:"column",gap:6}},problemEls) : null,
h(Insight,{style:{marginTop:16}},insightText)
);
}
// ═══════════════════════════════════════════════════════════════
// PANEL 2: ALTERNATIVES
// ═══════════════════════════════════════════════════════════════
var ALTS = [
{name:"Docker Compose",color:C.blue,sub:null,sweet:"1\u201310 containers, single host, dev environments",
gives:["Simple YAML config","One-command startup","Zero learning curve","Great for local dev"],
lacks:["Single machine only","No self-healing","No autoscaling","No rolling deploys"],
verdict:"Perfect when everything fits on one box. Falls apart the moment it doesn't."},
{name:"Managed PaaS",sub:"Heroku, Render, Railway",color:C.purple,sweet:"Small teams, web apps, rapid prototyping",
gives:["Zero infrastructure work","Push-to-deploy","Managed databases","Built-in SSL/DNS"],
lacks:["Expensive at scale ($$$)","Limited customization","Vendor lock-in","Can't run stateful workloads well"],
verdict:"Incredible for getting started. But at 50+ containers the bill is painful and you'll hit walls on what you can configure."},
{name:"AWS ECS / Cloud Run",sub:"Managed container services",color:C.orange,sweet:"Teams already deep in one cloud provider",
gives:["Less complex than K8s","Tight cloud integration","Managed control plane","Scales well"],
lacks:["Vendor lock-in","Smaller ecosystem","Fewer community tools","Cloud-specific abstractions"],
verdict:"Solid if you're committed to one cloud. But your deployment configs, tooling, and team knowledge don't transfer anywhere else."},
{name:"Kubernetes",color:C.accent,sub:null,sweet:"Multi-service systems, multi-cloud, teams needing portability",
gives:["Self-healing & autoscaling","Works on any cloud or bare metal","Massive ecosystem (Helm, Istio, Argo...)","Declarative, version-controlled infra"],
lacks:["Steep learning curve","Operational complexity","Overkill for small projects","YAML sprawl is real"],
verdict:"The cost is complexity. The payoff is a portable, self-healing platform that runs identically everywhere and has the largest ecosystem of any infrastructure tool."}
];
function AltsPanel() {
var ref = useState(null), sel = ref[0], setSel = ref[1];
var items = ALTS.map(function(a,i) {
var open = sel === i;
var givesEls = a.gives.map(function(g) {
return h("div",{key:g,style:{fontFamily:F.mono,fontSize:11,color:C.textMuted,lineHeight:1.8,paddingLeft:8,borderLeft:"2px solid "+C.accent+"20"}},g);
});
var lacksEls = a.lacks.map(function(l) {
return h("div",{key:l,style:{fontFamily:F.mono,fontSize:11,color:C.textMuted,lineHeight:1.8,paddingLeft:8,borderLeft:"2px solid "+C.red+"20"}},l);
});
var nameSpan = h("span",{style:{fontFamily:F.mono,fontSize:14,fontWeight:600,color:a.color}},a.name);
var subSpan = a.sub ? h("span",{style:{fontFamily:F.body,fontSize:11,color:C.textDim,marginLeft:8}},a.sub) : null;
var expandedContent = null;
if (open) {
expandedContent = h("div",{style:{marginTop:14,paddingTop:14,borderTop:"1px solid rgba(255,255,255,0.04)"}},
h("div",{style:{fontFamily:F.mono,fontSize:10,color:C.textDim,textTransform:"uppercase",letterSpacing:"0.08em",marginBottom:6}},"Sweet spot"),
h("div",{style:{fontFamily:F.body,fontSize:13,color:C.textMuted,marginBottom:14}},a.sweet),
h("div",{style:{display:"grid",gridTemplateColumns:"1fr 1fr",gap:12,marginBottom:14}},
h("div",null,
h("div",{style:{fontFamily:F.mono,fontSize:10,color:C.accent,marginBottom:6}},"\u2713 gives you"),
givesEls
),
h("div",null,
h("div",{style:{fontFamily:F.mono,fontSize:10,color:C.red,marginBottom:6}},"\u2717 costs you"),
lacksEls
)
),
h("div",{style:{padding:"10px 14px",background:a.color+"08",borderRadius:6,borderLeft:"3px solid "+a.color+"40"}},
h("div",{style:{fontFamily:F.body,fontSize:13,color:"#a0a4ac",lineHeight:1.6}},a.verdict)
)
);
}
var sweetText = !open ? h("div",{style:{marginTop:6,fontFamily:F.body,fontSize:12,color:C.textDim}},"Best for: "+a.sweet) : null;
return h("div",{key:a.name,onClick:function(){setSel(open?null:i);},style:{
background:open?a.color+"08":C.surface,borderRadius:10,padding:"14px 18px",
border:"1px solid "+(open?a.color+"35":C.border),cursor:"pointer",transition:"all 0.25s"
}},
h("div",{style:{display:"flex",alignItems:"center",gap:10}},
h("span",{style:{width:10,height:10,borderRadius:3,background:a.color,flexShrink:0}}),
h("div",{style:{flex:1}},nameSpan,subSpan),
h("span",{style:{fontFamily:F.mono,fontSize:11,color:C.textDim,transform:open?"rotate(90deg)":"rotate(0deg)",transition:"transform 0.2s"}},"\u25b8")
),
sweetText,
expandedContent
);
});
return h("div",null,
h("div",{style:{marginBottom:20}},
h("h3",{style:{fontFamily:F.body,fontSize:18,fontWeight:400,color:C.text,margin:"0 0 6px"}},"Why not something simpler?"),
h("p",{style:{fontFamily:F.body,fontSize:14,color:C.textMuted,margin:0,lineHeight:1.5}},"Every option involves a real tradeoff. Kubernetes isn't always the answer. Tap each to compare.")
),
h("div",{style:{display:"flex",flexDirection:"column",gap:8}},items),
h(Insight,{style:{marginTop:16}},
"The real question isn't \"should I use Kubernetes\" but ",
h("span",{style:{color:C.accent,fontWeight:600}},"\"do I need a portable, self-healing platform?\""),
" If you're running 3 containers on Heroku, the answer is no. If you're running 50 services across teams and clouds, it's likely yes. The ecosystem is the moat: Helm charts, GitOps with Argo, service meshes, observability stacks \u2014 all of it assumes Kubernetes."
)
);
}
// ═══════════════════════════════════════════════════════════════
// PANEL 3: KEY CONCEPTS
// ═══════════════════════════════════════════════════════════════
var ABSTRACTIONS = [
{name:"Pod",color:C.blue,icon:"\u2b21",
what:"The smallest deployable unit. One or more containers that share an IP and storage.",
why:"You don't deploy a container \u2014 you deploy a pod. This lets K8s manage sidecar patterns (logging, proxies) as first-class units."},
{name:"Deployment",color:C.accent,icon:"\u229e",
what:"Manages a set of identical pods. Handles rolling updates and rollbacks.",
why:"You say 'I want 3 copies of this pod running version 2.1.' The Deployment controller makes it happen, replacing old pods one at a time so traffic keeps flowing."},
{name:"Service",color:C.purple,icon:"\u25ce",
what:"A stable network endpoint that routes to a set of pods, even as they're created and destroyed.",
why:"Pods are ephemeral \u2014 they get new IPs every time they restart. A Service gives them a permanent address so other pods can find them."},
{name:"Node",color:C.cyan,icon:"\u25a2",
what:"A machine (physical or virtual) that runs pods. The Scheduler decides which node gets which pod.",
why:"You never assign a pod to a node manually. The Scheduler considers CPU, memory, affinity rules, and constraints to pack pods efficiently."}
];
function AbstractionsPanel() {
var ref = useState(0), sel = ref[0], setSel = ref[1];
var a = ABSTRACTIONS[sel];
var podGroups = [0,1,2].map(function(i) {
return h("g",{key:i,onClick:function(){setSel(0);},style:{cursor:"pointer"}},
h("rect",{x:70+i*155,y:92,width:140,height:50,rx:6,
fill:sel===0?C.blueGlow:"rgba(255,255,255,0.02)",
stroke:sel===0?C.blue+"50":"rgba(255,255,255,0.04)",strokeWidth:1,
style:{transition:"all 0.3s"}}),
h("text",{x:100+i*155,y:114,fontFamily:F.mono,fontSize:11,fill:C.blue},"\u2b21 Pod "+(i+1)),
h("rect",{x:82+i*155,y:120,width:52,height:16,rx:3,
fill:"rgba(140,180,255,0.08)",stroke:"rgba(140,180,255,0.15)",strokeWidth:0.5}),
h("text",{x:88+i*155,y:132,fontFamily:F.mono,fontSize:8,fill:C.blue},"nginx:2.1"),
h("rect",{x:140+i*155,y:120,width:58,height:16,rx:3,
fill:"rgba(167,139,250,0.08)",stroke:"rgba(167,139,250,0.15)",strokeWidth:0.5}),
h("text",{x:146+i*155,y:132,fontFamily:F.mono,fontSize:8,fill:C.purple},"log-sidecar")
);
});
var cpItems = [
{name:"API Server",color:C.accent},{name:"etcd",color:C.yellow},
{name:"Scheduler",color:C.blue},{name:"Controller Mgr",color:C.purple}
];
var selButtons = ABSTRACTIONS.map(function(ab,i) {
return h("button",{key:ab.name,onClick:function(){setSel(i);},style:{
padding:"6px 14px",borderRadius:6,cursor:"pointer",fontFamily:F.mono,fontSize:11,
background:sel===i?ab.color+"15":C.surface,
border:"1px solid "+(sel===i?ab.color+"40":C.border),
color:sel===i?ab.color:C.textDim,transition:"all 0.2s"
}},ab.icon+" "+ab.name);
});
return h("div",null,
h("div",{style:{marginBottom:20}},
h("h3",{style:{fontFamily:F.body,fontSize:18,fontWeight:400,color:C.text,margin:"0 0 6px"}},"The key abstractions"),
h("p",{style:{fontFamily:F.body,fontSize:14,color:C.textMuted,margin:0,lineHeight:1.5}},"Kubernetes gives you a vocabulary for describing infrastructure. Four concepts cover most of what you need.")
),
h("svg",{width:"100%",height:180,viewBox:"0 0 580 180",style:{display:"block",marginBottom:20,overflow:"visible"}},
h("rect",{x:10,y:8,width:560,height:164,rx:10,
fill:sel===3?C.cyanGlow:"rgba(255,255,255,0.01)",
stroke:sel===3?C.cyan+"50":"rgba(255,255,255,0.05)",strokeWidth:1,
style:{transition:"all 0.3s",cursor:"pointer"},onClick:function(){setSel(3);}}),
h("text",{x:24,y:28,fontFamily:F.mono,fontSize:10,fill:C.cyan},"NODE"),
h("rect",{x:30,y:38,width:520,height:124,rx:8,
fill:sel===2?C.purpleGlow:"rgba(255,255,255,0.01)",
stroke:sel===2?C.purple+"50":"rgba(255,255,255,0.04)",strokeWidth:1,
style:{transition:"all 0.3s",cursor:"pointer"},onClick:function(){setSel(2);}}),
h("text",{x:44,y:56,fontFamily:F.mono,fontSize:10,fill:C.purple},"SERVICE \u2192 api-svc:8080"),
h("rect",{x:50,y:66,width:480,height:86,rx:6,
fill:sel===1?C.accentGlow:"rgba(255,255,255,0.01)",
stroke:sel===1?C.accent+"50":"rgba(255,255,255,0.04)",strokeWidth:1,
style:{transition:"all 0.3s",cursor:"pointer"},onClick:function(){setSel(1);}}),
h("text",{x:64,y:82,fontFamily:F.mono,fontSize:10,fill:C.accent},"DEPLOYMENT (replicas: 3)"),
podGroups
),
h("div",{style:{
padding:"16px 20px",borderRadius:10,marginBottom:12,
background:a.color+"08",border:"1px solid "+a.color+"25",
borderLeft:"3px solid "+a.color+"60"
}},
h("div",{style:{display:"flex",alignItems:"center",gap:10,marginBottom:10}},
h("span",{style:{fontSize:20}},a.icon),
h("span",{style:{fontFamily:F.mono,fontSize:16,fontWeight:600,color:a.color}},a.name)
),
h("div",{style:{fontFamily:F.body,fontSize:13,color:C.textMuted,lineHeight:1.6,marginBottom:10}},
h("span",{style:{color:"#a0a4ac",fontWeight:500}},"What it is: "),a.what
),
h("div",{style:{fontFamily:F.body,fontSize:13,color:C.textMuted,lineHeight:1.6}},
h("span",{style:{color:a.color,fontWeight:500}},"Why it matters: "),a.why
)
),
h("div",{style:{display:"flex",gap:6,justifyContent:"center"}},selButtons),
h(Insight,{style:{marginTop:16}},"These four abstractions are why Kubernetes feels like an operating system for your infrastructure. You describe relationships \u2014 \"3 copies, reachable at this address, on machines with enough RAM\" \u2014 and the system maintains them. Click the diagram or the buttons to explore each layer.")
);
}
// ═══════════════════════════════════════════════════════════════
// PANEL 4: RECONCILIATION
// ═══════════════════════════════════════════════════════════════
function ReconcilePanel() {
var ref1 = useState(3), desired = ref1[0], setDesired = ref1[1];
var ref2 = useState(3), actual = ref2[0], setActual = ref2[1];
var ref3 = useState(false), healing = ref3[0], setHealing = ref3[1];
var ref4 = useState([]), log = ref4[0], setLog = ref4[1];
function killPod() {
if (actual <= 0 || healing) return;
var next = actual - 1;
setActual(next);
setLog(function(p){return p.slice(-4).concat({msg:"Pod crashed. actual="+next+", desired="+desired,color:C.red});});
setHealing(true);
setTimeout(function(){setLog(function(p){return p.slice(-4).concat({msg:"Controller detected drift: "+next+" < "+desired,color:C.yellow});});},600);
setTimeout(function(){setLog(function(p){return p.slice(-4).concat({msg:"Scheduler assigning new pod to healthy node",color:C.blue});});},1200);
setTimeout(function(){
setActual(desired);
setHealing(false);
setLog(function(p){return p.slice(-4).concat({msg:"Pod started. actual="+desired+", desired="+desired+" \u2713",color:C.accent});});
},1800);
}
function changeDesired(n) {
var d = Math.max(1, Math.min(6, n));
setDesired(d);
setLog(function(p){return p.slice(-4).concat({msg:"Desired replicas changed to "+d,color:C.purple});});
setHealing(true);
setTimeout(function(){
setActual(d);
setHealing(false);
setLog(function(p){return p.slice(-4).concat({msg:"Reconciled. actual="+d+", desired="+d+" \u2713",color:C.accent});});
},1000);
}
var pods = [];
for (var i = 0; i < Math.max(desired, actual); i++) pods.push({alive:i<actual,idx:i});
var podEls = pods.map(function(p,i) {
return h("div",{key:i,onClick:p.alive?killPod:undefined,style:{
width:62,height:62,borderRadius:10,display:"flex",flexDirection:"column",
alignItems:"center",justifyContent:"center",cursor:p.alive?"pointer":"default",
background:p.alive?C.accentGlow:C.redGlow,
border:"1px solid "+(p.alive?C.accent+"35":C.red+"35"),
transition:"all 0.4s",opacity:p.alive?1:0.3
}},
h("div",{style:{fontSize:18}},p.alive?"\u2b21":"\u2717"),
h("div",{style:{fontFamily:F.mono,fontSize:9,color:p.alive?C.accent:C.red,marginTop:2}},"pod-"+(i+1))
);
});
var statusItems = [
{label:"Desired",val:desired,color:C.purple},
{label:"Actual",val:actual,color:actual===desired?C.accent:C.red},
{label:"Status",val:actual===desired?"\u2713 converged":healing?"\u21bb healing":"\u2717 drift",
color:actual===desired?C.accent:healing?C.yellow:C.red,isTxt:true}
];
var statusEls = statusItems.map(function(s) {
return h("div",{key:s.label,style:{flex:1,padding:"10px 12px",background:C.surface,borderRadius:6,border:"1px solid "+C.border}},
h("div",{style:{fontSize:10,color:C.textDim,textTransform:"uppercase",letterSpacing:"0.08em"}},s.label),
h("div",{style:{fontSize:s.isTxt?14:20,fontFamily:F.mono,color:s.color,fontWeight:s.isTxt?400:300}},s.val)
);
});
var logEls = log.map(function(l,i) {
return h("div",{key:i,style:{color:l.color,lineHeight:1.8,opacity:i===log.length-1?1:0.5}},"\u2192 "+l.msg);
});
return h("div",null,
h("div",{style:{marginBottom:20}},
h("h3",{style:{fontFamily:F.body,fontSize:18,fontWeight:400,color:C.text,margin:"0 0 6px"}},"The core idea: desired vs. actual"),
h("p",{style:{fontFamily:F.body,fontSize:14,color:C.textMuted,margin:0,lineHeight:1.5}},"You declare what you want. Kubernetes runs a continuous loop to make reality match. This is the single idea that replaces scripts, runbooks, and 2 AM pages.")
),
h("div",{style:{display:"flex",alignItems:"center",gap:16,marginBottom:20,justifyContent:"center"}},
h("span",{style:{fontFamily:F.mono,fontSize:12,color:C.textMuted}},"replicas:"),
h("button",{onClick:function(){changeDesired(desired-1);},disabled:healing,style:{width:28,height:28,borderRadius:6,background:C.surface,border:"1px solid "+C.border,color:C.textMuted,cursor:healing?"default":"pointer",fontFamily:F.mono,fontSize:14,opacity:healing?0.4:1}},"\u2212"),
h("span",{style:{fontFamily:F.mono,fontSize:22,color:C.accent,width:24,textAlign:"center"}},desired),
h("button",{onClick:function(){changeDesired(desired+1);},disabled:healing,style:{width:28,height:28,borderRadius:6,background:C.surface,border:"1px solid "+C.border,color:C.textMuted,cursor:healing?"default":"pointer",fontFamily:F.mono,fontSize:14,opacity:healing?0.4:1}},"+")
),
h("div",{style:{display:"flex",gap:10,justifyContent:"center",marginBottom:8,flexWrap:"wrap",minHeight:70}},podEls),
h("div",{style:{textAlign:"center",fontFamily:F.mono,fontSize:10,color:C.textDim,marginBottom:16}},"click a pod to kill it"),
h("div",{style:{display:"flex",gap:10,marginBottom:16}},statusEls),
h("div",{style:{background:"rgba(0,0,0,0.2)",borderRadius:8,padding:"10px 14px",fontFamily:F.mono,fontSize:11,border:"1px solid "+C.border,minHeight:80}},
h("div",{style:{fontSize:10,color:C.textDim,marginBottom:6,textTransform:"uppercase",letterSpacing:"0.08em"}},"Event Log"),
log.length===0?h("div",{style:{color:C.textDim}},"Waiting for events..."):null,
logEls
),
h(Insight,{style:{marginTop:16}},
"This is why people adopt Kubernetes: ",
h("span",{style:{color:C.accent,fontWeight:600}},"declarative self-healing"),
". Without it, every one of these events is a human task \u2014 detect the failure, decide where to restart, execute it, verify it worked. With it, the system handles all four automatically, at any hour, for any number of pods. That's the real value: not the features, but the operational burden that disappears."
)
);
}
// ═══════════════════════════════════════════════════════════════
// MAIN
// ═══════════════════════════════════════════════════════════════
var PANELS = [
{id:"scale",num:"01",label:"The Pain",Component:ScalePanel},
{id:"alts",num:"02",label:"Alternatives",Component:AltsPanel},
{id:"abstractions",num:"03",label:"Key Concepts",Component:AbstractionsPanel},
{id:"reconcile",num:"04",label:"Self-Healing",Component:ReconcilePanel}
];
function App() {
var ref = useState(0), active = ref[0], setActive = ref[1];
var Comp = PANELS[active].Component;
var tabs = PANELS.map(function(p,i) {
return h("button",{key:p.id,onClick:function(){setActive(i);},style:{
flex:1,fontFamily:F.mono,fontSize:12,fontWeight:active===i?500:400,
color:active===i?C.text:C.textDim,
background:active===i?"rgba(255,255,255,0.05)":"transparent",
border:"none",borderRadius:7,padding:"10px 6px",
cursor:"pointer",transition:"all 0.2s",letterSpacing:"0.01em"
}},
h("span",{style:{display:"inline-block",width:16,fontFamily:F.mono,fontSize:10,opacity:0.4,marginRight:4}},p.num),
p.label
);
});
return h("div",{style:{minHeight:"100vh",background:C.bg,color:C.text,fontFamily:F.body,padding:"40px 16px"}},
h("div",{style:{maxWidth:660,margin:"0 auto 32px"}},
h("div",{style:{fontFamily:F.mono,fontSize:11,color:C.accent,textTransform:"uppercase",letterSpacing:"0.12em",marginBottom:8}},"Interactive Explainer"),
h("h1",{style:{fontSize:28,fontWeight:300,margin:"0 0 8px",lineHeight:1.3,letterSpacing:"-0.01em"}},"When Your Containers Outgrow One Server"),
h("p",{style:{fontSize:15,color:C.textMuted,margin:0,lineHeight:1.5}},"Most systems don't start with Kubernetes. They grow into needing it.")
),
h("div",{style:{maxWidth:660,margin:"0 auto 24px",display:"flex",gap:4,background:C.surface,borderRadius:10,padding:4,border:"1px solid "+C.border}},tabs),
h("div",{style:{maxWidth:660,margin:"0 auto",background:C.surface,border:"1px solid "+C.border,borderRadius:12,padding:"28px 24px",minHeight:420}},
h(Comp,{key:active})
),
h("div",{style:{maxWidth:660,margin:"16px auto 0",display:"flex",justifyContent:"space-between",alignItems:"center"}},
h("button",{onClick:function(){setActive(Math.max(0,active-1));},disabled:active===0,style:{
fontFamily:F.mono,fontSize:12,color:active===0?C.textDim:C.textMuted,
background:"none",border:"1px solid "+(active===0?"transparent":C.border),
borderRadius:6,padding:"8px 16px",cursor:active===0?"default":"pointer",transition:"all 0.2s"
}},"\u2190 prev"),
h("span",{style:{fontFamily:F.mono,fontSize:11,color:C.textDim}},(active+1)+" / "+PANELS.length),
h("button",{onClick:function(){setActive(Math.min(PANELS.length-1,active+1));},disabled:active===PANELS.length-1,style:{
fontFamily:F.mono,fontSize:12,color:active===PANELS.length-1?C.textDim:C.textMuted,
background:"none",border:"1px solid "+(active===PANELS.length-1?"transparent":C.border),
borderRadius:6,padding:"8px 16px",cursor:active===PANELS.length-1?"default":"pointer",transition:"all 0.2s"
}},"next \u2192")
)
);
}
ReactDOM.render(h(App), document.getElementById("root"));
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment