
This is a comprehensive diagram generator that outputs professional, dark-themed SVG files for architecture diagrams, flowcharts, sequence diagrams, mind maps, timelines, and more. It comes with a complete design system including semantic color palettes (cyan for frontend, emerald for backend, violet for databases), embedded JetBrains Mono typography, and strict spacing rules to prevent overlapping elements. The implementation details are thorough, covering z-index layering tricks, SVG patterns for grid backgrounds, and reusable component templates for common shapes like decision diamonds and database cylinders. It's built for technical documentation and system design work where you need consistent, readable diagrams without reaching for specialized tools. The dark theme and monospace fonts give it a distinctly developer-focused aesthetic.
$ npx -y skills add jimliu/baoyu-skills --skill baoyu-diagram --agent claude-codeInstalls into .claude/skills of the current project.
Create SVG diagrams. Each SVG embeds its own styles, uses CSS custom properties for theming, and loads nothing over the network.
For a diagram saved as <name>.svg, produce exactly these files:
| File | What it is | Made by |
|---|---|---|
<name>.svg | Source SVG, CSS variables with dark theme values | You (the LLM) |
<name>.light.svg | Same markup with light theme values | theme-switch.ts --theme=light |
<name>@2x.png | Dark PNG at 2x scale | main.ts |
<name>.light@2x.png | Light PNG at 2x scale | main.ts --theme light |
If the user asks for only one theme, or only SVG, produce just those files.
All colors are CSS custom properties defined in the :root block of the SVG's <style>; markup references them as var(--bg), var(--color-primary-fill), etc. Never hardcode color values in SVG attributes.
Theme values live in {baseDir}/themes/<name>.json — one flat JSON object mapping CSS property names (keys starting with --) to colors. Other keys (e.g. "description") are metadata, ignored by scripts. Built-in: dark.json, light.json.
Custom themes: drop a new .json into themes/ — no script changes needed:
// themes/my-brand.json
{
"description": "Corporate brand theme",
"--bg": "#ffffff",
"--text": "#1a1a2e",
"--text-muted": "#6b7280",
"--arrow": "#9ca3af",
"--color-primary-fill": "rgba(37,99,235,0.12)",
"--color-primary-stroke": "#2563eb",
"--color-secondary-fill": "rgba(22,163,74,0.12)",
"--color-secondary-stroke": "#16a34a"
}
Apply with --theme=my-brand (theme-switch.ts, main.ts and flatten-vars.ts all support it). Variables omitted from a custom theme are not replaced. When generating SVGs for a custom theme, use its values in the :root block.
| Variable | Dark Theme Default | Light Theme Value | Usage |
|---|---|---|---|
--bg | #0f172a | #f8fafc | Page background |
--bg-grid | #1e293b | #e2e8f0 | Grid pattern stroke |
--mask | #0f172a | #f8fafc | Opaque mask fill (same as --bg) |
--text | white | #0f172a | Primary text color |
--text-muted | #94a3b8 | #475569 | Secondary/sublabel text |
--arrow | #64748b | #64748b | Arrow/connector lines (unchanged) |
--color-primary-fill | rgba(8,51,68,0.4) | rgba(6,182,212,0.12) | Frontend, user-facing, inputs |
--color-primary-stroke | #22d3ee | #0891b2 | Primary border |
--color-secondary-fill | rgba(6,78,59,0.4) | rgba(5,150,105,0.12) | Backend, services, processing |
--color-secondary-stroke | #34d399 | #059669 | Secondary border |
--color-tertiary-fill | rgba(76,29,149,0.4) | rgba(124,58,237,0.12) | Database, storage, persistence |
--color-tertiary-stroke | #a78bfa | #7c3aed | Tertiary border |
--color-accent-fill | rgba(120,53,15,0.3) | rgba(217,119,6,0.12) | Cloud, infrastructure, regions |
--color-accent-stroke | #fbbf24 | #d97706 | Accent border |
--color-alert-fill | rgba(136,19,55,0.4) | rgba(225,29,72,0.12) | Security, errors, warnings |
--color-alert-stroke | #fb7185 | #e11d48 | Alert border |
--color-connector-fill | rgba(251,146,60,0.3) | rgba(234,88,12,0.12) | Buses, queues, middleware |
--color-connector-stroke | #fb923c | #ea580c | Connector border |
--color-neutral-fill | rgba(30,41,59,0.5) | rgba(100,116,139,0.12) | External, generic, unknown |
--color-neutral-stroke | #94a3b8 | #64748b | Neutral border |
--color-highlight-fill | rgba(59,130,246,0.3) | rgba(59,130,246,0.12) | Active state, focus, current step |
--color-highlight-stroke | #60a5fa | #3b82f6 | Highlight border |
Use a locally installed monospace font stack. Do not @import web fonts or reference any remote resource: the SVG must render the same offline, and the PNG converter cannot fetch fonts.
<style>
text { font-family: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Noto Sans Mono', 'DejaVu Sans Mono', monospace; }
</style>
Font sizes by role:
var(--text)var(--text)var(--text-muted)Background + Grid:
<defs>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--bg-grid)" stroke-width="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="var(--bg)"/>
<rect width="100%" height="100%" fill="url(#grid)"/>
Arrowhead marker (standard):
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--arrow)"/>
</marker>
Arrowhead marker (colored) — create per-color as needed:
<marker id="arrow-primary" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--color-primary-stroke)"/>
</marker>
Open arrowhead (for async/return messages):
<marker id="arrow-open" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polyline points="0 0, 10 3.5, 0 7" fill="none" stroke="var(--arrow)" stroke-width="1.5"/>
</marker>
Draw in this order (SVG paints back-to-front):
fill="var(--mask)")The opaque mask rect is required — semi-transparent component fills otherwise show arrows underneath:
<!-- Mask layer: opaque background to hide arrows -->
<rect x="100" y="100" width="160" height="60" rx="6" fill="var(--mask)"/>
<!-- Visual layer: styled component -->
<rect x="100" y="100" width="160" height="60" rx="6" fill="var(--color-primary-fill)" stroke="var(--color-primary-stroke)" stroke-width="1.5"/>
<text x="180" y="125" fill="var(--text)" font-size="11" font-weight="600" text-anchor="middle">API Gateway</text>
<text x="180" y="141" fill="var(--text-muted)" font-size="9" text-anchor="middle">Kong / Nginx</text>
Follow strictly to prevent overlaps:
Standard box (service/process):
<rect x="X" y="Y" width="160" height="60" rx="6" fill="var(--mask)"/>
<rect x="X" y="Y" width="160" height="60" rx="6" fill="var(--color-primary-fill)" stroke="var(--color-primary-stroke)" stroke-width="1.5"/>
<text x="CX" y="Y+24" fill="var(--text)" font-size="11" font-weight="600" text-anchor="middle">Name</text>
<text x="CX" y="Y+40" fill="var(--text-muted)" font-size="9" text-anchor="middle">description</text>
Decision diamond (flowchart):
<g transform="translate(CX, CY)">
<polygon points="0,-35 50,0 0,35 -50,0" fill="var(--mask)"/>
<polygon points="0,-35 50,0 0,35 -50,0" fill="var(--color-accent-fill)" stroke="var(--color-accent-stroke)" stroke-width="1.5"/>
<text y="4" fill="var(--text)" font-size="10" font-weight="600" text-anchor="middle">Condition?</text>
</g>
Database cylinder:
<g transform="translate(X, Y)">
<rect x="0" y="10" width="120" height="50" rx="2" fill="var(--mask)"/>
<ellipse cx="60" cy="10" rx="60" ry="12" fill="var(--mask)"/>
<ellipse cx="60" cy="60" rx="60" ry="12" fill="var(--mask)"/>
<rect x="0" y="10" width="120" height="50" fill="var(--color-tertiary-fill)"/>
<ellipse cx="60" cy="10" rx="60" ry="12" fill="var(--color-tertiary-fill)" stroke="var(--color-tertiary-stroke)" stroke-width="1.5"/>
<ellipse cx="60" cy="60" rx="60" ry="12" fill="var(--color-tertiary-fill)" stroke="var(--color-tertiary-stroke)" stroke-width="1.5"/>
<line x1="0" y1="10" x2="0" y2="60" stroke="var(--color-tertiary-stroke)" stroke-width="1.5"/>
<line x1="120" y1="10" x2="120" y2="60" stroke="var(--color-tertiary-stroke)" stroke-width="1.5"/>
<text x="60" y="40" fill="var(--text)" font-size="11" font-weight="600" text-anchor="middle">PostgreSQL</text>
</g>
Region boundary:
<rect x="X" y="Y" width="W" height="H" rx="12" fill="none" stroke="var(--color-accent-stroke)" stroke-width="1" stroke-dasharray="8,4"/>
<text x="X+12" y="Y+16" fill="var(--color-accent-stroke)" font-size="9" font-weight="600">AWS us-east-1</text>
Security group:
<rect x="X" y="Y" width="W" height="H" rx="8" fill="none" stroke="var(--color-alert-stroke)" stroke-width="1" stroke-dasharray="4,4"/>
<text x="X+10" y="Y+14" fill="var(--color-alert-stroke)" font-size="8" font-weight="500">VPC / Security Group</text>
Determine this SKILL.md file's directory path as {baseDir}. Before layout, read the type's reference in {baseDir}/references/ if one exists.
| Type | When to use | Reference | Layout key points |
|---|---|---|---|
| Architecture | System components & relationships | references/architecture.md | LTR or TTB data flow; group related services in region boundaries; buses/connectors between layers; databases at bottom or right |
| Flowchart | Decision logic, process steps | references/flowchart.md | Top-to-bottom primary flow; diamonds with Yes/No labels on exit arrows; rounded rects for start/end; Highlight color for the happy path |
| Sequence | Time-ordered interactions between actors | references/sequence.md | Actor boxes at top, vertical dashed lifelines; horizontal messages (solid=sync, dashed=return); time flows down; activation bars; number messages if complex |
| Structural | Class/ER diagrams, org charts | references/structural.md | Compartmented boxes (name / attributes / methods); filled diamond=composition, empty diamond=aggregation, dashed arrow=dependency, solid triangle=inheritance |
| Mind Map | Brainstorming, topic exploration | — | Radiating layout from a central concept; cubic-bezier <path> branches; vary branch colors; font size decreases outward |
| Timeline | Chronological events | — | Horizontal or vertical axis; circle/diamond event markers, period spans; descriptions on alternating sides; color by event type |
| State Machine | State transitions, lifecycle | — | Rounded-rect states, double border for composite; filled circle = initial, bullseye = final; curved self-transitions; label transitions event [guard] / action |
| Illustrative | Conceptual explanations, comparisons | — | Free-form layout, icons, annotations, visual metaphors |
| Data Flow | Data transformation pipelines | — | Process bubbles, data stores, external entities |
@import, web fonts, remote images or scriptsviewBox to fit all content with 30px padding; do NOT set fixed width/height attributesxmlns="http://www.w3.org/2000/svg" on the root <svg><style>, <defs>, markers, and patterns at the toptext-anchor="middle" for centered labels; text must not overflow boxesfont-family: 'JetBrains Mono', 'Noto Sans SC', 'PingFang SC', sans-serif' and widen boxes — CJK characters are widervar(--...) reference so theme switching works{inputFileDir}/diagram/; otherwise {projectDir}/diagram/{topic-slug}/. Create the directory if missingDetermine this SKILL.md file's directory path as {baseDir}. Scripts live in {baseDir}/scripts/:
theme-switch.ts — write the SVG with another theme's variable valuesmain.ts — convert an SVG to a @2x PNGflatten-vars.ts — inline CSS variables into literal colors (only for other non-browser tools; main.ts does this itself)Resolve ${BUN_X} runtime: if bun installed → bun; if npx available → npx -y bun; else suggest installing bun.
${BUN_X} {baseDir}/scripts/theme-switch.ts <svg-path> --theme=light [--output=path/to/output.svg]
${BUN_X} {baseDir}/scripts/theme-switch.ts <svg-path> --theme=dark
${BUN_X} {baseDir}/scripts/theme-switch.ts <svg-path> --theme=my-brand # {baseDir}/themes/<name>.json
Replaces the :root { ... } definitions with the target theme's values; var(--...) references stay unchanged and the source is not modified. Without --output, writes <name>.<theme>.svg next to the input. The source already carries dark values, so --theme=dark on it is unnecessary.
main.ts needs sharp, pinned in {baseDir}/scripts/package.json with a lockfile. Install once:
cd {baseDir}/scripts && ${BUN_X} install
If sharp is missing, main.ts stops with an error that gives this command; it never installs anything itself. Then deliver the SVG files and tell the user how to enable PNG output. theme-switch.ts and flatten-vars.ts have no dependencies.
${BUN_X} {baseDir}/scripts/main.ts <svg-path> [options]
Options:
-t, --theme <name> — Theme used to resolve CSS variables (default: dark; any {baseDir}/themes/<name>.json works)-s, --scale <n> — Scale factor (default: 2)-o, --output <path> — Custom output path (default: <input>@2x.png)--json — JSON outputsharp (librsvg) does not understand CSS var(), so main.ts replaces every var(--xxx) with the theme's literal value and drops the :root block. It fails if the SVG references a variable that neither the theme nor the SVG defines. SVGs with hardcoded colors (legacy diagrams) are converted as-is.
To flatten without converting (e.g. for another renderer):
${BUN_X} {baseDir}/scripts/flatten-vars.ts <svg-path> <output-path> [--theme=<name>]
:root { ... } block using the dark defaults from the CSS Custom Properties table<name>.svg (the dark version)${BUN_X} {baseDir}/scripts/theme-switch.ts <name>.svg --theme=light → <name>.light.svg${BUN_X} {baseDir}/scripts/main.ts <name>.svg → <name>@2x.png${BUN_X} {baseDir}/scripts/main.ts <name>.svg --theme light -o <name>.light@2x.png → <name>.light@2x.pngSponsors