{"manifest":{"name":"SVG Architecture Diagram","version":"1.0.0","description":"Interactive drag-and-drop system architecture diagram builder. Pre-loaded with a microservices example. Add nodes, drag to rearrange, and export clean SVG.","tags":["visualization","architecture","diagram","devops","tool","svg"],"standard":"agentskills.io","standard_version":"1.0","content_checksum":"5641150e47f5c41bc509548a3d917b784ee6d619bd6a025b75e3f2309a836778","bundle_checksum":"37a524c7a165a77b0ae0446fb161c991321f64b65b411f787d365524dd4dcc88","metadata":{"runtime":{"entrypoint":"arch-diagram.html"}},"files":[{"path":"arch-diagram.html","name":"arch-diagram.html","mime_type":"text/html","checksum":"e4e8c4723467c23123ad6b9470111f7d30130077fbc1d4671bc8f5572ea1514e"}]},"files":{"SKILL.md":"# SVG Architecture Diagram\n\n> **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.\n\n---\n\n## How to Use\n\n1. **Drag nodes** — click and drag any service box to reposition it\n2. **Double-click a node** — rename it inline\n3. **Add Node button** — cycles through Client, Service, Database, Cache, External types\n4. **Export SVG** — downloads the current diagram as `architecture.svg`\n\n---\n\n## Node Types\n\n| Type | Color | Use For |\n|------|-------|---------|\n| Client | Blue | Browser, mobile app, CLI |\n| Service | Purple | API servers, microservices |\n| Database | Green | PostgreSQL, MySQL, SQLite |\n| Cache | Amber | Redis, Memcached, CDN |\n| External | Red | Third-party APIs, Stripe, AWS |\n\n---\n\n## Connections\n\nEdges are drawn automatically based on a predefined connection list. The edge labels show the protocol (HTTPS, REST, SQL, etc.).\n\n---\n\n## Export Format\n\nThe exported SVG is self-contained and renders cleanly in browsers, Figma, Notion, and GitHub READMEs.\n","arch-diagram.html":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>System Architecture Diagram</title>\n<style>\n* { box-sizing: border-box; margin: 0; padding: 0; }\nbody { background: #0d0d1a; color: #e2e8f0; font-family: system-ui, sans-serif; height: 100vh; display: flex; flex-direction: column; }\nheader { padding: 12px 20px; background: #1a1a2e; border-bottom: 1px solid #2d2d4e; display: flex; align-items: center; gap: 16px; }\nh1 { font-size: 15px; font-weight: 700; color: #a78bfa; }\n.toolbar { display: flex; gap: 8px; margin-left: auto; }\nbutton { padding: 6px 14px; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 700; }\n.btn-add { background: #6d28d9; color: #e9d5ff; }\n.btn-export { background: #065f46; color: #6ee7b7; }\n.canvas-wrap { flex: 1; position: relative; overflow: hidden; background: #080810; }\nsvg#diagram { width: 100%; height: 100%; cursor: default; }\n.node { cursor: grab; }\n.node:active { cursor: grabbing; }\n.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; }\n.legend-title { font-weight: 700; color: #a78bfa; margin-bottom: 7px; font-size: 11px; }\n.li { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; color: #94a3b8; }\n.dot { width: 10px; height: 10px; border-radius: 3px; }\n.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; }\n</style>\n</head>\n<body>\n<header>\n  <h1>&#9651; System Architecture Diagram</h1>\n  <span style=\"font-size:11px;color:#4b5563\">Drag nodes &#8226; Double-click to rename &#8226; Add &#38; Export</span>\n  <div class=\"toolbar\">\n    <button class=\"btn-add\" onclick=\"addNode()\">+ Add Node</button>\n    <button class=\"btn-export\" onclick=\"exportSVG()\">Export SVG</button>\n  </div>\n</header>\n<div class=\"canvas-wrap\">\n  <svg id=\"diagram\" viewBox=\"0 0 940 530\"></svg>\n  <div class=\"legend\">\n    <div class=\"legend-title\">Node Types</div>\n    <div class=\"li\"><div class=\"dot\" style=\"background:#1d4ed8\"></div>Client</div>\n    <div class=\"li\"><div class=\"dot\" style=\"background:#6d28d9\"></div>Service</div>\n    <div class=\"li\"><div class=\"dot\" style=\"background:#065f46\"></div>Database</div>\n    <div class=\"li\"><div class=\"dot\" style=\"background:#92400e\"></div>Cache</div>\n    <div class=\"li\"><div class=\"dot\" style=\"background:#991b1b\"></div>External</div>\n  </div>\n  <div class=\"tip\">Drag &#8226; Double-click to rename</div>\n</div>\n<script>\nconst COLORS = { client:'#1d4ed8', service:'#6d28d9', database:'#065f46', cache:'#92400e', external:'#991b1b' };\nconst TEXT   = { client:'#93c5fd', service:'#c4b5fd', database:'#6ee7b7', cache:'#fcd34d', external:'#fca5a5' };\nconst TYPES  = Object.keys(COLORS);\n\nlet nodes = [\n  {id:'browser',  label:'Browser',       type:'client',   x:60,  y:210, w:110, h:50},\n  {id:'gateway',  label:'API Gateway',   type:'service',  x:250, y:210, w:120, h:50},\n  {id:'auth',     label:'Auth Service',  type:'service',  x:450, y:110, w:120, h:50},\n  {id:'skills',   label:'Skill Service', type:'service',  x:450, y:220, w:120, h:50},\n  {id:'sandbox',  label:'Sandbox',       type:'service',  x:450, y:340, w:120, h:50},\n  {id:'postgres', label:'PostgreSQL',    type:'database', x:660, y:155, w:120, h:50},\n  {id:'redis',    label:'Redis Cache',   type:'cache',    x:660, y:285, w:120, h:50},\n  {id:'storage',  label:'S3 Storage',   type:'external', x:660, y:385, w:120, h:50},\n];\nconst edges = [\n  {f:'browser', t:'gateway', lb:'HTTPS'},\n  {f:'gateway', t:'auth',    lb:'JWT'},\n  {f:'gateway', t:'skills',  lb:'REST'},\n  {f:'gateway', t:'sandbox', lb:'WS'},\n  {f:'auth',    t:'postgres',lb:'SQL'},\n  {f:'skills',  t:'postgres',lb:'SQL'},\n  {f:'skills',  t:'redis',   lb:'cache'},\n  {f:'sandbox', t:'storage', lb:'upload'},\n];\n\nconst svg = document.getElementById('diagram');\nlet drag = null, off = {x:0, y:0};\nlet nc = nodes.length;\n\nfunction ns(tag) { return document.createElementNS('http://www.w3.org/2000/svg', tag); }\nfunction node(id) { return nodes.find(n => n.id === id); }\n\nfunction render() {\n  svg.innerHTML = '';\n  const defs = ns('defs');\n  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>';\n  svg.appendChild(defs);\n\n  edges.forEach(e => {\n    const f = node(e.f), t = node(e.t);\n    if (!f || !t) return;\n    const x1 = f.x + f.w, y1 = f.y + f.h/2;\n    const x2 = t.x,       y2 = t.y + t.h/2;\n    const mx = (x1+x2)/2;\n    const p = ns('path');\n    p.setAttribute('d', 'M'+x1+' '+y1+' C'+mx+' '+y1+' '+mx+' '+y2+' '+x2+' '+y2);\n    p.setAttribute('fill','none'); p.setAttribute('stroke','#374151');\n    p.setAttribute('stroke-width','1.5'); p.setAttribute('marker-end','url(#arr)');\n    svg.appendChild(p);\n    const tx = ns('text');\n    tx.setAttribute('x', mx); tx.setAttribute('y', (y1+y2)/2-4);\n    tx.setAttribute('text-anchor','middle'); tx.setAttribute('font-size','9');\n    tx.setAttribute('fill','#6b7280'); tx.setAttribute('font-family','monospace');\n    tx.textContent = e.lb;\n    svg.appendChild(tx);\n  });\n\n  nodes.forEach(n => {\n    const g = ns('g'); g.setAttribute('class','node'); g.setAttribute('data-id',n.id);\n    const sh = ns('rect');\n    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)');\n    g.appendChild(sh);\n    const r = ns('rect');\n    r.setAttribute('x',n.x); r.setAttribute('y',n.y); r.setAttribute('width',n.w); r.setAttribute('height',n.h); r.setAttribute('rx','8');\n    r.setAttribute('fill',COLORS[n.type]); r.setAttribute('stroke',TEXT[n.type]); r.setAttribute('stroke-width','1'); r.setAttribute('stroke-opacity','0.45');\n    g.appendChild(r);\n    const badge = ns('text');\n    badge.setAttribute('x',n.x+n.w/2); badge.setAttribute('y',n.y+13); badge.setAttribute('text-anchor','middle');\n    badge.setAttribute('font-size','8'); badge.setAttribute('fill',TEXT[n.type]); badge.setAttribute('opacity','0.6'); badge.setAttribute('font-family','monospace');\n    badge.textContent = n.type.toUpperCase();\n    g.appendChild(badge);\n    const lbl = ns('text');\n    lbl.setAttribute('x',n.x+n.w/2); lbl.setAttribute('y',n.y+n.h/2+5); lbl.setAttribute('text-anchor','middle');\n    lbl.setAttribute('font-size','12'); lbl.setAttribute('font-weight','600'); lbl.setAttribute('fill',TEXT[n.type]);\n    lbl.textContent = n.label;\n    g.appendChild(lbl);\n    g.addEventListener('mousedown', startDrag);\n    g.addEventListener('dblclick', rename);\n    svg.appendChild(g);\n  });\n}\n\nfunction startDrag(e) {\n  drag = node(e.currentTarget.getAttribute('data-id'));\n  const r = svg.getBoundingClientRect();\n  off.x = e.clientX * (940/r.width) - drag.x;\n  off.y = e.clientY * (530/r.height) - drag.y;\n  e.preventDefault();\n}\nsvg.addEventListener('mousemove', e => {\n  if (!drag) return;\n  const r = svg.getBoundingClientRect();\n  drag.x = Math.max(0, Math.min(940-drag.w, e.clientX*(940/r.width)-off.x));\n  drag.y = Math.max(0, Math.min(530-drag.h, e.clientY*(530/r.height)-off.y));\n  render();\n});\nsvg.addEventListener('mouseup', () => { drag = null; });\nsvg.addEventListener('mouseleave', () => { drag = null; });\n\nfunction rename(e) {\n  const n = node(e.currentTarget.getAttribute('data-id'));\n  const v = prompt('Rename:', n.label);\n  if (v && v.trim()) { n.label = v.trim(); render(); }\n}\nfunction addNode() {\n  const type = TYPES[nc % TYPES.length];\n  nodes.push({id:'n'+nc, label:'New Node', type, x:370+nc*5, y:440, w:110, h:50});\n  nc++; render();\n}\nfunction exportSVG() {\n  const blob = new Blob([svg.outerHTML], {type:'image/svg+xml'});\n  const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'architecture.svg'; a.click();\n}\nrender();\n</script>\n</body>\n</html>"}}