Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Diagrams

Animated diagrams — not another static boxes-and-arrows generator. Declare an architecture (or a flowchart) with auto-positioned nodes and directed connections — geometry is optional, so a diagram auto-fits the canvas and, when it grows dense, scales itself down as one to stay inside the frame (you never touch a coordinate) — then move one persistent request through the graph. A flowchart ranks its nodes top-down and runs: a token walks the process and takes a branch. Node artwork comes from a string kind (aws:lambda, gcp:bigquery, onprem:redis, k8s:pod — 17 providers, see icon reference & aliases); paths are coloured by relationship. The kit never infers behaviour — the creator authors it with route, flow, and ordinary verbs.

Each block is the whole file — copy it into x.manic and run manic x.manic (live) or --record out (video).

systems-foundation

Provider-neutral: only native client/gateway/service/cache/database archetypes — reveals cold topology, sends one persistent request forward, and returns the same identity over separately directed paths, with no cloud assets.

title("Systems Foundation — One Request, One Return");
canvas("16:9");
template("mono");

watermark(mark, (160, 54), "Made With Manic");
text(kicker, (cx, 54), "PROVIDER-NEUTRAL SYSTEMS");
text(headline, (cx, 104), "Structure is not behaviour");
text(caption, (cx, h-58), "Cold paths show what is possible. One persistent dot shows what actually happened.");
size(kicker, 18); color(kicker, dim); bold(kicker);
size(headline, 34); bold(headline);
size(caption, 20); color(caption, dim); wrap(caption, w*0.86);

architecture(platform, (cx, cy+15), w*0.88, h*0.58);
node(user, platform, "client", "User");
node(edge, platform, "gateway", "Gateway");
node(api, platform, "service", "API Service");
node(cache, platform, "cache", "Cache");
node(db, platform, "database", "Database");

connect(toEdge, user, edge);
connect(toApi, edge, api);
connect(toCache, api, cache);
// One semantic connection: Manic chooses node-boundary ports and keeps the
// packet continuous across every internal orthogonal segment.
connect(toDb, cache, db, orthogonal, right, top);
connect(dbReturn, db, api, 95);
connect(apiReturn, api, edge, 75);
connect(edgeReturn, edge, user, 55);

message(packet, user, "GET");
hidden(kicker); hidden(headline); hidden(caption);
untraced(platform.connections);
hidden(packet);

step("structure") {
  par {
    show(kicker, 0.35);
    show(headline, 0.45);
    show(caption, 0.45);
    draw(platform.connections, 1.20, smooth);
  }
}
wait(0.45);

step("continuous-round-trip") {
  par {
    show(packet, 0.20);
    say(caption, "One dot moves continuously end to end. Every route hand-off preserves its identity.", 0.35);
    seq {
      route(packet, toEdge, 0.70, linear);
      route(packet, toApi, 0.70, linear);
      route(packet, toCache, 0.70, linear);
      route(packet, toDb, 0.70, linear);
      par {
        recolor(packet, magenta, 0.30);
        route(packet, dbReturn, 0.80, linear);
      }
      route(packet, apiReturn, 0.75, linear);
      route(packet, edgeReturn, 0.75, linear);
    }
  }
}
wait(0.45);

step("proof") {
  par {
    pulse(packet, 0.70);
    say(caption, "The same dot is back at the User: one identity, seven connected journeys, zero animation gaps.", 0.40);
  }
}
wait(1.10);

systems-architecture-poc

The first story: one Buy request travels Browser → CloudFront → API Gateway → Lambda → DynamoDB → SQS, auto-laid horizontally or vertically by the responsive region.

title("Systems Kit PoC — What Happens When You Click Buy?");
canvas("16:9");
template("mono");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.07), "Made With Manic");
text(kicker, (cx, h*0.075), "SYSTEMS KIT · REQUEST JOURNEY");
text(headline, (cx, h*0.155), "What happens when you click Buy?");
text(caption, (cx, h*0.88), "One request keeps its identity while the architecture responds.");
size(kicker, 18*u); color(kicker, dim); bold(kicker);
size(headline, 34*u); bold(headline); wrap(headline, w*0.84);
size(caption, 19*u); color(caption, dim); wrap(caption, w*0.82);

architecture(shop, (cx, h*0.49), w*0.84, h*0.58);
node(browser, shop, "client", "Browser");
node(edge, shop, "aws:cloudfront", "CloudFront");
node(api, shop, "aws:api-gateway", "API Gateway");
node(checkout, shop, "aws:lambda", "Checkout");
node(orders, shop, "aws:dynamodb", "Orders");
node(events, shop, "aws:sqs", "Events");

connect(toEdge, browser, edge);
connect(toApi, edge, api);
connect(toCheckout, api, checkout);
connect(toOrders, checkout, orders);
connect(toEvents, orders, events);
request(order, browser, "BUY");

hidden(shop.nodes);
untraced(shop.connections);
hidden(order.parts);

step("architecture") {
  stagger(0.10) {
    show(browser, 0.30); show(edge, 0.30); show(api, 0.30);
    show(checkout, 0.30); show(orders, 0.30); show(events, 0.30);
  }
}
wait(0.25);

step("request") {
  par {
    show(order.parts, 0.30);
    draw(shop.connections, 0.90);
    say(caption, "The browser sends one request into the edge network.", 0.35);
  }
}
wait(0.20);

step("edge") {
  par {
    route(order, toEdge, 0.90, smooth);
    say(caption, "CloudFront receives it first, close to the customer.", 0.35);
  }
}

step("api") {
  par {
    route(order, toApi, 0.90, smooth);
    say(caption, "API Gateway validates and routes the operation.", 0.35);
  }
}

step("compute") {
  par {
    route(order, toCheckout, 0.90, smooth);
    say(caption, "Lambda executes the checkout logic.", 0.35);
  }
}

step("persist") {
  par {
    route(order, toOrders, 0.90, smooth);
    say(caption, "DynamoDB persists the order.", 0.35);
  }
}

step("event") {
  par {
    route(order, toEvents, 0.90, smooth);
    say(caption, "SQS decouples the work that can happen next.", 0.35);
  }
}
wait(0.30);

step("takeaway") {
  par {
    pulse(shop.nodes, 0.75);
    say(caption, "Architecture becomes understandable when we follow behavior—not only boxes and arrows.", 0.45);
  }
}
wait(1.20);

systems-arrow-patterns

The connection-grammar reference: one-way, parallel, round-trip, port-aware orthogonal, fan-out, and diagonal-duplex lanes.

title("Systems Foundation — Arrow Patterns");
canvas("16:9");
template("mono");

let u = (w+h-abs(w-h))/1440;
let pw = w*0.27;
let ph = h*0.25;
let x1 = w*0.18;
let x2 = w*0.50;
let x3 = w*0.82;
let y1 = h*0.34;
let y2 = h*0.68;

watermark(mark, (w*0.09, h*0.045), "Made With Manic");
text(kicker, (cx, h*0.045), "SYSTEMS FOUNDATION · DIRECTION IS VISIBLE");
text(headline, (cx, h*0.12), "One arrow grammar, many delivery stories");
text(caption, (cx, h*0.925), "Arrows describe paths. seq, par, travel, and flow describe what moves through them.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 31*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.88);

rect(p1, (x1,y1), pw,ph); outlined(p1); color(p1, dim); stroke(p1, 2*u);
rect(p2, (x2,y1), pw,ph); outlined(p2); color(p2, dim); stroke(p2, 2*u);
rect(p3, (x3,y1), pw,ph); outlined(p3); color(p3, dim); stroke(p3, 2*u);
rect(p4, (x1,y2), pw,ph); outlined(p4); color(p4, dim); stroke(p4, 2*u);
rect(p5, (x2,y2), pw,ph); outlined(p5); color(p5, dim); stroke(p5, 2*u);
rect(p6, (x3,y2), pw,ph); outlined(p6); color(p6, dim); stroke(p6, 2*u);

text(t1, (x1,y1-ph*0.36), "ONE WAY");
text(t2, (x2,y1-ph*0.36), "ROUND TRIP");
text(t3, (x3,y1-ph*0.36), "ORTHOGONAL");
text(t4, (x1,y2-ph*0.36), "VERTICAL PORTS");
text(t5, (x2,y2-ph*0.36), "FAN OUT");
text(t6, (x3,y2-ph*0.36), "DIAGONAL DUPLEX");
size(t1, 16*u); size(t2, 16*u); size(t3, 16*u);
size(t4, 16*u); size(t5, 16*u); size(t6, 16*u);
color(t1, dim); color(t2, dim); color(t3, dim);
color(t4, dim); color(t5, dim); color(t6, dim);
bold(t1); bold(t2); bold(t3); bold(t4); bold(t5); bold(t6);

// 1 — one source, one destination.
arrow(one, (x1-pw*0.34,y1+ph*0.08), (x1+pw*0.34,y1+ph*0.08));
color(one, cyan); stroke(one, 4*u); untraced(one);
circle(oneDot, (x1-pw*0.34,y1+ph*0.08), 8*u); color(oneDot, cyan); hidden(oneDot);

// 2 — request and response remain separate, honest directions.
arrow(outbound, (x2-pw*0.34,y1), (x2+pw*0.34,y1));
arrow(inbound, (x2+pw*0.34,y1+ph*0.17), (x2-pw*0.34,y1+ph*0.17));
color(outbound, cyan); color(inbound, magenta);
stroke(outbound, 4*u); stroke(inbound, 4*u);
untraced(outbound); untraced(inbound);
circle(requestDot, (x2-pw*0.34,y1), 8*u); color(requestDot, cyan); hidden(requestDot);
circle(responseDot, (x2+pw*0.34,y1+ph*0.17), 8*u); color(responseDot, magenta); hidden(responseDot);

// 3 — generic geometry can draw a stepped path; Systems nodes use one
// `connect(path, a, b, orthogonal)` declaration for the same routed identity.
line(elbowA, (x3-pw*0.34,y1-ph*0.02), (x3,y1-ph*0.02));
line(elbowB, (x3,y1-ph*0.02), (x3,y1+ph*0.20));
arrow(elbowC, (x3,y1+ph*0.20), (x3+pw*0.34,y1+ph*0.20));
color(elbowA, gold); color(elbowB, gold); color(elbowC, gold);
stroke(elbowA, 4*u); stroke(elbowB, 4*u); stroke(elbowC, 4*u);
untraced(elbowA); untraced(elbowB); untraced(elbowC);
circle(elbowDot, (x3-pw*0.34,y1-ph*0.02), 8*u); color(elbowDot, gold); hidden(elbowDot);

// 4 — ports can face vertically without changing motion semantics.
arrow(downLane, (x1-pw*0.12,y2-ph*0.20), (x1-pw*0.12,y2+ph*0.25));
arrow(upLane, (x1+pw*0.12,y2+ph*0.25), (x1+pw*0.12,y2-ph*0.20));
color(downLane, cyan); color(upLane, magenta);
stroke(downLane, 4*u); stroke(upLane, 4*u);
untraced(downLane); untraced(upLane);
circle(downDot, (x1-pw*0.12,y2-ph*0.20), 8*u); color(downDot, cyan); hidden(downDot);
circle(upDot, (x1+pw*0.12,y2+ph*0.25), 8*u); color(upDot, magenta); hidden(upDot);

// 5 — fan-out is three explicitly authored deliveries, not inferred broadcast.
arrow(fanA, (x2-pw*0.34,y2+ph*0.04), (x2+pw*0.34,y2-ph*0.22));
arrow(fanB, (x2-pw*0.34,y2+ph*0.04), (x2+pw*0.34,y2+ph*0.04));
arrow(fanC, (x2-pw*0.34,y2+ph*0.04), (x2+pw*0.34,y2+ph*0.30));
color(fanA, cyan); color(fanB, cyan); color(fanC, cyan);
stroke(fanA, 3*u); stroke(fanB, 3*u); stroke(fanC, 3*u);
untraced(fanA); untraced(fanB); untraced(fanC);
circle(fanDotA, (x2-pw*0.34,y2+ph*0.04), 7*u); color(fanDotA, cyan); hidden(fanDotA);
circle(fanDotB, (x2-pw*0.34,y2+ph*0.04), 7*u); color(fanDotB, cyan); hidden(fanDotB);
circle(fanDotC, (x2-pw*0.34,y2+ph*0.04), 7*u); color(fanDotC, cyan); hidden(fanDotC);

// 6 — diagonal request/return lanes use different styling and directions.
arrow(diagOut, (x3-pw*0.32,y2+ph*0.24), (x3+pw*0.27,y2-ph*0.22));
arrow(diagBack, (x3+pw*0.34,y2-ph*0.12), (x3-pw*0.25,y2+ph*0.34));
color(diagOut, cyan); color(diagBack, magenta); dashed(diagBack);
stroke(diagOut, 4*u); stroke(diagBack, 4*u);
untraced(diagOut); untraced(diagBack);
circle(diagRequest, (x3-pw*0.32,y2+ph*0.24), 8*u); color(diagRequest, cyan); hidden(diagRequest);
circle(diagResponse, (x3+pw*0.34,y2-ph*0.12), 8*u); color(diagResponse, magenta); hidden(diagResponse);

tag(p1, cards); tag(p2, cards); tag(p3, cards); tag(p4, cards); tag(p5, cards); tag(p6, cards);
tag(t1, cards); tag(t2, cards); tag(t3, cards); tag(t4, cards); tag(t5, cards); tag(t6, cards);
tag(one, paths); tag(outbound, paths); tag(inbound, paths);
tag(elbowA, paths); tag(elbowB, paths); tag(elbowC, paths);
tag(downLane, paths); tag(upLane, paths);
tag(fanA, paths); tag(fanB, paths); tag(fanC, paths);
tag(diagOut, paths); tag(diagBack, paths);

hidden(kicker); hidden(headline); hidden(caption); hidden(cards);

step("arrow-grammar") {
  par {
    show(kicker, 0.30);
    show(headline, 0.45);
    show(caption, 0.40);
    show(cards, 0.45);
    draw(paths, 1.10, smooth);
  }
}
wait(0.35);

step("traffic") {
  par {
    show(oneDot, 0.15); travel(oneDot, one, 1.20, linear); flow(one, 1.20);
    seq {
      show(requestDot, 0.15); travel(requestDot, outbound, 0.65, linear);
      show(responseDot, 0.15); travel(responseDot, inbound, 0.65, linear);
    }
    seq {
      show(elbowDot, 0.15);
      travel(elbowDot, elbowA, 0.40, linear);
      travel(elbowDot, elbowB, 0.35, linear);
      travel(elbowDot, elbowC, 0.45, linear);
    }
    par {
      show(downDot, 0.15); travel(downDot, downLane, 1.20, linear);
      show(upDot, 0.15); travel(upDot, upLane, 1.20, linear);
    }
    par {
      show(fanDotA, 0.15); travel(fanDotA, fanA, 1.20, linear);
      show(fanDotB, 0.15); travel(fanDotB, fanB, 1.20, linear);
      show(fanDotC, 0.15); travel(fanDotC, fanC, 1.20, linear);
    }
    par {
      show(diagRequest, 0.15); travel(diagRequest, diagOut, 1.20, linear);
      show(diagResponse, 0.15); travel(diagResponse, diagBack, 1.20, linear);
    }
  }
}
wait(0.55);

step("creator-controls-semantics") {
  par {
    pulse(paths, 0.75);
    say(caption, "The same paths can mean request, response, queue delivery, broadcast, or pure motion—the creator decides.", 0.40);
  }
}
wait(1.20);

microservices-platform

Auto-fit showcase: Route 53 → load balancer → gateway, three availability zones of ECS services, a replicated RDS database cluster, and CloudWatch monitoring — declared with zero coordinates. Add a tier or a zone and the whole diagram reflows and scales itself down to the frame; paths coloured by relationship (request · write · replication · telemetry).

title("A Microservices Platform — Laid Out Automatically");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · AUTO-FIT · NO COORDINATES");
text(headline, (cx, h*0.12), "Add a tier. It re-fits itself.");
text(caption, (cx, h*0.95), "DNS, load balancing, three AZs of services, a database cluster, and monitoring — all auto-laid.");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.86);
size(caption, 17*u); color(caption, dim); wrap(caption, w*0.86);

// No geometry: the diagram auto-fits. Route 53, an edge load balancer, three
// availability zones, a replicated database cluster and monitoring all reflow
// to fit — and when the content would overflow, the whole diagram scales down
// as one. The author types no coordinate.
architecture(platform);

node(dns, platform, "aws:route53", "route 53");
node(lb, platform, "aws:elb", "load balancer");
node(gateway, platform, "aws:api-gateway", "gateway");

cluster(region, platform, "REGION us-east-1");
cluster(azA, region, "AZ-A");
node(a1, azA, "aws:ecs", "svc"); node(a2, azA, "aws:ecs", "svc"); node(a3, azA, "aws:ecs", "svc");
cluster(azB, region, "AZ-B");
node(b1, azB, "aws:ecs", "svc"); node(b2, azB, "aws:ecs", "svc"); node(b3, azB, "aws:ecs", "svc");
cluster(azC, region, "AZ-C");
node(c1, azC, "aws:ecs", "svc"); node(c2, azC, "aws:ecs", "svc"); node(c3, azC, "aws:ecs", "svc");

cluster(database, platform, "DATABASE CLUSTER");
node(primary, database, "aws:rds", "primary");
node(replica1, database, "aws:rds", "replica");
node(replica2, database, "aws:rds", "replica");
node(cache, database, "aws:elasticache", "cache");

cluster(observability, platform, "MONITORING");
node(metrics, observability, "aws:cloudwatch", "metrics");
node(logs, observability, "aws:cloudwatch", "logs");
node(alarms, observability, "aws:cloudwatch", "alarms");

connect(dnsToLb, dns, lb);        // resolve, then hit the edge load balancer
connect(lbToGw, lb, gateway);     // balanced across the API gateway
connect(toA, gateway, azA);       // gateway fans out to each zone's services
connect(toB, gateway, azB);
connect(toC, gateway, azC);
connect(toWrite, a1, primary);    // a representative service writes the primary
connect(toRead, a1, replica1);    // reads served from a replica
connect(toCache, a1, cache);
connect(repl1, primary, replica1); // primary replicates to both replicas
connect(repl2, primary, replica2);
connect(toMetrics, b1, metrics);  // services emit telemetry to monitoring

color(dnsToLb, cyan);   color(dnsToLb.hot, cyan);      // request path
color(lbToGw, cyan);    color(lbToGw.hot, cyan);
color(toA, cyan);       color(toA.hot, cyan);
color(toB, cyan);       color(toB.hot, cyan);
color(toC, cyan);       color(toC.hot, cyan);
color(toRead, cyan);    color(toRead.hot, cyan);
color(toCache, cyan);   color(toCache.hot, cyan);
color(toWrite, magenta); color(toWrite.hot, magenta);  // write path
color(repl1, lime);     color(repl1.hot, lime);         // replication
color(repl2, lime);     color(repl2.hot, lime);
color(toMetrics, gold); color(toMetrics.hot, gold);     // telemetry

request(call, dns, "GET /order");

hidden(platform.nodes);
hidden(region.parts); hidden(azA.parts); hidden(azB.parts); hidden(azC.parts);
hidden(database.parts); hidden(observability.parts);
hidden(call.parts);
untraced(dnsToLb); untraced(lbToGw);
untraced(toA); untraced(toB); untraced(toC);
untraced(toWrite); untraced(toRead); untraced(toCache);
untraced(repl1); untraced(repl2); untraced(toMetrics);

step("edge") {
  par {
    stagger(0.08) { show(dns, 0.4); show(lb, 0.4); show(gateway, 0.4); }
    say(caption, "Route 53 resolves the name, an edge load balancer spreads the traffic, the gateway takes it from there.", 0.4);
  }
}
wait(0.2);

step("reveal-zones") {
  par {
    show(region.parts, 0.4);
    show(azA.parts, 0.4); stagger(0.05) { show(a1,0.3); show(a2,0.3); show(a3,0.3); }
    show(azB.parts, 0.4); stagger(0.05) { show(b1,0.3); show(b2,0.3); show(b3,0.3); }
    show(azC.parts, 0.4); stagger(0.05) { show(c1,0.3); show(c2,0.3); show(c3,0.3); }
    say(caption, "Three availability zones, each with its own services — nested clusters, auto-laid.", 0.4);
  }
}
wait(0.2);

step("state-and-monitoring") {
  par {
    show(database.parts, 0.4); show(primary, 0.4); show(replica1, 0.4); show(replica2, 0.4); show(cache, 0.4);
    show(observability.parts, 0.4); show(metrics, 0.4); show(logs, 0.4); show(alarms, 0.4);
    say(caption, "A replicated database cluster and monitoring join in — the whole diagram re-fits.", 0.4);
  }
}
wait(0.25);

step("topology") {
  par {
    stagger(0.06) {
      draw(dnsToLb,0.4); draw(lbToGw,0.4);
      draw(toA,0.4); draw(toB,0.4); draw(toC,0.4);
      draw(toWrite,0.4); draw(toRead,0.4); draw(toCache,0.4);
      draw(repl1,0.4); draw(repl2,0.4); draw(toMetrics,0.4);
    }
    say(caption, "Paths coloured by relationship: request cyan, writes magenta, replication lime, telemetry gold.", 0.4);
  }
}
wait(0.3);

step("one-request") {
  par {
    seq {
      show(call.parts, 0.2);
      route(call, dnsToLb, 0.7, smooth);
      route(call, lbToGw, 0.7, smooth);
      route(call, toA, 0.7, smooth);
      route(call, toWrite, 0.7, smooth);
    }
    say(caption, "One order: Route 53 → load balancer → gateway → an AZ-A service → the primary database.", 0.4);
  }
}
wait(0.3);

step("takeaway") {
  par {
    pulse(region.parts, 0.7);
    pulse(database.parts, 0.7);
    say(caption, "Add a tier, a zone, a replica — you never touch a coordinate. That is manic.", 0.45);
  }
}
wait(1.4);

factorial-flowchart

A flowchart that runs: flowchart(fc) auto-lays seven shape-nodes (terminator/io/process/decision) top-down with no coordinates, then a token walks the factorial loop — taking the yes branch, looping back, and exiting to the end. Node shapes are string kinds; branches are coloured and annotated yes/no/loop, on a clean paper theme with a colour legend.

title("A Flowchart That Runs — the Factorial Loop");
canvas("16:9");
template("paper");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.06), "DIAGRAMS · FLOWCHART · AUTO-LAID");
text(headline, (cx, h*0.12), "A flowchart that runs");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.86);

// `flowchart(fc)` ranks nodes by their connections, auto-fits, and auto-orients:
// a shallow flow like this stays top-down on its own. No coordinates, no direction
// to choose. Node shapes are string kinds.
flowchart(fc);
node(start, fc, "terminator", "start");
node(rd, fc, "io", "read n");
node(init, fc, "process", "f=1  i=1");
node(dec, fc, "decision", "i <= n?");
node(body, fc, "process", "f=f*i  i=i+1");
node(out, fc, "io", "print f");
node(fin, fc, "terminator", "end");

connect(e1, start, rd);
connect(e2, rd, init);
connect(e3, init, dec);
connect(e4, dec, body);                          // keep looping
connect(e5, dec, out);                           // exit
connect(e6, body, dec, orthogonal, right, right); // loop back up the side
connect(e7, out, fin);

color(e4, cyan); color(e4.hot, cyan);
color(e5, lime); color(e5.hot, lime);
color(e6, gold); color(e6.hot, gold);
annotate(e4, "yes"); annotate(e5, "no"); annotate(e6, "loop");

// A colour key stands in for narration — the diagram explains itself.
text(k1, (w*0.34, h*0.93), "— continue"); size(k1, 14*u); color(k1, cyan); bold(k1);
text(k2, (w*0.50, h*0.93), "— exit");     size(k2, 14*u); color(k2, lime); bold(k2);
text(k3, (w*0.66, h*0.93), "— loop back"); size(k3, 14*u); color(k3, gold); bold(k3);

request(tok, start, "n = 3");
hidden(fc.nodes);
hidden(tok.parts);

step("shape") {
  stagger(0.07) {
    show(start, 0.3); show(rd, 0.3); show(init, 0.3); show(dec, 0.3);
    show(body, 0.3); show(out, 0.3); show(fin, 0.3);
  }
}
wait(0.2);

step("run") {
  seq {
    show(tok.parts, 0.2);
    route(tok, e1, 0.4, smooth); route(tok, e2, 0.4, smooth); route(tok, e3, 0.4, smooth);
    route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
    route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
    route(tok, e4, 0.4, smooth); route(tok, e6, 0.5, smooth);
    route(tok, e5, 0.4, smooth); route(tok, e7, 0.4, smooth);
  }
}
wait(0.3);

step("takeaway") {
  seq { pulse(fin, 0.7); pulse(fin, 0.7); }
}
wait(1.0);

complex-flowchart

A big pipeline that builds itself, then runs: a 24-node CI/CD flow with 7 decisions and feedback loops auto-wraps into readable side-by-side columns (long loops routed around the perimeter). It first draws box-by-box in flow order, then pairs of commits race the pipeline in parallel to different outcomes — ship, rollback-to-start, held — twice. All flowchart + route + par, no coordinates.

title("Deployment Pipeline — Many Decisions & Loops");
// Stress test: 7 decisions and several back-edges — some loop to an earlier step,
// one loops all the way back to `start`. `flowchart` still auto-wraps into
// side-by-side columns; forward edges are clean elbows, backward edges (loops,
// column wraps) arc as curves. No coordinates.
canvas("16:9");
template("paper");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.04), "Made With Manic");
text(kicker, (cx, h*0.055), "DIAGRAMS · FLOWCHART · DECISIONS & LOOPS");
text(headline, (cx, h*0.12), "Seven decisions, five loops, one chart");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 23*u); bold(headline); wrap(headline, w*0.86);

flowchart(cd, auto, 40);
node(start, cd, "terminator", "start");
node(checkout, cd, "io", "checkout");
node(install, cd, "process", "install deps");
node(build, cd, "process", "build");
node(bok, cd, "decision", "build ok?");
node(fix, cd, "process", "fix build");
node(unit, cd, "process", "unit tests");
node(uok, cd, "decision", "tests pass?");
node(debug, cd, "process", "debug");
node(integ, cd, "process", "integration");
node(iok, cd, "decision", "integ ok?");
node(scan, cd, "process", "security scan");
node(sok, cd, "decision", "scan clean?");
node(patch, cd, "process", "patch deps");
node(stage, cd, "process", "deploy staging");
node(smoke, cd, "process", "smoke test");
node(smok, cd, "decision", "smoke ok?");
node(approve, cd, "decision", "approve prod?");
node(hold, cd, "terminator", "held");
node(roll, cd, "process", "rollback");
node(prod, cd, "process", "deploy prod");
node(health, cd, "decision", "healthy?");
node(notify, cd, "process", "notify team");
node(done, cd, "terminator", "shipped");

connect(e1, start, checkout);
connect(e2, checkout, install);
connect(e3, install, build);
connect(e4, build, bok);
connect(e5, bok, unit);            // yes
connect(e6, bok, fix);             // no
connect(e7, fix, build);           // loop -> build
connect(e8, unit, uok);
connect(e9, uok, integ);           // yes
connect(e10, uok, debug);          // no
connect(e11, debug, build);        // loop -> build
connect(e12, integ, iok);
connect(e13, iok, scan);           // yes
connect(e14, iok, debug);          // no -> debug (which loops to build)
connect(e15, scan, sok);
connect(e16, sok, stage);          // yes
connect(e17, sok, patch);          // no
connect(e18, patch, scan);         // loop -> scan
connect(e19, stage, smoke);
connect(e20, smoke, smok);
connect(e21, smok, approve);       // yes
connect(e22, smok, roll);          // no
connect(e23, roll, start);         // loop -> start (all the way back)
connect(e24, approve, prod);       // yes
connect(e25, approve, hold);       // no
connect(e26, prod, health);
connect(e27, health, notify);      // yes
connect(e28, health, roll);        // no -> rollback
connect(e29, notify, done);

// forward = proceed, back-edges = loops/failures
color(e6, magenta);  color(e6.hot, magenta);
color(e10, magenta); color(e10.hot, magenta);
color(e14, magenta); color(e14.hot, magenta);
color(e17, magenta); color(e17.hot, magenta);
color(e22, magenta); color(e22.hot, magenta);
color(e28, magenta); color(e28.hot, magenta);
color(e7, gold);  color(e7.hot, gold);
color(e11, gold); color(e11.hot, gold);
color(e18, gold); color(e18.hot, gold);
color(e23, gold); color(e23.hot, gold);
annotate(e5, "yes");  annotate(e6, "no");
annotate(e9, "yes");  annotate(e10, "no");
annotate(e16, "yes"); annotate(e17, "no");
annotate(e21, "yes"); annotate(e22, "no");
annotate(e24, "yes"); annotate(e25, "no");
annotate(e27, "yes"); annotate(e28, "no");

text(k1, (w*0.36, h*0.94), "— proceed"); size(k1, 13*u); color(k1, fg); bold(k1);
text(k2, (w*0.52, h*0.94), "— reject"); size(k2, 13*u); color(k2, magenta); bold(k2);
text(k3, (w*0.66, h*0.94), "— loop back"); size(k3, 13*u); color(k3, gold); bold(k3);

// Two tokens per round, released together — they share the pipeline, then split
// at a decision and reach different ends. Two rounds, then done.
request(a1, start, "PR-A"); request(b1, start, "PR-B");   // round 1
request(a2, start, "PR-C"); request(b2, start, "PR-D");   // round 2
hidden(cd.nodes);
untraced(cd.connections);   // edges start undrawn — we draw them as we build
hidden(a1.parts); hidden(b1.parts); hidden(a2.parts); hidden(b2.parts);

// Build the chart the way you'd draw it: each box appears in flow order and the
// edge into it draws first, so you watch the whole pipeline take shape — then the
// tokens run it. Best way to learn how a flowchart is put together.
step("build-chart") {
  seq {
    show(start, 0.3);
    draw(e1, 0.14);  show(checkout, 0.18);
    draw(e2, 0.14);  show(install, 0.18);
    draw(e3, 0.14);  show(build, 0.18);
    draw(e4, 0.14);  show(bok, 0.18);
    draw(e5, 0.14);  show(unit, 0.18);
    draw(e6, 0.14);  show(fix, 0.18);
    draw(e8, 0.14);  show(uok, 0.18);
    draw(e9, 0.14);  show(integ, 0.18);
    draw(e10, 0.14); show(debug, 0.18);
    draw(e12, 0.14); show(iok, 0.18);
    draw(e13, 0.14); show(scan, 0.18);
    draw(e14, 0.14);                       // iok no → debug (already shown)
    draw(e15, 0.14); show(sok, 0.18);
    draw(e16, 0.14); show(stage, 0.18);
    draw(e17, 0.14); show(patch, 0.18);
    draw(e19, 0.14); show(smoke, 0.18);
    draw(e20, 0.14); show(smok, 0.18);
    draw(e21, 0.14); show(approve, 0.18);
    draw(e22, 0.14); show(roll, 0.18);
    draw(e24, 0.14); show(prod, 0.18);
    draw(e25, 0.14); show(hold, 0.18);
    draw(e26, 0.14); show(health, 0.18);
    draw(e27, 0.14); show(notify, 0.18);
    draw(e28, 0.14);                       // health no → roll (already shown)
    draw(e29, 0.14); show(done, 0.18);
    // feedback loops complete the picture last
    draw(e7, 0.22); draw(e11, 0.22); draw(e18, 0.22); draw(e23, 0.35);
  }
}
wait(0.5);

// Round 1 — two PRs race the pipeline in parallel. They ride together until the
// smoke test, where A ships and B fails and rolls back around to the start.
step("round-1") {
  par {
    seq {
      show(a1.parts, 0.2);
      route(a1, e1, 0.22, smooth); route(a1, e2, 0.22, smooth); route(a1, e3, 0.22, smooth);
      route(a1, e4, 0.22, smooth); route(a1, e5, 0.22, smooth); route(a1, e8, 0.22, smooth);
      route(a1, e9, 0.22, smooth); route(a1, e12, 0.22, smooth); route(a1, e13, 0.22, smooth);
      route(a1, e15, 0.22, smooth); route(a1, e16, 0.22, smooth); route(a1, e19, 0.22, smooth);
      route(a1, e20, 0.22, smooth); route(a1, e21, 0.22, smooth); route(a1, e24, 0.22, smooth);
      route(a1, e26, 0.22, smooth); route(a1, e27, 0.22, smooth); route(a1, e29, 0.3, smooth);
    }
    seq {
      show(b1.parts, 0.2);
      route(b1, e1, 0.22, smooth); route(b1, e2, 0.22, smooth); route(b1, e3, 0.22, smooth);
      route(b1, e4, 0.22, smooth); route(b1, e5, 0.22, smooth); route(b1, e8, 0.22, smooth);
      route(b1, e9, 0.22, smooth); route(b1, e12, 0.22, smooth); route(b1, e13, 0.22, smooth);
      route(b1, e15, 0.22, smooth); route(b1, e16, 0.22, smooth); route(b1, e19, 0.22, smooth);
      route(b1, e20, 0.22, smooth);
      route(b1, e22, 0.35, smooth);  // smoke ok? no → rollback
      route(b1, e23, 0.8, smooth);   // rollback → start (around the perimeter)
    }
  }
}
wait(0.6);
step("clear-1") {
  par { fade(a1.parts, 0.3); fade(b1.parts, 0.3); to(cd.hotpaths, trace, 0, 0.4); }
}
wait(0.3);

// Round 2 — another pair. They split at the unit-test gate: C carries on but is
// held at approval, while D fails its tests and loops back to rebuild.
step("round-2") {
  par {
    seq {
      show(a2.parts, 0.2);
      route(a2, e1, 0.22, smooth); route(a2, e2, 0.22, smooth); route(a2, e3, 0.22, smooth);
      route(a2, e4, 0.22, smooth); route(a2, e5, 0.22, smooth); route(a2, e8, 0.22, smooth);
      route(a2, e9, 0.22, smooth); route(a2, e12, 0.22, smooth); route(a2, e13, 0.22, smooth);
      route(a2, e15, 0.22, smooth); route(a2, e16, 0.22, smooth); route(a2, e19, 0.22, smooth);
      route(a2, e20, 0.22, smooth); route(a2, e21, 0.25, smooth);
      route(a2, e25, 0.35, smooth);  // approve prod? no → held
    }
    seq {
      show(b2.parts, 0.2);
      route(b2, e1, 0.22, smooth); route(b2, e2, 0.22, smooth); route(b2, e3, 0.22, smooth);
      route(b2, e4, 0.22, smooth); route(b2, e5, 0.22, smooth); route(b2, e8, 0.22, smooth);
      route(b2, e10, 0.35, smooth);  // tests pass? no → debug
      route(b2, e11, 0.6, smooth);   // debug → build (loop back)
    }
  }
}
wait(0.6);
step("clear-2") {
  par { fade(a2.parts, 0.3); fade(b2.parts, 0.3); to(cd.hotpaths, trace, 0, 0.4); }
}
wait(1.0);

systems-rabbitmq-consumers

Authored one-of-many delivery: messages 101/102/103 explicitly select different workers — the kit never infers RabbitMQ or round-robin behaviour.

title("RabbitMQ Consumers — One Message, One Worker");
canvas("16:9");
template("mono");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.14, h*0.035), "Made With Manic");
text(kicker, (cx, h*0.055), "SYSTEMS KIT · COMPETING CONSUMERS");
text(headline, (cx, h*0.125), "One queue. Three workers. One owner per message.");
text(caption, (cx, h*0.94), "Cold paths show every legal route; the moving job shows the route selected now.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);

if h > 1.25*w {
  architecture(runtime, (cx, h*0.52), w*0.84, h*0.70);
}
else {
  architecture(runtime, (cx, h*0.52), w*0.90, h*0.68);
}

node(queue, runtime, "queue", "RabbitMQ Queue");

cluster(consumers, runtime, "RABBITMQ CONSUMERS");
node(worker1, consumers, "service", "Pod Worker 1");
node(worker2, consumers, "service", "Pod Worker 2");
node(worker3, consumers, "service", "Pod Worker 3");

node(database, runtime, "database", "Database");

// Every lane is structural possibility. Explicit ports keep the upper and
// lower routes orthogonal without changing the semantic connection identity.
connect(queueToWorker1, queue, worker1, orthogonal, right, left);
connect(queueToWorker2, queue, worker2);
connect(queueToWorker3, queue, worker3, orthogonal, right, left);
connect(worker1ToDatabase, worker1, database, orthogonal, right, left);
connect(worker2ToDatabase, worker2, database);
connect(worker3ToDatabase, worker3, database, orthogonal, right, left);

message(job101, queue, "101");
message(job102, queue, "102");
message(job103, queue, "103");

hidden(kicker); hidden(headline); hidden(caption);
hidden(runtime.nodes); hidden(consumers.parts);
hidden(job101.parts); hidden(job102.parts); hidden(job103.parts);
untraced(runtime.connections);

step("queue-and-consumer-pool") {
  par {
    show(kicker, 0.30); show(headline, 0.45); show(caption, 0.40);
    show(queue, 0.35);
    show(consumers.parts, 0.45);
    stagger(0.10) {
      show(worker1, 0.32);
      show(worker2, 0.32);
      show(worker3, 0.32);
    }
    say(caption, "The queue and three workers are structure only. No delivery policy is inferred from their icons.", 0.40);
  }
}
wait(0.25);

step("shared-database") {
  par {
    show(database, 0.38);
    draw(runtime.connections, 1.10, smooth);
    say(caption, "Each worker can consume from RabbitMQ and write its result to the same database.", 0.40);
  }
}
wait(0.35);

step("message-101-selects-worker-2") {
  par {
    seq {
      show(job101.parts, 0.18);
      route(job101, queueToWorker2, 0.90, linear);
      route(job101, worker2ToDatabase, 0.90, linear);
    }
    say(caption, "Message 101 is delivered to Worker 2 only. The upper and lower consumer routes stay cold.", 0.40);
  }
}
wait(0.35);

step("message-102-selects-worker-1") {
  par {
    fade(job101.parts, 0.20);
    fade(queueToWorker2.hot, 0.25); fade(worker2ToDatabase.hot, 0.25);
    seq {
      show(job102.parts, 0.18);
      route(job102, queueToWorker1, 1.05, linear);
      route(job102, worker1ToDatabase, 1.05, linear);
    }
    say(caption, "The next message can select Worker 1. Orthogonal turns preserve the same moving identity.", 0.40);
  }
}
wait(0.35);

step("message-103-selects-worker-3") {
  par {
    fade(job102.parts, 0.20);
    fade(queueToWorker1.hot, 0.25); fade(worker1ToDatabase.hot, 0.25);
    seq {
      show(job103.parts, 0.18);
      route(job103, queueToWorker3, 1.05, linear);
      route(job103, worker3ToDatabase, 1.05, linear);
    }
    say(caption, "Message 103 selects Worker 3: three jobs were shared, never broadcast as three copies.", 0.40);
  }
}
wait(0.45);

step("takeaway") {
  par {
    pulse(queue, 0.65); pulse(consumers.parts, 0.75); pulse(database, 0.65);
    say(caption, "Manic does not guess RabbitMQ semantics—the creator explicitly authors each selected delivery.", 0.45);
  }
}
wait(1.30);

aws-three-tier-web-application

Presentation / Application / Data as three responsive tiers; one request travels Route 53 → CloudFront → ELB → ECS → ElastiCache → RDS.

title("Designing and Building a Three-Tier Web Application Architecture on AWS");
canvas("16:9");
template("mono");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "AWS ARCHITECTURE · THREE TIERS");
text(headline, (cx, h*0.145), "Designing a Three-Tier Web Application");
text(caption, (cx, h*0.91), "Each tier has one responsibility—and the request keeps moving.");
size(kicker, 17*u); color(kicker, dim); bold(kicker);
size(headline, 32*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.84);

if h > 1.25*w {
  architecture(presentation, (cx, h*0.30), w*0.72, h*0.18);
  architecture(application, (cx, h*0.52), w*0.72, h*0.18);
  architecture(dataTier, (cx, h*0.74), w*0.72, h*0.18);
  text(presentationLabel, (w*0.13, h*0.30), "1 · PRESENTATION");
  text(applicationLabel, (w*0.13, h*0.52), "2 · APPLICATION");
  text(dataLabel, (w*0.13, h*0.74), "3 · DATA");
}
else {
  architecture(presentation, (w*0.20, h*0.50), w*0.27, h*0.55);
  architecture(application, (w*0.50, h*0.50), w*0.27, h*0.55);
  architecture(dataTier, (w*0.80, h*0.50), w*0.27, h*0.55);
  text(presentationLabel, (w*0.20, h*0.235), "1 · PRESENTATION");
  text(applicationLabel, (w*0.50, h*0.235), "2 · APPLICATION");
  text(dataLabel, (w*0.80, h*0.235), "3 · DATA");
}

size(presentationLabel, 16*u); color(presentationLabel, cyan); bold(presentationLabel);
size(applicationLabel, 16*u); color(applicationLabel, magenta); bold(applicationLabel);
size(dataLabel, 16*u); color(dataLabel, gold); bold(dataLabel);

node(dns, presentation, "aws:route53", "Route 53");
node(cdn, presentation, "aws:cloudfront", "CloudFront");
node(balancer, application, "aws:elb", "Load Balancer");
node(app, application, "aws:ecs", "ECS Service");
node(cache, dataTier, "aws:elasticache", "ElastiCache");
node(database, dataTier, "aws:rds", "Amazon RDS");

connect(resolvePath, dns, cdn);
connect(toApplication, cdn, balancer);
connect(toService, balancer, app);
connect(toCache, app, cache);
connect(toDatabase, cache, database);
request(webRequest, dns, "GET /checkout");

hidden(presentation.nodes); hidden(application.nodes); hidden(dataTier.nodes);
untraced(resolvePath); untraced(toApplication); untraced(toService);
untraced(toCache); untraced(toDatabase);
hidden(webRequest.parts);

step("three-responsibilities") {
  par {
    show(presentation.nodes, 0.55);
    say(caption, "Presentation receives users, resolves DNS, and serves content from the edge.", 0.40);
  }
}
wait(0.20);

step("application-tier") {
  par {
    show(application.nodes, 0.55);
    say(caption, "Application distributes traffic and runs stateless business logic.", 0.40);
  }
}
wait(0.20);

step("data-tier") {
  par {
    show(dataTier.nodes, 0.55);
    say(caption, "Data accelerates repeated reads and persists durable records.", 0.40);
  }
}
wait(0.25);

step("connect-the-tiers") {
  par {
    stagger(0.10) {
      draw(resolvePath, 0.35); draw(toApplication, 0.35); draw(toService, 0.35);
      draw(toCache, 0.35); draw(toDatabase, 0.35);
    }
    show(webRequest.parts, 0.30);
    say(caption, "Now follow one checkout request through the complete design.", 0.40);
  }
}
wait(0.20);

step("presentation-flow") {
  par {
    route(webRequest, resolvePath, 0.80, smooth);
    say(caption, "Route 53 resolves the domain; CloudFront brings the experience closer to users.", 0.40);
  }
}

step("load-balance") {
  par {
    route(webRequest, toApplication, 0.95, smooth);
    say(caption, "The load balancer is the controlled entry into the application tier.", 0.40);
  }
}

step("run-the-application") {
  par {
    route(webRequest, toService, 0.80, smooth);
    say(caption, "ECS runs replicated application tasks without mixing them with stored data.", 0.40);
  }
}

step("read-fast") {
  par {
    route(webRequest, toCache, 0.95, smooth);
    say(caption, "ElastiCache answers hot reads quickly and protects the database from repeated work.", 0.40);
  }
}

step("persist-safely") {
  par {
    route(webRequest, toDatabase, 0.80, smooth);
    say(caption, "Amazon RDS remains the durable source of truth for the transaction.", 0.40);
  }
}
wait(0.30);

step("takeaway") {
  par {
    pulse(presentation.nodes, 0.70);
    pulse(application.nodes, 0.70);
    pulse(dataTier.nodes, 0.70);
    say(caption, "Three tiers isolate delivery, computation, and state—so each can scale and evolve independently.", 0.45);
  }
}
wait(1.40);

aws-event-processing-clusters-poc

Nested clusters + parallel topology: EKS → three ECS workers → SQS → three Lambda processors → S3 & Redshift, with one seeded hot path through the fan-out.

title("Event Processing on AWS — From Topology to Hot Path");
canvas("16:9");
template("mono");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.055), "AWS ARCHITECTURE · EVENT PROCESSING");
text(headline, (cx, h*0.115), "One topology. One event. One visible hot path.");
text(caption, (cx, h*0.935), "Dashed relationships show every route the architecture allows.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);

if h > 1.25*w {
  architecture(eventProcessing, (cx, h*0.51), w*0.82, h*0.70);
}
else {
  architecture(eventProcessing, (cx, h*0.51), w*0.92, h*0.69);
}

node(source, eventProcessing, "aws:eks", "K8s Source");
cluster(flows, eventProcessing, "EVENT FLOWS");
cluster(workers, flows, "EVENT WORKERS");
node(worker1, workers, "aws:ecs", "Worker 1");
node(worker2, workers, "aws:ecs", "Worker 2");
node(worker3, workers, "aws:ecs", "Worker 3");
node(queue, flows, "aws:sqs", "Event Queue");
cluster(processing, flows, "PROCESSING");
node(proc1, processing, "aws:lambda", "Processor 1");
node(proc2, processing, "aws:lambda", "Processor 2");
node(proc3, processing, "aws:lambda", "Processor 3");
node(store, eventProcessing, "aws:s3", "Events Store");
node(warehouse, eventProcessing, "aws:redshift", "Analytics");

connect(toWorkers, source, workers);
connect(toQueue, workers, queue);
connect(toHandlers, queue, processing);
connect(toStore, processing, store);
connect(toAnalytics, processing, warehouse);

message(event, source, "EVENT");

hidden(eventProcessing.nodes);
hidden(flows.parts); hidden(workers.parts); hidden(processing.parts);
hidden(event.parts);
untraced(toWorkers); untraced(toQueue); untraced(toHandlers);
untraced(toStore); untraced(toAnalytics);

step("source") {
  par {
    show(source, 0.45);
    say(caption, "EKS is the source; the rest of the system is still only structure.", 0.40);
  }
}
wait(0.25);

step("ownership") {
  par {
    show(flows.parts, 0.50);
    show(workers.parts, 0.45);
    stagger(0.10) { show(worker1, 0.35); show(worker2, 0.35); show(worker3, 0.35); }
    show(queue, 0.40);
    show(processing.parts, 0.45);
    stagger(0.10) { show(proc1, 0.35); show(proc2, 0.35); show(proc3, 0.35); }
    show(store, 0.40); show(warehouse, 0.40);
    say(caption, "Nested boundaries reveal ownership without hiding the individual services.", 0.40);
  }
}
wait(0.20);

step("possible-topology") {
  par {
    draw(toWorkers, 0.55);
    draw(toQueue, 0.55);
    draw(toHandlers, 0.55);
    draw(toStore, 0.55); draw(toAnalytics, 0.55);
    say(caption, "Every dashed edge is a possible relationship—not a claim that data is flowing.", 0.40);
  }
}
wait(0.35);

step("runtime-hot-path") {
  par {
    show(event.parts, 0.25);
    hotpath(event, 6.20, 27);
    say(caption, "The event now chooses valid branches and travels end-to-end as one continuous hot path.", 0.40);
  }
}
wait(0.35);

step("takeaway") {
  par {
    pulse(event.parts, 0.70);
    say(caption, "Cold topology explains relationships. The moving dot proves what happened now.", 0.45);
  }
}
wait(1.40);

aws-clustered-web-services

Route 53 → ELB → ECS pool with an RDS primary/replica cluster and ElastiCache; request and response follow separately coloured lanes.

title("Clustered Web Services on AWS");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.025), "Made With Manic");
text(kicker, (cx, h*0.055), "AWS ARCHITECTURE · CLUSTERED WEB SERVICES");
text(headline, (cx, h*0.115), "Requests in. Responses back.");
text(caption, (cx, h*0.935), "Dashed paths describe what is connected—not what is moving now.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);

if h > 1.25*w {
  architecture(clusteredWeb, (cx, h*0.51), w*0.84, h*0.70);
  text(requestKey, (w*0.36, h*0.225), "REQUEST");
  arrow(requestSample, (w*0.48, h*0.225), (w*0.64, h*0.225));
  text(responseKey, (w*0.36, h*0.265), "RESPONSE");
  arrow(responseSample, (w*0.48, h*0.265), (w*0.64, h*0.265));
}
else {
  architecture(clusteredWeb, (cx, h*0.51), w*0.94, h*0.69);
  text(requestKey, (w*0.105, h*0.205), "REQUEST");
  arrow(requestSample, (w*0.15, h*0.205), (w*0.205, h*0.205));
  text(responseKey, (w*0.105, h*0.245), "RESPONSE");
  arrow(responseSample, (w*0.15, h*0.245), (w*0.205, h*0.245));
}
size(requestKey, 15*u); color(requestKey, fg); bold(requestKey);
size(responseKey, 15*u); hue(responseKey, 328, 0.90, 0.62); bold(responseKey);
color(requestSample, fg); stroke(requestSample, 3*u);
hue(responseSample, 328, 0.90, 0.62); dashed(responseSample, 10*u, 7*u);
stroke(responseSample, 4*u); glow(responseSample, 0.85);
hidden(requestKey); hidden(requestSample);
hidden(responseKey); hidden(responseSample);

node(dns, clusteredWeb, "aws:route53", "dns");
node(lb, clusteredWeb, "aws:elb", "lb");

cluster(services, clusteredWeb, "SERVICES");
node(web1, services, "aws:ecs", "web1");
node(web2, services, "aws:ecs", "web2");
node(web3, services, "aws:ecs", "web3");

cluster(database, clusteredWeb, "DB CLUSTER");
node(dbPrimary, database, "aws:rds", "userdb");
node(dbReplica, database, "aws:rds", "userdb ro");

node(memcached, clusteredWeb, "aws:elasticache", "memcached");

connect(dnsToLb, dns, lb);
connect(lbToServices, lb, services);
connect(servicesToDb, services, dbPrimary);
connect(servicesToCache, services, memcached, 145*u);
connect(dbToServices, dbPrimary, services, 42*u);
connect(servicesToLb, services, lb, 42*u);
connect(cacheToWeb2, memcached, web2, 145*u);
hue(dbToServices.hot, 328, 0.90, 0.62);
hue(servicesToLb.hot, 328, 0.90, 0.62);
hue(cacheToWeb2.hot, 328, 0.90, 0.62);
dashed(dbToServices.hot, 10*u, 7*u);
dashed(servicesToLb.hot, 10*u, 7*u);
dashed(cacheToWeb2.hot, 10*u, 7*u);
stroke(dbToServices.hot, 4.5*u); glow(dbToServices.hot, 0.95);
stroke(servicesToLb.hot, 4.5*u); glow(servicesToLb.hot, 0.95);
stroke(cacheToWeb2.hot, 4.5*u); glow(cacheToWeb2.hot, 0.95);
link(replication, dbPrimary.card, dbReplica.card);
dashed(replication); color(replication, dim); stroke(replication, 2.5*u);

message(pageRequest, dns, "GET");
message(cacheRead, web2, "READ");

hidden(clusteredWeb.nodes);
hidden(services.parts); hidden(database.parts);
hidden(pageRequest.parts); hidden(cacheRead.parts);
untraced(dnsToLb); untraced(lbToServices);
untraced(servicesToDb); untraced(servicesToCache);
untraced(dbToServices); untraced(servicesToLb); untraced(cacheToWeb2);
untraced(replication);

step("entry") {
  par {
    show(dns, 0.40);
    show(lb, 0.40);
    say(caption, "Route 53 and the load balancer form the public entry into the design.", 0.40);
  }
}
wait(0.20);

step("service-pool") {
  par {
    show(services.parts, 0.45);
    stagger(0.10) {
      show(web1, 0.35);
      show(web2, 0.35);
      show(web3, 0.35);
    }
    say(caption, "Three ECS services share one ownership boundary; no runtime choice is implied yet.", 0.40);
  }
}
wait(0.20);

step("state") {
  par {
    show(database.parts, 0.45);
    show(dbPrimary, 0.40);
    show(dbReplica, 0.40);
    show(memcached, 0.40);
    say(caption, "The database cluster and cache complete the static architecture.", 0.40);
  }
}
wait(0.20);

step("possible-topology") {
  par {
    draw(dnsToLb, 0.40);
    draw(lbToServices, 0.60);
    draw(servicesToDb, 0.60);
    draw(servicesToCache, 0.60);
    draw(replication, 0.45);
    show(requestKey, 0.35); show(requestSample, 0.35);
    show(responseKey, 0.35); show(responseSample, 0.35);
    say(caption, "Every dashed lane is possible. The next moving object will identify the path actually used.", 0.40);
  }
}
wait(0.30);

step("request-to-database") {
  par {
    seq {
      show(pageRequest.parts, 0.20);
      route(pageRequest, dnsToLb, 0.65, smooth);
      route(pageRequest, lbToServices, 0.90, smooth);
      route(pageRequest, servicesToDb, 0.90, smooth);
    }
    say(caption, "This request follows one authored lane: dns → lb → web1 → userdb.", 0.40);
  }
}
wait(0.35);

step("database-response") {
  par {
    seq {
      par {
        say(pageRequest.label, "200 OK", 0.25);
        to(pageRequest.parts, hue, 328, 0.25, smooth);
      }
      route(pageRequest, dbToServices, 0.90, smooth);
      route(pageRequest, servicesToLb, 0.90, smooth);
    }
    say(caption, "The same identity returns userdb → web1 → lb. DNS resolved the name; it is not the HTTP response path.", 0.40);
  }
}
wait(0.35);

step("cache-round-trip") {
  par {
    seq {
      show(cacheRead.parts, 0.20);
      route(cacheRead, servicesToCache, 1.05, smooth);
      par {
        say(cacheRead.label, "HIT", 0.25);
        to(cacheRead.parts, hue, 328, 0.25, smooth);
      }
      route(cacheRead, cacheToWeb2, 1.05, smooth);
    }
    say(caption, "A separate cache read travels web2 → memcached, becomes HIT, and returns to that same service.", 0.40);
  }
}
wait(0.35);

step("replica-relationship") {
  par {
    flow(replication, 3.20, both, continuous);
    say(caption, "The neutral database link can carry a two-way visual pulse only because the creator asked for it.", 0.40);
  }
}
wait(0.35);

step("takeaway") {
  par {
    pulse(services.parts, 0.70);
    pulse(database.parts, 0.70);
    say(caption, "Architecture stays readable; objects, paths, timing, and composition tell the runtime story.", 0.45);
  }
}
wait(1.40);

gcp-clustered-web-services

The same clustered-web story on Google Cloud: Cloud DNS → Load Balancing → GKE pool → Cloud SQL cluster + Memorystore — a pure provider swap.

title("Clustered Web Services on Google Cloud");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.025), "Made With Manic");
text(kicker, (cx, h*0.055), "GOOGLE CLOUD ARCHITECTURE · CLUSTERED WEB SERVICES");
text(headline, (cx, h*0.115), "Requests in. Responses back.");
text(caption, (cx, h*0.935), "Dashed paths describe what is connected—not what is moving now.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 29*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);

if h > 1.25*w {
  architecture(clusteredWeb, (cx, h*0.51), w*0.84, h*0.70);
  text(requestKey, (w*0.36, h*0.225), "REQUEST");
  arrow(requestSample, (w*0.48, h*0.225), (w*0.64, h*0.225));
  text(responseKey, (w*0.36, h*0.265), "RESPONSE");
  arrow(responseSample, (w*0.48, h*0.265), (w*0.64, h*0.265));
}
else {
  architecture(clusteredWeb, (cx, h*0.51), w*0.94, h*0.69);
  text(requestKey, (w*0.105, h*0.205), "REQUEST");
  arrow(requestSample, (w*0.15, h*0.205), (w*0.205, h*0.205));
  text(responseKey, (w*0.105, h*0.245), "RESPONSE");
  arrow(responseSample, (w*0.15, h*0.245), (w*0.205, h*0.245));
}
size(requestKey, 15*u); color(requestKey, fg); bold(requestKey);
size(responseKey, 15*u); hue(responseKey, 328, 0.90, 0.62); bold(responseKey);
color(requestSample, fg); stroke(requestSample, 3*u);
hue(responseSample, 328, 0.90, 0.62); dashed(responseSample, 10*u, 7*u);
stroke(responseSample, 4*u); glow(responseSample, 0.85);
hidden(requestKey); hidden(requestSample);
hidden(responseKey); hidden(responseSample);

node(dns, clusteredWeb, "gcp:dns", "cloud dns");
node(lb, clusteredWeb, "gcp:load-balancing", "load balancing");

cluster(services, clusteredWeb, "SERVICES");
node(web1, services, "gcp:kubernetes-engine", "web1");
node(web2, services, "gcp:kubernetes-engine", "web2");
node(web3, services, "gcp:kubernetes-engine", "web3");

cluster(database, clusteredWeb, "DB CLUSTER");
node(dbPrimary, database, "gcp:sql", "userdb");
node(dbReplica, database, "gcp:sql", "userdb ro");

node(cache, clusteredWeb, "gcp:memorystore", "memorystore");

connect(dnsToLb, dns, lb);
connect(lbToServices, lb, services);
connect(servicesToDb, services, dbPrimary);
connect(servicesToCache, services, cache, 145*u);
connect(dbToServices, dbPrimary, services, 42*u);
connect(servicesToLb, services, lb, 42*u);
connect(cacheToWeb2, cache, web2, 145*u);
hue(dbToServices.hot, 328, 0.90, 0.62);
hue(servicesToLb.hot, 328, 0.90, 0.62);
hue(cacheToWeb2.hot, 328, 0.90, 0.62);
dashed(dbToServices.hot, 10*u, 7*u);
dashed(servicesToLb.hot, 10*u, 7*u);
dashed(cacheToWeb2.hot, 10*u, 7*u);
stroke(dbToServices.hot, 4.5*u); glow(dbToServices.hot, 0.95);
stroke(servicesToLb.hot, 4.5*u); glow(servicesToLb.hot, 0.95);
stroke(cacheToWeb2.hot, 4.5*u); glow(cacheToWeb2.hot, 0.95);
link(replication, dbPrimary.card, dbReplica.card);
dashed(replication); color(replication, dim); stroke(replication, 2.5*u);

message(pageRequest, dns, "GET");
message(cacheRead, web2, "READ");

hidden(clusteredWeb.nodes);
hidden(services.parts); hidden(database.parts);
hidden(pageRequest.parts); hidden(cacheRead.parts);
untraced(dnsToLb); untraced(lbToServices);
untraced(servicesToDb); untraced(servicesToCache);
untraced(dbToServices); untraced(servicesToLb); untraced(cacheToWeb2);
untraced(replication);

step("entry") {
  par {
    show(dns, 0.40);
    show(lb, 0.40);
    say(caption, "Cloud DNS and Cloud Load Balancing form the public entry into the design.", 0.40);
  }
}
wait(0.20);

step("service-pool") {
  par {
    show(services.parts, 0.45);
    stagger(0.10) {
      show(web1, 0.35);
      show(web2, 0.35);
      show(web3, 0.35);
    }
    say(caption, "Three GKE services share one ownership boundary; no runtime choice is implied yet.", 0.40);
  }
}
wait(0.20);

step("state") {
  par {
    show(database.parts, 0.45);
    show(dbPrimary, 0.40);
    show(dbReplica, 0.40);
    show(cache, 0.40);
    say(caption, "The Cloud SQL cluster and Memorystore complete the static architecture.", 0.40);
  }
}
wait(0.20);

step("possible-topology") {
  par {
    draw(dnsToLb, 0.40);
    draw(lbToServices, 0.60);
    draw(servicesToDb, 0.60);
    draw(servicesToCache, 0.60);
    draw(replication, 0.45);
    show(requestKey, 0.35); show(requestSample, 0.35);
    show(responseKey, 0.35); show(responseSample, 0.35);
    say(caption, "Every dashed lane is possible. The next moving object will identify the path actually used.", 0.40);
  }
}
wait(0.30);

step("request-to-database") {
  par {
    seq {
      show(pageRequest.parts, 0.20);
      route(pageRequest, dnsToLb, 0.65, smooth);
      route(pageRequest, lbToServices, 0.90, smooth);
      route(pageRequest, servicesToDb, 0.90, smooth);
    }
    say(caption, "This request follows one authored lane: dns → lb → web1 → userdb.", 0.40);
  }
}
wait(0.35);

step("database-response") {
  par {
    seq {
      par {
        say(pageRequest.label, "200 OK", 0.25);
        to(pageRequest.parts, hue, 328, 0.25, smooth);
      }
      route(pageRequest, dbToServices, 0.90, smooth);
      route(pageRequest, servicesToLb, 0.90, smooth);
    }
    say(caption, "The same identity returns userdb → web1 → lb. Cloud DNS resolved the name; it is not the HTTP response path.", 0.40);
  }
}
wait(0.35);

step("cache-round-trip") {
  par {
    seq {
      show(cacheRead.parts, 0.20);
      route(cacheRead, servicesToCache, 1.05, smooth);
      par {
        say(cacheRead.label, "HIT", 0.25);
        to(cacheRead.parts, hue, 328, 0.25, smooth);
      }
      route(cacheRead, cacheToWeb2, 1.05, smooth);
    }
    say(caption, "A separate cache read travels web2 → memorystore, becomes HIT, and returns to that same service.", 0.40);
  }
}
wait(0.35);

step("replica-relationship") {
  par {
    flow(replication, 3.20, both, continuous);
    say(caption, "The neutral database link can carry a two-way visual pulse only because the creator asked for it.", 0.40);
  }
}
wait(0.35);

step("takeaway") {
  par {
    pulse(services.parts, 0.70);
    pulse(database.parts, 0.70);
    say(caption, "Architecture stays readable; objects, paths, timing, and composition tell the runtime story.", 0.45);
  }
}
wait(1.40);

gcp-message-collecting

GCP IoT: three IoT Core devices publish to Pub/Sub, and Dataflow fans out to a data lake, a processing branch, and a serverless branch — three-level nested clusters.

title("Message Collecting on Google Cloud — IoT to Data Lake");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.045), "Made With Manic");
text(kicker, (cx, h*0.05), "GOOGLE CLOUD · IOT MESSAGE COLLECTING");
text(headline, (cx, h*0.11), "Devices publish once. The pipeline fans out.");
text(caption, (cx, h*0.94), "Dashed relationships show every route the architecture allows.");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.88);
size(caption, 17*u); color(caption, dim); wrap(caption, w*0.86);

architecture(collect, (cx, h*0.52), w*0.96, h*0.72);

cluster(sourceData, collect, "SOURCE OF DATA");
node(core1, sourceData, "gcp:iot-core", "core1");
node(core2, sourceData, "gcp:iot-core", "core2");
node(core3, sourceData, "gcp:iot-core", "core3");

node(pubsub, collect, "gcp:pubsub", "pubsub");

cluster(targets, collect, "TARGETS");

cluster(dataFlow, targets, "DATA FLOW");
node(df, dataFlow, "gcp:dataflow", "data flow");

cluster(dataLake, targets, "DATA LAKE");
node(bq, dataLake, "gcp:bigquery", "bq");
node(gcs, dataLake, "gcp:storage", "storage");

cluster(eventDriven, targets, "EVENT DRIVEN");

cluster(processing, eventDriven, "PROCESSING");
node(engine, processing, "gcp:app-engine", "engine");
node(bigtable, processing, "gcp:bigtable", "bigtable");

cluster(serverless, eventDriven, "SERVERLESS");
node(func, serverless, "gcp:functions", "func");
node(appengine, serverless, "gcp:app-engine", "appengine");

// --- topology ---
connect(toPubsub, sourceData, pubsub);   // [core1,core2,core3] >> pubsub
connect(toFlow, pubsub, df);             // pubsub >> flow
connect(toBq, df, bq);                   // flow >> bq
connect(toGcs, df, gcs);                 // flow >> storage
connect(toEngine, df, engine);           // flow >> engine
connect(engineToBt, engine, bigtable);   // engine >> bigtable
connect(toFunc, df, func);               // flow >> func
connect(funcToApp, func, appengine);     // func >> appengine

// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
color(toPubsub, cyan);     color(toPubsub.hot, cyan);
color(toFlow, cyan);       color(toFlow.hot, cyan);
color(toBq, gold);         color(toBq.hot, gold);
color(toGcs, gold);        color(toGcs.hot, gold);
color(toEngine, magenta);  color(toEngine.hot, magenta);
color(engineToBt, magenta);color(engineToBt.hot, magenta);
color(toFunc, lime);       color(toFunc.hot, lime);
color(funcToApp, lime);    color(funcToApp.hot, lime);
// legend
text(legIn, (w*0.22, h*0.175), "INGEST"); size(legIn, 13); color(legIn, cyan); bold(legIn);
text(legLake, (w*0.36, h*0.175), "DATA LAKE"); size(legLake, 13); color(legLake, gold); bold(legLake);
text(legProc, (w*0.54, h*0.175), "PROCESSING"); size(legProc, 13); color(legProc, magenta); bold(legProc);
text(legSls, (w*0.72, h*0.175), "SERVERLESS"); size(legSls, 13); color(legSls, lime); bold(legSls);
hidden(legIn); hidden(legLake); hidden(legProc); hidden(legSls);

message(msg, pubsub, "msg");

hidden(collect.nodes);
hidden(sourceData.parts); hidden(targets.parts); hidden(dataFlow.parts);
hidden(dataLake.parts); hidden(eventDriven.parts); hidden(processing.parts); hidden(serverless.parts);
hidden(msg.parts);
untraced(toPubsub); untraced(toFlow); untraced(toBq); untraced(toGcs);
untraced(toEngine); untraced(engineToBt); untraced(toFunc); untraced(funcToApp);

step("devices") {
  par {
    show(sourceData.parts, 0.45);
    stagger(0.10) { show(core1, 0.35); show(core2, 0.35); show(core3, 0.35); }
    show(pubsub, 0.40);
    say(caption, "Three IoT Core devices publish into one Pub/Sub topic.", 0.40);
  }
}
wait(0.20);

step("targets") {
  par {
    show(targets.parts, 0.5);
    show(dataFlow.parts, 0.4); show(df, 0.4);
    show(dataLake.parts, 0.4); show(bq, 0.4); show(gcs, 0.4);
    show(eventDriven.parts, 0.4);
    show(processing.parts, 0.4); show(engine, 0.4); show(bigtable, 0.4);
    show(serverless.parts, 0.4); show(func, 0.4); show(appengine, 0.4);
    say(caption, "Dataflow is the hub; targets nest into a data lake, a processing branch, and a serverless branch.", 0.40);
  }
}
wait(0.25);

step("possible-topology") {
  par {
    stagger(0.07) {
      draw(toPubsub, 0.4); draw(toFlow, 0.4); draw(toBq, 0.4); draw(toGcs, 0.4);
      draw(toEngine, 0.4); draw(engineToBt, 0.4); draw(toFunc, 0.4); draw(funcToApp, 0.4);
    }
    show(legIn, 0.3); show(legLake, 0.3); show(legProc, 0.3); show(legSls, 0.3);
    say(caption, "Every dashed edge is a possible relationship—not a claim that data is flowing.", 0.40);
  }
}
wait(0.30);

step("collect") {
  par {
    seq {
      show(msg.parts, 0.2);
      route(msg, toFlow, 0.9, smooth);
    }
    say(caption, "A published message reaches Dataflow through Pub/Sub.", 0.40);
  }
}
wait(0.25);

step("fan-out") {
  par {
    flow(toBq, 1.0); flow(toGcs, 1.0);
    seq { flow(toEngine, 0.9); flow(engineToBt, 0.9); }
    seq { flow(toFunc, 0.9); flow(funcToApp, 0.9); }
    say(caption, "Dataflow fans out: BigQuery + storage (lake), App Engine → Bigtable, and Functions → App Engine.", 0.40);
  }
}
wait(0.30);

step("takeaway") {
  par {
    pulse(targets.parts, 0.70);
    say(caption, "One publish; many destinations—each branch owns its own runtime and storage.", 0.45);
  }
}
wait(1.40);

k8s-three-tier

A three-tier app on Kubernetes: Ingress → web Deployment → api Service/Deployment → Redis + a PostgreSQL StatefulSet, following one checkout request.

title("A Three-Tier Application on Kubernetes — Following One Request");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.06), "Made With Manic");
text(kicker, (cx, h*0.065), "KUBERNETES · THREE TIERS · ONE DATA FLOW");
text(headline, (cx, h*0.145), "A Three-Tier App on Kubernetes");
text(caption, (cx, h*0.91), "Ingress in, pods compute, state persists — and the request keeps moving.");
size(kicker, 17*u); color(kicker, dim); bold(kicker);
size(headline, 32*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.84);

if h > 1.25*w {
  architecture(presentation, (cx, h*0.30), w*0.72, h*0.18);
  architecture(application, (cx, h*0.52), w*0.72, h*0.18);
  architecture(dataTier, (cx, h*0.74), w*0.72, h*0.18);
  text(presentationLabel, (w*0.13, h*0.30), "1 · PRESENTATION");
  text(applicationLabel, (w*0.13, h*0.52), "2 · APPLICATION");
  text(dataLabel, (w*0.13, h*0.74), "3 · DATA");
}
else {
  architecture(presentation, (w*0.20, h*0.50), w*0.27, h*0.55);
  architecture(application, (w*0.50, h*0.50), w*0.27, h*0.55);
  architecture(dataTier, (w*0.80, h*0.50), w*0.27, h*0.55);
  text(presentationLabel, (w*0.20, h*0.235), "1 · PRESENTATION");
  text(applicationLabel, (w*0.50, h*0.235), "2 · APPLICATION");
  text(dataLabel, (w*0.80, h*0.235), "3 · DATA");
}

size(presentationLabel, 16*u); color(presentationLabel, cyan); bold(presentationLabel);
size(applicationLabel, 16*u); color(applicationLabel, magenta); bold(applicationLabel);
size(dataLabel, 16*u); color(dataLabel, gold); bold(dataLabel);

node(ingress, presentation, "k8s:ing", "Ingress");
node(web, presentation, "k8s:deploy", "web Deployment x3");
node(apisvc, application, "k8s:svc", "api Service");
node(api, application, "k8s:deploy", "api Deployment x3");
node(cache, dataTier, "onprem:redis", "Redis");
node(database, dataTier, "onprem:postgresql", "PostgreSQL (StatefulSet)");

connect(toWeb, ingress, web);
connect(toApiSvc, web, apisvc);
connect(toApi, apisvc, api);
connect(toCache, api, cache);
connect(toDatabase, cache, database);
request(userRequest, ingress, "GET /checkout");

// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
color(toWeb, cyan);        color(toWeb.hot, cyan);
color(toApiSvc, cyan);     color(toApiSvc.hot, cyan);
color(toApi, cyan);        color(toApi.hot, cyan);
color(toCache, magenta);   color(toCache.hot, magenta);
color(toDatabase, gold);   color(toDatabase.hot, gold);
// legend
text(legReq, (w*0.30, h*0.175), "REQUEST"); size(legReq, 13); color(legReq, cyan); bold(legReq);
text(legCache, (w*0.48, h*0.175), "CACHE"); size(legCache, 13); color(legCache, magenta); bold(legCache);
text(legDb, (w*0.62, h*0.175), "PERSIST"); size(legDb, 13); color(legDb, gold); bold(legDb);
hidden(legReq); hidden(legCache); hidden(legDb);
hidden(presentation.nodes); hidden(application.nodes); hidden(dataTier.nodes);
untraced(toWeb); untraced(toApiSvc); untraced(toApi);
untraced(toCache); untraced(toDatabase);
hidden(userRequest.parts);

step("presentation-tier") {
  par {
    show(presentation.nodes, 0.55);
    say(caption, "Presentation: the Ingress terminates traffic and routes it to the web pods.", 0.40);
  }
}
wait(0.20);

step("application-tier") {
  par {
    show(application.nodes, 0.55);
    say(caption, "Application: a Service load-balances across stateless api pods that scale on demand.", 0.40);
  }
}
wait(0.20);

step("data-tier") {
  par {
    show(dataTier.nodes, 0.55);
    say(caption, "Data: Redis serves hot reads; a PostgreSQL StatefulSet holds durable state.", 0.40);
  }
}
wait(0.25);

step("connect-the-tiers") {
  par {
    stagger(0.10) {
      draw(toWeb, 0.35); draw(toApiSvc, 0.35); draw(toApi, 0.35);
      draw(toCache, 0.35); draw(toDatabase, 0.35);
    }
    show(userRequest.parts, 0.30);
    show(legReq, 0.3); show(legCache, 0.3); show(legDb, 0.3);
    say(caption, "Now follow one checkout request through the whole cluster.", 0.40);
  }
}
wait(0.20);

step("ingress-to-web") {
  par {
    route(userRequest, toWeb, 0.85, smooth);
    say(caption, "The Ingress forwards the request to a web pod via the Service.", 0.40);
  }
}

step("web-to-api") {
  par {
    route(userRequest, toApiSvc, 0.90, smooth);
    say(caption, "The web tier calls the api Service — one controlled entry into business logic.", 0.40);
  }
}

step("into-a-pod") {
  par {
    route(userRequest, toApi, 0.80, smooth);
    say(caption, "The Service load-balances into one replicated api pod.", 0.40);
  }
}

step("read-fast") {
  par {
    route(userRequest, toCache, 0.90, smooth);
    say(caption, "Redis answers the hot read first, shielding the database from repeated work.", 0.40);
  }
}

step("persist-safely") {
  par {
    route(userRequest, toDatabase, 0.85, smooth);
    say(caption, "On a miss, the PostgreSQL StatefulSet is the durable source of truth.", 0.40);
  }
}
wait(0.30);

step("takeaway") {
  par {
    pulse(presentation.nodes, 0.70);
    pulse(application.nodes, 0.70);
    pulse(dataTier.nodes, 0.70);
    say(caption, "Ingress, Services, Deployments and a StatefulSet keep delivery, compute, and state independent.", 0.45);
  }
}
wait(1.40);

k8s-stateful-architecture

A StatefulSet’s storage: a Service, three pods, their PVCs, and the PV/StorageClass that provision them — generated with a for loop, coloured by access/ownership/provisioning.

title("A Stateful Set on Kubernetes — Pods, Claims, and Volumes");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.045), "Made With Manic");
text(kicker, (cx, h*0.05), "KUBERNETES · STATEFUL ARCHITECTURE");
text(headline, (cx, h*0.11), "Every replica keeps its own storage.");
text(caption, (cx, h*0.94), "Dashed relationships show every route the architecture allows.");
size(kicker, 15*u); color(kicker, dim); bold(kicker);
size(headline, 26*u); bold(headline); wrap(headline, w*0.88);
size(caption, 17*u); color(caption, dim); wrap(caption, w*0.86);

architecture(stateful, (cx, h*0.52), w*0.94, h*0.72);

cluster(apps, stateful, "APPS");
node(svc, apps, "k8s:svc", "svc");
node(sts, apps, "k8s:sts", "sts");

cluster(pods, stateful, "PODS");
for i in 0..3 {
  node(pod{i}, pods, "k8s:pod", "pod");
}

cluster(claims, stateful, "CLAIMS");
for i in 0..3 {
  node(pvc{i}, claims, "k8s:pvc", "pvc");
}

cluster(storage, stateful, "STORAGE");
node(pv, storage, "k8s:pv", "pv");
node(sc, storage, "k8s:sc", "sc");

// topology: svc >> pod >> pvc, pod - sts - pvc, and sc >> pv >> pvc (provisioning)
for i in 0..3 {
  connect(svcPod{i}, svc, pod{i});
  connect(podPvc{i}, pod{i}, pvc{i});
  connect(podSts{i}, pod{i}, sts);
  connect(stsPvc{i}, sts, pvc{i});
  connect(pvPvc{i}, pv, pvc{i});
}
connect(scPv, sc, pv);

// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
for i in 0..3 {
  color(svcPod{i}, cyan);      // ACCESS
  color(podPvc{i}, cyan);
  color(podSts{i}, magenta);   // OWNERSHIP
  color(stsPvc{i}, magenta);
  color(pvPvc{i}, gold);       // PROVISIONING
}
color(scPv, gold); color(scPv.hot, gold);
text(legAcc, (w*0.30, h*0.175), "ACCESS"); size(legAcc, 13); color(legAcc, cyan); bold(legAcc);
text(legOwn, (w*0.46, h*0.175), "OWNERSHIP"); size(legOwn, 13); color(legOwn, magenta); bold(legOwn);
text(legProv, (w*0.64, h*0.175), "PROVISIONING"); size(legProv, 13); color(legProv, gold); bold(legProv);
hidden(legAcc); hidden(legOwn); hidden(legProv);

hidden(stateful.nodes);
hidden(apps.parts); hidden(pods.parts); hidden(claims.parts); hidden(storage.parts);
for i in 0..3 {
  untraced(svcPod{i}); untraced(podPvc{i}); untraced(podSts{i});
  untraced(stsPvc{i}); untraced(pvPvc{i});
}
untraced(scPv);

step("apps") {
  par {
    show(apps.parts, 0.5);
    show(svc, 0.4); show(sts, 0.4);
    show(pods.parts, 0.4);
    stagger(0.08) { show(pod0, 0.35); show(pod1, 0.35); show(pod2, 0.35); }
    show(claims.parts, 0.4);
    stagger(0.08) { show(pvc0, 0.35); show(pvc1, 0.35); show(pvc2, 0.35); }
    say(caption, "A Service fronts three pods; a StatefulSet gives each pod a stable identity and claim.", 0.40);
  }
}
wait(0.20);

step("storage") {
  par {
    show(storage.parts, 0.45);
    show(pv, 0.4); show(sc, 0.4);
    say(caption, "A StorageClass provisions PersistentVolumes that back each PersistentVolumeClaim.", 0.40);
  }
}
wait(0.25);

step("possible-topology") {
  par {
    stagger(0.05) {
      draw(svcPod0, 0.35); draw(svcPod1, 0.35); draw(svcPod2, 0.35);
      draw(podPvc0, 0.35); draw(podPvc1, 0.35); draw(podPvc2, 0.35);
      draw(podSts0, 0.35); draw(podSts1, 0.35); draw(podSts2, 0.35);
      draw(stsPvc0, 0.35); draw(stsPvc1, 0.35); draw(stsPvc2, 0.35);
      draw(scPv, 0.35);
      draw(pvPvc0, 0.35); draw(pvPvc1, 0.35); draw(pvPvc2, 0.35);
    }
    show(legAcc, 0.3); show(legOwn, 0.3); show(legProv, 0.3);
    say(caption, "Every dashed edge is a relationship: routing, ownership, and storage binding together.", 0.40);
  }
}
wait(0.30);

step("app-access") {
  par {
    stagger(0.12) {
      seq { flow(svcPod0, 0.7); flow(podPvc0, 0.7); }
      seq { flow(svcPod1, 0.7); flow(podPvc1, 0.7); }
      seq { flow(svcPod2, 0.7); flow(podPvc2, 0.7); }
    }
    say(caption, "Traffic: the Service reaches a pod, which reads and writes its own claim.", 0.40);
  }
}
wait(0.25);

step("provisioning") {
  par {
    seq {
      flow(scPv, 0.8);
      par { flow(pvPvc0, 0.8); flow(pvPvc1, 0.8); flow(pvPvc2, 0.8); }
    }
    say(caption, "Provisioning: the StorageClass creates PVs that bind to each claim.", 0.40);
  }
}
wait(0.30);

step("takeaway") {
  par {
    pulse(apps.parts, 0.7);
    pulse(storage.parts, 0.7);
    say(caption, "Stateful means identity plus durable storage—one claim and volume per replica.", 0.45);
  }
}
wait(1.40);

k8s-cluster-components

The canonical Kubernetes components diagram — control plane (api hub, c-m, c-c-m, etcd, sched) + nodes + the cloud provider — drawn AND flowed with plain primitives, no systems kit at all.

title("Kubernetes Cluster Components");
canvas("16:9");
template("neon");

// ---- boxes (drawn first, behind; hollow-ish so icons read on top) ----
rect(clusterBox, (800, 380), 880, 560);   outline(clusterBox, dim); opacity(clusterBox, 0.22);
rect(cpBox,      (575, 440), 400, 430);    outline(cpBox, cyan);    opacity(cpBox, 0.28);
rect(node1Box,   (1000, 320), 400, 150);   outline(node1Box, dim);  opacity(node1Box, 0.35);
rect(node2Box,   (1000, 520), 400, 150);   outline(node2Box, dim);  opacity(node2Box, 0.35);

text(clusterLabel, (470, 130), "Kubernetes cluster"); size(clusterLabel, 26); bold(clusterLabel);
text(cpLabel, (490, 250), "Control Plane"); size(cpLabel, 20); color(cpLabel, cyan); bold(cpLabel);
text(n1Label, (855, 268), "Node"); size(n1Label, 18); color(n1Label, dim); bold(n1Label);
text(n2Label, (855, 468), "Node"); size(n2Label, 18); color(n2Label, dim); bold(n2Label);

// ---- cloud provider API (outside the cluster, left) ----
ellipse(cloud, (165, 360), 210, 120); outline(cloud, blue); opacity(cloud, 0.30);
text(cloudLabel, (165, 360), "Cloud provider\nAPI"); size(cloudLabel, 20); color(cloudLabel, blue); bold(cloudLabel);

// ---- control-plane connections (api is the hub) ----
line(lCm,   (615, 400), (615, 320));   stroke(lCm, 3);
line(lSched,(615, 485), (615, 560));   stroke(lSched, 3);
line(lEtcd, (590, 465), (505, 500));   stroke(lEtcd, 3);
line(lCcmApi, (505, 375), (580, 425)); stroke(lCcmApi, 3); dashed(lCcmApi, 9, 7);
arrow(aCloud, (440, 360), (270, 360)); stroke(aCloud, 3);
line(lN1, (800, 320), (660, 425));     stroke(lN1, 3);
line(lN2, (800, 520), (660, 450));     stroke(lN2, 3);

// ---- component icons (placed at the reference positions) ----
image(api,   (615, 440), "asset:diagrams/k8s/controlplane/api.png", 62, 62);
image(cm,    (615, 285), "asset:diagrams/k8s/controlplane/c-m.png", 58, 58);
image(sched, (615, 590), "asset:diagrams/k8s/controlplane/sched.png", 58, 58);
image(ccm,   (470, 360), "asset:diagrams/k8s/controlplane/c-c-m.png", 58, 58);
image(etcd,  (470, 510), "asset:diagrams/k8s/infra/etcd.png", 58, 58);
image(kubelet1, (890, 335), "asset:diagrams/k8s/controlplane/kubelet.png", 54, 54);
image(kproxy1,  (1100, 335), "asset:diagrams/k8s/controlplane/k-proxy.png", 54, 54);
image(kubelet2, (890, 535), "asset:diagrams/k8s/controlplane/kubelet.png", 54, 54);
image(kproxy2,  (1100, 535), "asset:diagrams/k8s/controlplane/k-proxy.png", 54, 54);

// ---- icon captions ----
text(tApi, (615, 478), "api"); size(tApi, 15);
text(tCm, (615, 320), "c-m"); size(tCm, 15);
text(tSched, (615, 625), "sched"); size(tSched, 15);
text(tCcm, (470, 395), "c-c-m"); size(tCcm, 15);
text(tEtcd, (470, 545), "etcd"); size(tEtcd, 15);
text(tKl1, (890, 370), "kubelet"); size(tKl1, 14);
text(tKp1, (1100, 370), "k-proxy"); size(tKp1, 14);
text(tKl2, (890, 570), "kubelet"); size(tKl2, 14);
text(tKp2, (1100, 570), "k-proxy"); size(tKp2, 14);

// ---- data flow: ordinary manic verbs on the hand-drawn wires (NO systems kit) ----
text(cap, (cx, h*0.95), "Every component talks through the API server."); size(cap, 20); color(cap, dim); wrap(cap, w*0.8);
dot(packet, (800, 320), 11); color(packet, lime); hidden(packet);

wait(0.5);
par {
  flow(lN1, 1.0); flow(lN2, 1.0);
  say(cap, "kubelets report node and pod status up to the API server", 0.4);
}
par {
  flow(lCm, 1.0); flow(lSched, 1.0);
  say(cap, "the scheduler and controllers act only through the API server", 0.4);
}
par {
  flow(lEtcd, 1.0);
  say(cap, "the API server reads and writes all cluster state in etcd", 0.4);
}
par {
  flow(aCloud, 1.0);
  say(cap, "the cloud-controller-manager provisions infrastructure via the cloud API", 0.4);
}
wait(0.2);
par {
  show(packet, 0.2);
  say(cap, "one node update: kubelet -> API server -> etcd", 0.4);
}
seq {
  travel(packet, lN1, 0.9, smooth);
  travel(packet, lEtcd, 0.9, smooth);
}
pulse(api, 0.7);
wait(1.4);

serverless-processing

A mixed on-prem + AWS pipeline: Kafka + Docker engines → SQS (+ dead-letter) → Lambda → S3/Redshift, with a Fluentd→Kafka→Spark tap; paths coloured by relationship.

title("Serverless Event Processing — From Kafka to Cold Storage");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.15, h*0.055), "Made With Manic");
text(kicker, (cx, h*0.055), "SERVERLESS · EVENT PROCESSING · MIXED STACK");
text(headline, (cx, h*0.115), "Events in. Results, dead letters, and cold storage out.");
text(caption, (cx, h*0.935), "Dashed relationships show every route the architecture allows.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 27*u); bold(headline); wrap(headline, w*0.88);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);

if h > 1.25*w {
  architecture(pipeline, (cx, h*0.52), w*0.86, h*0.72);
}
else {
  architecture(pipeline, (cx, h*0.52), w*0.94, h*0.70);
}

// --- the ingress + event source + a bold analytics tap (on-prem stack) ---
cluster(ingest, pipeline, "INGEST");
node(ingress, ingest, "onprem:fluentd", "logging");
node(source, ingest, "onprem:kafka", "Event-Sourcing");
node(spark, ingest, "onprem:spark", "Spark");

cluster(flows, pipeline, "EVENT FLOWS");

cluster(engines, flows, "DATA PROCESSING ENGINES");
node(clicks, engines, "onprem:docker", "click stream");
node(logins, engines, "onprem:docker", "user logins");
node(analytics, engines, "onprem:docker", "data analytics");

node(queue, flows, "aws:sqs", "results queue");
node(dlq, flows, "aws:sqs", "Dead Letter Queue");

cluster(processing, flows, "PROCESSING");
node(persist, processing, "aws:lambda", "persist results");

cluster(storage, flows, "STORAGE");
node(store, storage, "aws:s3", "cold storage");
node(dw, storage, "aws:redshift", "operational storage");

// --- topology (Mingrammer edges) ---
connect(parse, ingress, source);          // ingress >> Edge("parse") >> source
connect(toSpark, source, spark);          // source >> Edge(bold) >> Spark
connect(toEngines, source, engines);      // source >> workers
connect(toQueue, engines, queue);         // workers >> queue
connect(toHandlers, queue, processing);   // queue >> handlers
connect(toDlq, queue, dlq, 120*u);        // queue >> dlq
connect(toStore, processing, store);      // handlers >> store
connect(toDw, processing, dw);            // handlers >> dw

// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
color(parse, cyan);        color(parse.hot, cyan);
color(toEngines, cyan);    color(toEngines.hot, cyan);
color(toQueue, cyan);      color(toQueue.hot, cyan);
color(toHandlers, cyan);   color(toHandlers.hot, cyan);
color(toSpark, magenta);   color(toSpark.hot, magenta);
color(toStore, gold);      color(toStore.hot, gold);
color(toDw, gold);         color(toDw.hot, gold);
color(toDlq, red);         color(toDlq.hot, red);
// legend
text(legPipe, (w*0.24, h*0.175), "PIPELINE"); size(legPipe, 13); color(legPipe, cyan); bold(legPipe);
text(legAna, (w*0.40, h*0.175), "ANALYTICS"); size(legAna, 13); color(legAna, magenta); bold(legAna);
text(legStore, (w*0.58, h*0.175), "STORAGE"); size(legStore, 13); color(legStore, gold); bold(legStore);
text(legDlq, (w*0.74, h*0.175), "DEAD-LETTER"); size(legDlq, 13); color(legDlq, red); bold(legDlq);
hidden(legPipe); hidden(legAna); hidden(legStore); hidden(legDlq);
message(event, source, "event");

hidden(pipeline.nodes);
hidden(ingest.parts); hidden(flows.parts); hidden(engines.parts); hidden(processing.parts); hidden(storage.parts);
hidden(event.parts);
untraced(parse); untraced(toSpark); untraced(toEngines); untraced(toQueue);
untraced(toHandlers); untraced(toDlq); untraced(toStore); untraced(toDw);

step("ingest") {
  par {
    show(ingest.parts, 0.45);
    show(ingress, 0.40);
    show(source, 0.40);
    show(spark, 0.40);
    say(caption, "Fluentd parses raw logs into Kafka; a bold tap also streams the source into Spark.", 0.40);
  }
}
wait(0.20);

step("event-flows") {
  par {
    show(flows.parts, 0.50);
    show(engines.parts, 0.45);
    stagger(0.10) { show(clicks, 0.35); show(logins, 0.35); show(analytics, 0.35); }
    show(queue, 0.40); show(dlq, 0.40);
    show(processing.parts, 0.45); show(persist, 0.40);
    show(storage.parts, 0.45); show(store, 0.40); show(dw, 0.40);
    say(caption, "Docker engines process the stream; a queue, a dead-letter queue, a Lambda, and storage complete it.", 0.40);
  }
}
wait(0.20);

step("possible-topology") {
  par {
    stagger(0.08) {
      draw(parse, 0.4); draw(toEngines, 0.4); draw(toQueue, 0.4); draw(toHandlers, 0.4);
      draw(toDlq, 0.4); draw(toStore, 0.4); draw(toDw, 0.4); draw(toSpark, 0.4);
    }
    show(legPipe, 0.3); show(legAna, 0.3); show(legStore, 0.3); show(legDlq, 0.3);
    say(caption, "Every dashed edge is a possible relationship—not a claim that data is flowing.", 0.40);
  }
}
wait(0.30);

step("ingress-flow") {
  par {
    seq { flow(parse, 1.0); flow(toSpark, 1.0); }
    say(caption, "Logging → parse → Kafka → Spark: the ingest-and-analytics tap.", 0.40);
  }
}
wait(0.20);

step("runtime-hot-path") {
  par {
    show(event.parts, 0.25);
    hotpath(event, 6.0, 27);
    say(caption, "One event chooses valid branches: Kafka → an engine → results queue → Lambda → storage.", 0.40);
  }
}
wait(0.30);

step("dead-letter") {
  par {
    flow(toDlq, 1.2);
    say(caption, "Messages the handler cannot process are diverted to the dead-letter queue.", 0.40);
  }
}
wait(0.30);

step("takeaway") {
  par {
    pulse(flows.parts, 0.70);
    pulse(event.parts, 0.60);
    say(caption, "One mixed stack—on-prem ingest, serverless compute, cloud storage—stays readable end to end.", 0.45);
  }
}
wait(1.40);

onprem-advanced-web-service

The on-prem stress test with native archetypes: Nginx, gRPC, Redis/PostgreSQL HA, Fluentd→Kafka→Spark, and Prometheus/Grafana — three runtime stories on one platform.

title("Advanced On-Premises Web Service — Runtime Paths");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.14, h*0.03), "Made With Manic");
text(kicker, (cx, h*0.04), "SYSTEMS KIT · ON-PREMISES WEB SERVICE");
text(headline, (cx, h*0.125), "One platform. Three runtime stories.");
text(caption, (cx, h*0.945), "First reveal ownership; then distinguish requests, telemetry, and the analytics stream.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 30*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);

if h > 1.25*w {
  architecture(platform, (cx,h*0.52), w*0.84,h*0.72);
}
else {
  architecture(platform, (cx,h*0.52), w*0.95,h*0.72);
}

node(ingress, platform, "gateway", "Nginx Ingress");

cluster(services, platform, "SERVICE CLUSTER");
node(grpc1, services, "service", "gRPC 1");
node(grpc2, services, "service", "gRPC 2");
node(grpc3, services, "service", "gRPC 3");

cluster(state, platform, "");
cluster(sessions, state, "SESSIONS HA");
node(sessionPrimary, sessions, "cache", "Redis Session");
node(sessionReplica, sessions, "cache", "Redis Replica");
cluster(databases, state, "DATABASE HA");
node(dbPrimary, databases, "database", "PostgreSQL Users");
node(dbReplica, databases, "database", "PostgreSQL Replica");

cluster(pipeline, platform, "EVENT ANALYTICS");
node(logging, pipeline, "service", "Fluentd Logging");
node(stream, pipeline, "queue", "Kafka Stream");
node(analytics, pipeline, "service", "Spark Analytics");

cluster(observability, platform, "OBSERVABILITY");
node(metrics, observability, "external", "Prometheus");
node(monitoring, observability, "external", "Grafana");

// Main request and dependency possibilities.
connect(ingressToServices, ingress, services);
connect(servicesToSessions, services, sessionPrimary, orthogonal);
connect(servicesToDatabase, services, dbPrimary, orthogonal);
connect(servicesToLogging, services, logging, orthogonal);

// The log stream remains one explicit pipeline.
connect(loggingToStream, logging, stream, orthogonal);
connect(streamToAnalytics, stream, analytics, orthogonal);

// Grafana queries Prometheus; Prometheus explicitly scrapes both replicas.
connect(dashboardQuery, monitoring, metrics, orthogonal);
connect(sessionScrape, metrics, sessionReplica, orthogonal);
connect(databaseScrape, metrics, dbReplica, orthogonal);

// HA replication is a neutral relationship, not a directed runtime claim.
link(sessionReplication, sessionPrimary.card, sessionReplica.card);
link(databaseReplication, dbPrimary.card, dbReplica.card);
dashed(sessionReplication); dashed(databaseReplication);
color(sessionReplication, dim); color(databaseReplication, dim);
stroke(sessionReplication, 2.5*u); stroke(databaseReplication, 2.5*u);

message(webRequest, ingress, "REQ");
message(sessionRead, grpc2, "S");
message(databaseRead, grpc3, "DB");
message(logEvent, grpc1, "LOG");
message(query, monitoring, "Q");
message(sessionProbe, metrics, "S");
message(databaseProbe, metrics, "D");

hidden(kicker); hidden(headline); hidden(caption);
hidden(platform.nodes);
hidden(services.parts); hidden(state.parts); hidden(sessions.parts); hidden(databases.parts);
hidden(pipeline.parts); hidden(observability.parts);
hidden(webRequest.parts); hidden(sessionRead.parts); hidden(databaseRead.parts);
hidden(logEvent.parts); hidden(query.parts); hidden(sessionProbe.parts); hidden(databaseProbe.parts);
untraced(platform.connections); untraced(sessionReplication); untraced(databaseReplication);

step("entry-and-services") {
  par {
    show(kicker, 0.30); show(headline, 0.45); show(caption, 0.40);
    show(ingress, 0.35);
    show(services.parts, 0.45);
    stagger(0.08) { show(grpc1, 0.30); show(grpc2, 0.30); show(grpc3, 0.30); }
    say(caption, "Nginx can reach three gRPC workers. The dashed fan-out is possibility, not a load-balancing simulation.", 0.40);
  }
}
wait(0.20);

step("state-pipeline-and-monitoring") {
  par {
    show(state.parts, 0.45); show(sessions.parts, 0.40); show(databases.parts, 0.40);
    show(sessionPrimary, 0.30); show(sessionReplica, 0.30);
    show(dbPrimary, 0.30); show(dbReplica, 0.30);
    show(pipeline.parts, 0.45); show(logging, 0.30); show(stream, 0.30); show(analytics, 0.30);
    show(observability.parts, 0.45); show(metrics, 0.30); show(monitoring, 0.30);
    say(caption, "Sessions, users, analytics, and observability keep separate ownership boundaries.", 0.40);
  }
}
wait(0.20);

step("possible-topology") {
  par {
    draw(platform.connections, 1.10, smooth);
    draw(sessionReplication, 0.55, smooth);
    draw(databaseReplication, 0.55, smooth);
    say(caption, "Straight, curved, and port-aware orthogonal paths still form one selectable topology.", 0.40);
  }
}
wait(0.30);

step("one-ingress-request") {
  par {
    seq {
      show(webRequest.parts, 0.18);
      route(webRequest, ingressToServices, 1.00, linear);
    }
    say(caption, "One HTTP request selects one gRPC worker; the other possible ingress lanes remain cold.", 0.40);
  }
}
wait(0.25);

step("parallel-state-dependencies") {
  par {
    fade(webRequest.parts, 0.20);
    seq { show(sessionRead.parts, 0.18); route(sessionRead, servicesToSessions, 1.45, linear); }
    seq { show(databaseRead.parts, 0.18); route(databaseRead, servicesToDatabase, 1.45, linear); }
    flow(sessionReplication, 1.45, both, continuous);
    flow(databaseReplication, 1.45, both, continuous);
    say(caption, "Two workers can contact independent state systems while replication stays a neutral HA relationship.", 0.40);
  }
}
wait(0.25);

step("logging-to-analytics") {
  par {
    fade(sessionRead.parts, 0.20); fade(databaseRead.parts, 0.20);
    fade(servicesToSessions.hot, 0.25); fade(servicesToDatabase.hot, 0.25);
    seq {
      show(logEvent.parts, 0.18);
      route(logEvent, servicesToLogging, 1.00, linear);
      route(logEvent, loggingToStream, 0.85, linear);
      route(logEvent, streamToAnalytics, 0.85, linear);
    }
    say(caption, "The same log event travels gRPC → Fluentd → Kafka → Spark without becoming a replacement dot.", 0.40);
  }
}
wait(0.25);

step("observability") {
  par {
    fade(logEvent.parts, 0.20);
    fade(servicesToLogging.hot, 0.25);
    fade(loggingToStream.hot, 0.25);
    fade(streamToAnalytics.hot, 0.25);
    seq { show(query.parts, 0.18); route(query, dashboardQuery, 0.80, linear); }
    seq { show(sessionProbe.parts, 0.18); route(sessionProbe, sessionScrape, 1.25, linear); }
    seq { show(databaseProbe.parts, 0.18); route(databaseProbe, databaseScrape, 1.25, linear); }
    say(caption, "Grafana queries Prometheus while two explicit scrape messages select the replica targets.", 0.40);
  }
}
wait(0.35);

step("takeaway") {
  par {
    pulse(services.parts, 0.70); pulse(state.parts, 0.70);
    pulse(pipeline.parts, 0.70); pulse(observability.parts, 0.70);
    say(caption, "Ownership stays static. Persistent objects and authored paths explain what the platform does.", 0.45);
  }
}
wait(1.30);

onprem-advanced-web-service-v2

The same platform with real provider icons and paths coloured by relationship (request · analytics · telemetry · replication).

title("Advanced On-Premises Web Service — Runtime Paths (with icons)");
canvas("16:9");
template("neon");

let u = (w+h-abs(w-h))/1440;
watermark(mark, (w*0.14, h*0.03), "Made With Manic");
text(kicker, (cx, h*0.04), "SYSTEMS KIT · ON-PREMISES WEB SERVICE");
text(headline, (cx, h*0.125), "One platform. Three runtime stories.");
text(caption, (cx, h*0.945), "First reveal ownership; then distinguish requests, telemetry, and the analytics stream.");
size(kicker, 16*u); color(kicker, dim); bold(kicker);
size(headline, 30*u); bold(headline); wrap(headline, w*0.86);
size(caption, 18*u); color(caption, dim); wrap(caption, w*0.86);

if h > 1.25*w {
  architecture(platform, (cx,h*0.52), w*0.84,h*0.72);
}
else {
  architecture(platform, (cx,h*0.52), w*0.95,h*0.72);
}

node(ingress, platform, "onprem:nginx", "Nginx Ingress");

cluster(services, platform, "SERVICE CLUSTER");
node(grpc1, services, "onprem:server", "gRPC 1");
node(grpc2, services, "onprem:server", "gRPC 2");
node(grpc3, services, "onprem:server", "gRPC 3");

cluster(state, platform, "");
cluster(sessions, state, "SESSIONS HA");
node(sessionPrimary, sessions, "onprem:redis", "Redis Session");
node(sessionReplica, sessions, "onprem:redis", "Redis Replica");
cluster(databases, state, "DATABASE HA");
node(dbPrimary, databases, "onprem:postgresql", "PostgreSQL Users");
node(dbReplica, databases, "onprem:postgresql", "PostgreSQL Replica");

cluster(pipeline, platform, "EVENT ANALYTICS");
node(logging, pipeline, "onprem:fluentd", "Fluentd Logging");
node(stream, pipeline, "onprem:kafka", "Kafka Stream");
node(analytics, pipeline, "onprem:spark", "Spark Analytics");

cluster(observability, platform, "OBSERVABILITY");
node(metrics, observability, "onprem:prometheus", "Prometheus");
node(monitoring, observability, "onprem:grafana", "Grafana");

// Main request and dependency possibilities.
connect(ingressToServices, ingress, services);
connect(servicesToSessions, services, sessionPrimary, orthogonal);
connect(servicesToDatabase, services, dbPrimary, orthogonal);
connect(servicesToLogging, services, logging, orthogonal);

// The log stream remains one explicit pipeline.
connect(loggingToStream, logging, stream, orthogonal);
connect(streamToAnalytics, stream, analytics, orthogonal);

// Grafana queries Prometheus; Prometheus explicitly scrapes both replicas.
connect(dashboardQuery, monitoring, metrics, orthogonal);
connect(sessionScrape, metrics, sessionReplica, orthogonal);
connect(databaseScrape, metrics, dbReplica, orthogonal);

// HA replication is a neutral relationship, not a directed runtime claim.
link(sessionReplication, sessionPrimary.card, sessionReplica.card);
link(databaseReplication, dbPrimary.card, dbReplica.card);
dashed(sessionReplication); dashed(databaseReplication);
color(sessionReplication, dim); color(databaseReplication, dim);
stroke(sessionReplication, 2.5*u); stroke(databaseReplication, 2.5*u);

// --- colour every path by its RELATIONSHIP (cold dashed line + hot overlay) ---
// request path = cyan · analytics pipeline = gold · telemetry/scrape = magenta.
color(ingressToServices, cyan);  color(ingressToServices.hot, cyan);
color(servicesToSessions, cyan); color(servicesToSessions.hot, cyan);
color(servicesToDatabase, cyan); color(servicesToDatabase.hot, cyan);
color(servicesToLogging, gold);  color(servicesToLogging.hot, gold);
color(loggingToStream, gold);    color(loggingToStream.hot, gold);
color(streamToAnalytics, gold);  color(streamToAnalytics.hot, gold);
color(dashboardQuery, magenta);  color(dashboardQuery.hot, magenta);
color(sessionScrape, magenta);   color(sessionScrape.hot, magenta);
color(databaseScrape, magenta);  color(databaseScrape.hot, magenta);
// (replication links stay neutral dim — a relationship, not a runtime path)

// legend: relationship → colour
text(legReq, (w*0.30, h*0.175), "REQUEST"); size(legReq, 13*u); color(legReq, cyan); bold(legReq);
text(legAna, (w*0.44, h*0.175), "ANALYTICS"); size(legAna, 13*u); color(legAna, gold); bold(legAna);
text(legTel, (w*0.60, h*0.175), "TELEMETRY"); size(legTel, 13*u); color(legTel, magenta); bold(legTel);
text(legRep, (w*0.76, h*0.175), "REPLICATION"); size(legRep, 13*u); color(legRep, dim); bold(legRep);
hidden(legReq); hidden(legAna); hidden(legTel); hidden(legRep);

message(webRequest, ingress, "REQ");
message(sessionRead, grpc2, "S");
message(databaseRead, grpc3, "DB");
message(logEvent, grpc1, "LOG");
message(query, monitoring, "Q");
message(sessionProbe, metrics, "S");
message(databaseProbe, metrics, "D");

hidden(kicker); hidden(headline); hidden(caption);
hidden(platform.nodes);
hidden(services.parts); hidden(state.parts); hidden(sessions.parts); hidden(databases.parts);
hidden(pipeline.parts); hidden(observability.parts);
hidden(webRequest.parts); hidden(sessionRead.parts); hidden(databaseRead.parts);
hidden(logEvent.parts); hidden(query.parts); hidden(sessionProbe.parts); hidden(databaseProbe.parts);
untraced(platform.connections); untraced(sessionReplication); untraced(databaseReplication);

step("entry-and-services") {
  par {
    show(kicker, 0.30); show(headline, 0.45); show(caption, 0.40);
    show(ingress, 0.35);
    show(services.parts, 0.45);
    stagger(0.08) { show(grpc1, 0.30); show(grpc2, 0.30); show(grpc3, 0.30); }
    say(caption, "Nginx can reach three gRPC workers. The dashed fan-out is possibility, not a load-balancing simulation.", 0.40);
  }
}
wait(0.20);

step("state-pipeline-and-monitoring") {
  par {
    show(state.parts, 0.45); show(sessions.parts, 0.40); show(databases.parts, 0.40);
    show(sessionPrimary, 0.30); show(sessionReplica, 0.30);
    show(dbPrimary, 0.30); show(dbReplica, 0.30);
    show(pipeline.parts, 0.45); show(logging, 0.30); show(stream, 0.30); show(analytics, 0.30);
    show(observability.parts, 0.45); show(metrics, 0.30); show(monitoring, 0.30);
    say(caption, "Sessions, users, analytics, and observability keep separate ownership boundaries.", 0.40);
  }
}
wait(0.20);

step("possible-topology") {
  par {
    draw(platform.connections, 1.10, smooth);
    draw(sessionReplication, 0.55, smooth);
    draw(databaseReplication, 0.55, smooth);
    show(legReq, 0.3); show(legAna, 0.3); show(legTel, 0.3); show(legRep, 0.3);
    say(caption, "Each path is coloured by relationship: request, analytics, telemetry, replication.", 0.40);
  }
}
wait(0.30);

step("one-ingress-request") {
  par {
    seq {
      show(webRequest.parts, 0.18);
      route(webRequest, ingressToServices, 1.00, linear);
    }
    say(caption, "One HTTP request selects one gRPC worker; the other possible ingress lanes remain cold.", 0.40);
  }
}
wait(0.25);

step("parallel-state-dependencies") {
  par {
    fade(webRequest.parts, 0.20);
    seq { show(sessionRead.parts, 0.18); route(sessionRead, servicesToSessions, 1.45, linear); }
    seq { show(databaseRead.parts, 0.18); route(databaseRead, servicesToDatabase, 1.45, linear); }
    flow(sessionReplication, 1.45, both, continuous);
    flow(databaseReplication, 1.45, both, continuous);
    say(caption, "Two workers can contact independent state systems while replication stays a neutral HA relationship.", 0.40);
  }
}
wait(0.25);

step("logging-to-analytics") {
  par {
    fade(sessionRead.parts, 0.20); fade(databaseRead.parts, 0.20);
    fade(servicesToSessions.hot, 0.25); fade(servicesToDatabase.hot, 0.25);
    seq {
      show(logEvent.parts, 0.18);
      route(logEvent, servicesToLogging, 1.00, linear);
      route(logEvent, loggingToStream, 0.85, linear);
      route(logEvent, streamToAnalytics, 0.85, linear);
    }
    say(caption, "The same log event travels gRPC → Fluentd → Kafka → Spark without becoming a replacement dot.", 0.40);
  }
}
wait(0.25);

step("observability") {
  par {
    fade(logEvent.parts, 0.20);
    fade(servicesToLogging.hot, 0.25);
    fade(loggingToStream.hot, 0.25);
    fade(streamToAnalytics.hot, 0.25);
    seq { show(query.parts, 0.18); route(query, dashboardQuery, 0.80, linear); }
    seq { show(sessionProbe.parts, 0.18); route(sessionProbe, sessionScrape, 1.25, linear); }
    seq { show(databaseProbe.parts, 0.18); route(databaseProbe, databaseScrape, 1.25, linear); }
    say(caption, "Grafana queries Prometheus while two explicit scrape messages select the replica targets.", 0.40);
  }
}
wait(0.35);

step("takeaway") {
  par {
    pulse(services.parts, 0.70); pulse(state.parts, 0.70);
    pulse(pipeline.parts, 0.70); pulse(observability.parts, 0.70);
    say(caption, "Ownership stays static. Persistent objects and authored paths explain what the platform does.", 0.45);
  }
}
wait(1.30);