---
name: "svg-architecture-diagram"
description: "Interactive drag-and-drop system architecture diagram builder. Pre-loaded with a microservices example. Add nodes, drag to rearrange, and export clean SVG."
metadata:
  version: "1.0.0"
  runtime:
    entrypoint: "arch-diagram.html"
---

# SVG Architecture Diagram

> **Purpose:** An interactive canvas for visualizing system architecture. Pre-loaded with a microservices example (Browser → API Gateway → Auth/Skills services → PostgreSQL/Redis). Drag nodes to rearrange, double-click to rename, add new nodes from the toolbar, and export the result as a clean SVG file.

---

## How to Use

1. **Drag nodes** — click and drag any service box to reposition it
2. **Double-click a node** — rename it inline
3. **Add Node button** — cycles through Client, Service, Database, Cache, External types
4. **Export SVG** — downloads the current diagram as `architecture.svg`

---

## Node Types

| Type | Color | Use For |
|------|-------|---------|
| Client | Blue | Browser, mobile app, CLI |
| Service | Purple | API servers, microservices |
| Database | Green | PostgreSQL, MySQL, SQLite |
| Cache | Amber | Redis, Memcached, CDN |
| External | Red | Third-party APIs, Stripe, AWS |

---

## Connections

Edges are drawn automatically based on a predefined connection list. The edge labels show the protocol (HTTPS, REST, SQL, etc.).

---

## Export Format

The exported SVG is self-contained and renders cleanly in browsers, Figma, Notion, and GitHub READMEs.


---

## Files

### `arch-diagram.html`

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Architecture Diagram</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0d0d1a; color: #e2e8f0; font-family: system-ui, sans-serif; height: 100vh; display: flex; flex-direction: column; }
header { padding: 12px 20px; background: #1a1a2e; border-bottom: 1px solid #2d2d4e; display: flex; align-items: center; gap: 16px; }
h1 { font-size: 15px; font-weight: 700; color: #a78bfa; }
.toolbar { display: flex; gap: 8px; margin-left: auto; }
button { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 700; }
.btn-add { background: #6d28d9; color: #e9d5ff; }
.btn-export { background: #065f46; color: #6ee7b7; }
.canvas-wrap { flex: 1; position: relative; overflow: hidden; background: #080810; }
svg#diagram { width: 100%; height: 100%; cursor: default; }
.node { cursor: grab; }
.node:active { cursor: grabbing; }
.legend { position: absolute; bottom: 14px; left: 14px; background: rgba(20,20,40,0.95); border: 1px solid #2d2d4e; border-radius: 8px; padding: 10px 14px; font-size: 11px; }
.legend-title { font-weight: 700; color: #a78bfa; margin-bottom: 7px; font-size: 11px; }
.li { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; color: #94a3b8; }
.dot { width: 10px; height: 10px; border-radius: 3px; }
.tip { position: absolute; top: 14px; right: 14px; font-size: 11px; color: #4b5563; background: rgba(20,20,40,0.8); padding: 8px 12px; border-radius: 8px; border: 1px solid #1e1e3a; }
</style>
</head>
<body>
<header>
  <h1>&#9651; System Architecture Diagram</h1>
  <span style="font-size:11px;color:#4b5563">Drag nodes &#8226; Double-click to rename &#8226; Add &#38; Export</span>
  <div class="toolbar">
    <button class="btn-add" onclick="addNode()">+ Add Node</button>
    <button class="btn-export" onclick="exportSVG()">Export SVG</button>
  </div>
</header>
<div class="canvas-wrap">
  <svg id="diagram" viewBox="0 0 940 530"></svg>
  <div class="legend">
    <div class="legend-title">Node Types</div>
    <div class="li"><div class="dot" style="background:#1d4ed8"></div>Client</div>
    <div class="li"><div class="dot" style="background:#6d28d9"></div>Service</div>
    <div class="li"><div class="dot" style="background:#065f46"></div>Database</div>
    <div class="li"><div class="dot" style="background:#92400e"></div>Cache</div>
    <div class="li"><div class="dot" style="background:#991b1b"></div>External</div>
  </div>
  <div class="tip">Drag &#8226; Double-click to rename</div>
</div>
<script>
const COLORS = { client:'#1d4ed8', service:'#6d28d9', database:'#065f46', cache:'#92400e', external:'#991b1b' };
const TEXT   = { client:'#93c5fd', service:'#c4b5fd', database:'#6ee7b7', cache:'#fcd34d', external:'#fca5a5' };
const TYPES  = Object.keys(COLORS);

let nodes = [
  {id:'browser',  label:'Browser',       type:'client',   x:60,  y:210, w:110, h:50},
  {id:'gateway',  label:'API Gateway',   type:'service',  x:250, y:210, w:120, h:50},
  {id:'auth',     label:'Auth Service',  type:'service',  x:450, y:110, w:120, h:50},
  {id:'skills',   label:'Skill Service', type:'service',  x:450, y:220, w:120, h:50},
  {id:'sandbox',  label:'Sandbox',       type:'service',  x:450, y:340, w:120, h:50},
  {id:'postgres', label:'PostgreSQL',    type:'database', x:660, y:155, w:120, h:50},
  {id:'redis',    label:'Redis Cache',   type:'cache',    x:660, y:285, w:120, h:50},
  {id:'storage',  label:'S3 Storage',   type:'external', x:660, y:385, w:120, h:50},
];
const edges = [
  {f:'browser', t:'gateway', lb:'HTTPS'},
  {f:'gateway', t:'auth',    lb:'JWT'},
  {f:'gateway', t:'skills',  lb:'REST'},
  {f:'gateway', t:'sandbox', lb:'WS'},
  {f:'auth',    t:'postgres',lb:'SQL'},
  {f:'skills',  t:'postgres',lb:'SQL'},
  {f:'skills',  t:'redis',   lb:'cache'},
  {f:'sandbox', t:'storage', lb:'upload'},
];

const svg = document.getElementById('diagram');
let drag = null, off = {x:0, y:0};
let nc = nodes.length;

function ns(tag) { return document.createElementNS('http://www.w3.org/2000/svg', tag); }
function node(id) { return nodes.find(n => n.id === id); }

function render() {
  svg.innerHTML = '';
  const defs = ns('defs');
  defs.innerHTML = '<marker id="arr" markerWidth="9" markerHeight="7" refX="8" refY="3.5" orient="auto"><polygon points="0 0,9 3.5,0 7" fill="#374151"/></marker>';
  svg.appendChild(defs);

  edges.forEach(e => {
    const f = node(e.f), t = node(e.t);
    if (!f || !t) return;
    const x1 = f.x + f.w, y1 = f.y + f.h/2;
    const x2 = t.x,       y2 = t.y + t.h/2;
    const mx = (x1+x2)/2;
    const p = ns('path');
    p.setAttribute('d', 'M'+x1+' '+y1+' C'+mx+' '+y1+' '+mx+' '+y2+' '+x2+' '+y2);
    p.setAttribute('fill','none'); p.setAttribute('stroke','#374151');
    p.setAttribute('stroke-width','1.5'); p.setAttribute('marker-end','url(#arr)');
    svg.appendChild(p);
    const tx = ns('text');
    tx.setAttribute('x', mx); tx.setAttribute('y', (y1+y2)/2-4);
    tx.setAttribute('text-anchor','middle'); tx.setAttribute('font-size','9');
    tx.setAttribute('fill','#6b7280'); tx.setAttribute('font-family','monospace');
    tx.textContent = e.lb;
    svg.appendChild(tx);
  });

  nodes.forEach(n => {
    const g = ns('g'); g.setAttribute('class','node'); g.setAttribute('data-id',n.id);
    const sh = ns('rect');
    sh.setAttribute('x',n.x+3); sh.setAttribute('y',n.y+3); sh.setAttribute('width',n.w); sh.setAttribute('height',n.h); sh.setAttribute('rx','8'); sh.setAttribute('fill','rgba(0,0,0,0.35)');
    g.appendChild(sh);
    const r = ns('rect');
    r.setAttribute('x',n.x); r.setAttribute('y',n.y); r.setAttribute('width',n.w); r.setAttribute('height',n.h); r.setAttribute('rx','8');
    r.setAttribute('fill',COLORS[n.type]); r.setAttribute('stroke',TEXT[n.type]); r.setAttribute('stroke-width','1'); r.setAttribute('stroke-opacity','0.45');
    g.appendChild(r);
    const badge = ns('text');
    badge.setAttribute('x',n.x+n.w/2); badge.setAttribute('y',n.y+13); badge.setAttribute('text-anchor','middle');
    badge.setAttribute('font-size','8'); badge.setAttribute('fill',TEXT[n.type]); badge.setAttribute('opacity','0.6'); badge.setAttribute('font-family','monospace');
    badge.textContent = n.type.toUpperCase();
    g.appendChild(badge);
    const lbl = ns('text');
    lbl.setAttribute('x',n.x+n.w/2); lbl.setAttribute('y',n.y+n.h/2+5); lbl.setAttribute('text-anchor','middle');
    lbl.setAttribute('font-size','12'); lbl.setAttribute('font-weight','600'); lbl.setAttribute('fill',TEXT[n.type]);
    lbl.textContent = n.label;
    g.appendChild(lbl);
    g.addEventListener('mousedown', startDrag);
    g.addEventListener('dblclick', rename);
    svg.appendChild(g);
  });
}

function startDrag(e) {
  drag = node(e.currentTarget.getAttribute('data-id'));
  const r = svg.getBoundingClientRect();
  off.x = e.clientX * (940/r.width) - drag.x;
  off.y = e.clientY * (530/r.height) - drag.y;
  e.preventDefault();
}
svg.addEventListener('mousemove', e => {
  if (!drag) return;
  const r = svg.getBoundingClientRect();
  drag.x = Math.max(0, Math.min(940-drag.w, e.clientX*(940/r.width)-off.x));
  drag.y = Math.max(0, Math.min(530-drag.h, e.clientY*(530/r.height)-off.y));
  render();
});
svg.addEventListener('mouseup', () => { drag = null; });
svg.addEventListener('mouseleave', () => { drag = null; });

function rename(e) {
  const n = node(e.currentTarget.getAttribute('data-id'));
  const v = prompt('Rename:', n.label);
  if (v && v.trim()) { n.label = v.trim(); render(); }
}
function addNode() {
  const type = TYPES[nc % TYPES.length];
  nodes.push({id:'n'+nc, label:'New Node', type, x:370+nc*5, y:440, w:110, h:50});
  nc++; render();
}
function exportSVG() {
  const blob = new Blob([svg.outerHTML], {type:'image/svg+xml'});
  const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'architecture.svg'; a.click();
}
render();
</script>
</body>
</html>
```
