返回 Skills
jimliu/baoyu-skills· MIT 内容可用

baoyu-diagram

Create professional, dark-themed SVG diagrams of any type — architecture diagrams, flowcharts, sequence diagrams, structural diagrams, mind maps, timelines, illustrative/conceptual diagrams, and more. Use this skill whenever the user asks for any kind of technical or conceptual diagram, visualization of a system, process flow, data flow, component relationship, network topology, decision tree, org chart, state machine, or any visual representation of structure/logic/process. Also trigger when the user says "画个图" "画一个架构图" "diagram" "flowchart" "sequence diagram" "draw me a ..." or uploads content and asks to visualize it. Output is always a standalone .svg file.

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: baoyu-diagram description: Create professional, dark-themed SVG diagrams of any type — architecture diagrams, flowcharts, sequence diagrams, structural diagrams, mind maps, timelines, illustrative/conceptual diagrams, and more. Use this skill whenever the user asks for any kind of technical or conceptual diagram, visualization of a system, process flow, data flow, component relationship, network topology, decision tree, org chart, state machine, or any visual representation of structure/logic/process. Also trigger when the user says "画个图" "画一个架构图" "diagram" "flowchart" "sequence diagram" "draw me a ..." or uploads content and asks to visualize it. Output is always a standalone .svg file. version: 1.117.3

Diagram Generator

Create professional SVG diagrams across multiple diagram types. All output is a single self-contained .svg file with embedded styles and fonts.

Supported Diagram Types

TypeWhen to UseKey Characteristics
ArchitectureSystem components & relationshipsGrouped boxes, connection arrows, region boundaries
FlowchartDecision logic, process stepsDiamond decisions, rounded step boxes, directional flow
SequenceTime-ordered interactions between actorsVertical lifelines, horizontal messages, activation bars
StructuralClass diagrams, ER diagrams, org chartsCompartmented boxes, typed relationships (inheritance, composition)
Mind MapBrainstorming, topic explorationCentral node, radiating branches, organic layout
TimelineChronological eventsHorizontal/vertical axis, event markers, period spans
IllustrativeConceptual explanations, comparisonsFree-form layout, icons, annotations, visual metaphors
State MachineState transitions, lifecycleRounded state nodes, labeled transitions, start/end markers
Data FlowData transformation pipelinesProcess bubbles, data stores, external entities

Design System

Color Palette

Semantic colors for component categories:

CategoryFill (rgba)StrokeUse For
Primaryrgba(8, 51, 68, 0.4)#22d3ee (cyan)Frontend, user-facing, inputs
Secondaryrgba(6, 78, 59, 0.4)#34d399 (emerald)Backend, services, processing
Tertiaryrgba(76, 29, 149, 0.4)#a78bfa (violet)Database, storage, persistence
Accentrgba(120, 53, 15, 0.3)#fbbf24 (amber)Cloud, infrastructure, regions
Alertrgba(136, 19, 55, 0.4)#fb7185 (rose)Security, errors, warnings
Connectorrgba(251, 146, 60, 0.3)#fb923c (orange)Buses, queues, middleware
Neutralrgba(30, 41, 59, 0.5)#94a3b8 (slate)External, generic, unknown
Highlightrgba(59, 130, 246, 0.3)#60a5fa (blue)Active state, focus, current step

For flowcharts and sequence diagrams, assign colors by role (actor, decision, process) rather than by technology.

Typography

Use embedded SVG @font-face or system monospace fallback:

<style>
  @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&amp;display=swap');
  text { font-family: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', monospace; }
</style>

Font sizes by role:

  • Title: 16px, weight 700
  • Component name: 11-12px, weight 600
  • Sublabel / description: 9px, weight 400, color #94a3b8
  • Annotation / note: 8px, weight 400
  • Tiny label (on arrows): 7-8px

Core Visual Elements

Background: #0f172a (slate-900) with subtle grid:

<defs>
  <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
    <path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e293b" stroke-width="0.5"/>
  </pattern>
</defs>
<rect width="100%" height="100%" fill="#0f172a"/>
<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="#64748b"/>
</marker>

Arrowhead marker (colored) — create per-color as needed:

<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
  <polygon points="0 0, 10 3.5, 0 7" fill="#22d3ee"/>
</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="#64748b" stroke-width="1.5"/>
</marker>

SVG Structure & Layering

Draw elements in this order to get correct z-ordering (SVG paints back-to-front):

  1. Background fill + grid pattern
  2. Region/group boundaries (dashed outlines)
  3. Connection arrows and lines
  4. Opaque masking rects (same position as component boxes, fill="#0f172a")
  5. Component boxes (semi-transparent fill + stroke)
  6. Text labels
  7. Legend (bottom-right or bottom area, outside all boundaries)
  8. Title block (top-left)

The opaque masking rect trick is essential — semi-transparent component fills will show arrows underneath without it:

<!-- Mask layer: opaque background to hide arrows -->
<rect x="100" y="100" width="160" height="60" rx="6" fill="#0f172a"/>
<!-- Visual layer: styled component -->
<rect x="100" y="100" width="160" height="60" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="180" y="125" fill="white" font-size="11" font-weight="600" text-anchor="middle">API Gateway</text>
<text x="180" y="141" fill="#94a3b8" font-size="9" text-anchor="middle">Kong / Nginx</text>

Spacing Rules

These prevent overlapping — follow them strictly:

  • Component box height: 50-70px (standard), 80-120px (large/complex)
  • Minimum gap between components: 40px vertical, 30px horizontal
  • Arrow label clearance: 10px from any box edge
  • Region boundary padding: 20px inside edges around contained components
  • Legend placement: At least 20px below the lowest diagram element
  • Title block: 20px from top-left, outside diagram content area
  • viewBox: Always extend to fit all content + 30px padding on all sides

Component Patterns

Standard box (service/process):

<rect x="X" y="Y" width="160" height="60" rx="6" fill="#0f172a"/>
<rect x="X" y="Y" width="160" height="60" rx="6" fill="FILL" stroke="STROKE" stroke-width="1.5"/>
<text x="CX" y="Y+24" fill="white" font-size="11" font-weight="600" text-anchor="middle">Name</text>
<text x="CX" y="Y+40" fill="#94a3b8" 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="#0f172a"/>
  <polygon points="0,-35 50,0 0,35 -50,0" fill="rgba(120,53,15,0.3)" stroke="#fbbf24" stroke-width="1.5"/>
  <text y="4" fill="white" 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="#0f172a"/>
  <ellipse cx="60" cy="10" rx="60" ry="12" fill="#0f172a"/>
  <ellipse cx="60" cy="60" rx="60" ry="12" fill="#0f172a"/>
  <rect x="0" y="10" width="120" height="50" fill="rgba(76,29,149,0.4)"/>
  <ellipse cx="60" cy="10" rx="60" ry="12" fill="rgba(76,29,149,0.4)" stroke="#a78bfa" stroke-width="1.5"/>
  <ellipse cx="60" cy="60" rx="60" ry="12" fill="rgba(76,29,149,0.4)" stroke="#a78bfa" stroke-width="1.5"/>
  <line x1="0" y1="10" x2="0" y2="60" stroke="#a78bfa" stroke-width="1.5"/>
  <line x1="120" y1="10" x2="120" y2="60" stroke="#a78bfa" stroke-width="1.5"/>
  <text x="60" y="40" fill="white" 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="#fbbf24" stroke-width="1" stroke-dasharray="8,4"/>
<text x="X+12" y="Y+16" fill="#fbbf24" 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="#fb7185" stroke-width="1" stroke-dasharray="4,4"/>
<text x="X+10" y="Y+14" fill="#fb7185" font-size="8" font-weight="500">VPC / Security Group</text>

Type-Specific Layout Guidance

Determine this SKILL.md file's directory path as {baseDir}. Read the reference file for the specific diagram type before starting layout. Reference files are located at {baseDir}/references/ and contain detailed layout algorithms and examples.

Architecture Diagrams

→ Read {baseDir}/references/architecture.md

Key points: left-to-right or top-to-bottom data flow. Group related services in region boundaries. Use buses/connectors between layers. Place databases at the bottom or right.

Flowcharts

→ Read {baseDir}/references/flowchart.md

Key points: top-to-bottom primary flow. Diamonds for decisions with Yes/No labels on exit arrows. Rounded rectangles for start/end. Use the Highlight color for the happy path.

Sequence Diagrams

→ Read {baseDir}/references/sequence.md

Key points: actors as boxes at top, vertical dashed lifelines, horizontal arrows for messages (solid=sync, dashed=return). Time flows downward. Activation bars show processing. Number messages if complex.

Structural Diagrams

→ Read {baseDir}/references/structural.md

Key points: compartmented boxes (name / attributes / methods for class diagrams). Relationship lines: solid with filled diamond=composition, solid with empty diamond=aggregation, dashed arrow=dependency, solid triangle=inheritance.

Mind Maps

Free-form radiating layout from a central concept. Use organic curves (<path> with cubic beziers) for branches. Vary branch colors using the palette. Larger font for central node, decreasing as you go outward.

Timelines

Horizontal or vertical axis line. Event markers as circles or diamonds on the axis. Description text offset to alternating sides to avoid overlap. Use color to categorize event types.

State Machines

Rounded-rect states with double-border for composite states. Filled circle for initial state, bullseye for final state. Curved arrows for self-transitions. Label all transitions with event [guard] / action format.

Output Rules

  1. Output a single .svg file — no external dependencies except the Google Fonts import
  2. Set viewBox to fit all content with 30px padding; do NOT set fixed width/height attributes (let the SVG scale responsively)
  3. Include xmlns="http://www.w3.org/2000/svg" on the root <svg> element
  4. Put all <style>, <defs>, markers, and patterns at the top of the SVG
  5. Use text-anchor="middle" for centered labels; ensure text doesn't overflow boxes
  6. Chinese text support: When labels contain Chinese characters, use font-family: 'JetBrains Mono', 'Noto Sans SC', 'PingFang SC', sans-serif' and increase box widths — CJK characters are wider
  7. Save location: If the input is a file, save to {inputFileDir}/diagram/. Otherwise save to {projectDir}/diagram/{topic-slug}/. Create the directory if it doesn't exist

Script

Determine this SKILL.md file's directory path as {baseDir}. Script path: {baseDir}/scripts/main.ts.

Resolve ${BUN_X} runtime: if bun installed → bun; if npx available → npx -y bun; else suggest installing bun.

SVG → @2x PNG

After saving the SVG, convert it to a @2x PNG:

${BUN_X} {baseDir}/scripts/main.ts <svg-path> [options]

Options:

  • -s, --scale <n> — Scale factor (default: 2)
  • -o, --output <path> — Custom output path (default: <input>@2x.png)
  • --json — JSON output

Process

  1. Identify the diagram type from the user's request
  2. Read the relevant reference file if one exists for that type
  3. Plan the layout: list all components, determine grouping and flow direction, calculate positions
  4. Write the SVG following the layering order above
  5. Verify spacing rules — no overlaps, legends outside boundaries, viewBox large enough
  6. Save the SVG file
  7. Run ${BUN_X} {baseDir}/scripts/main.ts <svg-path> to generate @2x PNG
  8. Present both files to the user

附带文件

references/architecture.md
# Architecture Diagram Layout

## Flow Direction

Choose one primary direction:
- **Left-to-Right (LTR):** Best for data pipelines, request flows. Users/clients on left, data stores on right.
- **Top-to-Bottom (TTB):** Best for layered architectures. Clients at top, infrastructure at bottom.

## Layout Algorithm

1. **Identify layers:** Group components by role (clients, gateways, services, data, infrastructure)
2. **Assign columns (LTR) or rows (TTB):** One layer per column/row
3. **Within each layer:** Stack components vertically (LTR) or horizontally (TTB), 40px gap minimum
4. **Region boundaries:** Draw around groups that share infrastructure (e.g., "AWS us-east-1", "Kubernetes Cluster")
5. **Connectors:** Route arrows between layers. For buses/queues between layers, place a thin connector bar in the gap.

## Typical Layer Structure (LTR)

```
Col 1 (x=40)     Col 2 (x=250)     Col 3 (x=460)     Col 4 (x=670)
┌──────────┐     ┌──────────┐      ┌──────────┐      ┌──────────┐
│  Client   │────▶│ Gateway  │─────▶│ Services │─────▶│ Database │
│  Layer    │     │  Layer   │      │  Layer   │      │  Layer   │
└──────────┘     └──────────┘      └──────────┘      └──────────┘
```

Column spacing: 200-220px between column starts. Adjust if components are wider.

## Typical Layer Structure (TTB)

```
Row 1 (y=60):   [ Browser ]  [ Mobile App ]  [ API Client ]
Row 2 (y=160):  [         Load Balancer / API Gateway       ]
Row 3 (y=280):  [ Auth Svc ]  [ User Svc ]  [ Order Svc ]
Row 4 (y=400):  [  Redis  ]   [ PostgreSQL ]  [ S3 Bucket ]
```

Row spacing: 120-140px between row starts.

## Connection Routing

- Prefer straight horizontal or vertical lines
- For connections that would cross components, use two-segment (L-shaped) paths:
  ```svg
  <path d="M x1,y1 L midX,y1 L midX,y2" fill="none" stroke="#64748b" marker-end="url(#arrow)"/>
  ```
- For busy diagrams, use `stroke-opacity="0.6"` on less important connections
- Label important connections with a text element near the midpoint

## Message Bus / Event Bus Pattern

When services communicate through a shared bus, draw it as a horizontal bar between the service layer:

```
Services:  [ Svc A ]    [ Svc B ]    [ Svc C ]
              │              │            │
Bus:     ════╪══════════════╪════════════╪═══════
              │              │            │
Data:    [ DB A ]        [ DB B ]     [ Cache ]
```

Use the Connector color (orange) for the bus bar.

## Multi-Region / Multi-Cloud

Nest region boundaries:
- Outer boundary: Cloud provider (AWS, GCP)
- Inner boundary: Region or VPC
- Innermost: Availability zones or subnets

Use different dash patterns to distinguish nesting levels:
- Outer: `stroke-dasharray="12,4"`
- Middle: `stroke-dasharray="8,4"`
- Inner: `stroke-dasharray="4,4"`
references/flowchart.md
# Flowchart Layout

## Shape Vocabulary

| Shape | Meaning | SVG Element |
|-------|---------|-------------|
| Rounded rect (large radius) | Start / End | `<rect rx="25">` |
| Rectangle | Process / Action | `<rect rx="6">` |
| Diamond | Decision | `<polygon>` rotated 45° |
| Parallelogram | Input / Output | `<polygon>` with skew |
| Cylinder | Data store | Ellipse + rect combo |

## Flow Direction

Primary flow: **top to bottom**. Branch flows go left/right from decisions.

## Layout Algorithm

1. **Identify the main path** (happy path / most common flow) — this runs straight down the center
2. **Branch from decisions:** "Yes" continues down center, "No" branches right (or left if space is tight)
3. **Merge paths:** Route branches back to the main path using L-shaped connectors
4. **Loop-backs:** Route upward on the far left/right side of the diagram with curved paths

## Spacing

- Step-to-step vertical gap: 60-80px (enough for arrow + optional label)
- Decision diamond height: 70px (point to point)
- Decision diamond width: 100px (point to point)
- Branch horizontal offset: 200px from center
- Merge connector clearance: 20px from any box

## Decision Labels

Place "Yes" / "No" (or "True" / "False", "是" / "否") labels directly on the exit arrows, 10px from the diamond edge:

```svg
<!-- Decision diamond at center (400, 200) -->
<!-- Yes: downward -->
<line x1="400" y1="235" x2="400" y2="300" stroke="#64748b" marker-end="url(#arrow)"/>
<text x="412" y="260" fill="#34d399" font-size="8">Yes</text>

<!-- No: rightward -->
<line x1="450" y1="200" x2="550" y2="200" stroke="#64748b" marker-end="url(#arrow)"/>
<text x="480" y="193" fill="#fb7185" font-size="8">No</text>
```

## Coloring Strategy

- **Start/End nodes:** Highlight color (blue)
- **Process steps:** Primary (cyan) or Secondary (emerald)
- **Decision diamonds:** Accent (amber) — they draw the eye naturally
- **Error/exception paths:** Alert (rose) dashed arrows
- **Happy path arrows:** Slightly brighter than branch arrows (`stroke-opacity` difference)

## Complex Flowcharts

For flowcharts with 10+ steps:
- Group related steps into swim lanes (vertical columns with header bars)
- Add a "phase" row header at the top of each swim lane
- Use the region boundary pattern from Architecture for swim lanes
references/sequence.md
# Sequence Diagram Layout

## Core Elements

| Element | Visual | Description |
|---------|--------|-------------|
| Actor/Participant | Box at top + dashed vertical lifeline | Each entity in the interaction |
| Sync message | Solid arrow → | Request or call |
| Async message | Open arrowhead → | Fire-and-forget |
| Return message | Dashed arrow ← | Response |
| Activation bar | Narrow filled rect on lifeline | Entity is processing |
| Self-message | Arrow looping back to same lifeline | Internal processing |
| Note | Rounded rect with folded corner | Annotation |
| Alt/Opt frame | Dashed boundary with label tab | Conditional block |
| Loop frame | Dashed boundary with "loop" tab | Repetition |

## Layout Algorithm

1. **Place actors** horizontally across the top, evenly spaced (150-200px apart)
2. **Draw lifelines** as vertical dashed lines from each actor box downward
3. **Place messages** as horizontal arrows between lifelines, top to bottom in time order
4. **Vertical spacing** between messages: 40-50px
5. **Activation bars:** 10px wide, centered on lifeline, spanning from incoming to outgoing message

## Actor Box

```svg
<!-- Actor box -->
<rect x="X" y="20" width="130" height="45" rx="6" fill="#0f172a"/>
<rect x="X" y="20" width="130" height="45" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
<text x="CX" y="47" fill="white" font-size="11" font-weight="600" text-anchor="middle">Actor Name</text>

<!-- Lifeline -->
<line x1="CX" y1="65" x2="CX" y2="BOTTOM" stroke="#334155" stroke-width="1" stroke-dasharray="6,4"/>
```

## Message Arrows

```svg
<!-- Sync message (solid arrow) -->
<line x1="FROM_CX" y1="Y" x2="TO_CX" y2="Y" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="MID_X" y="Y-8" fill="#e2e8f0" font-size="9" text-anchor="middle">methodCall()</text>

<!-- Return message (dashed arrow, reversed direction) -->
<line x1="TO_CX" y1="Y" x2="FROM_CX" y2="Y" stroke="#64748b" stroke-width="1" stroke-dasharray="6,3" marker-end="url(#arrow)"/>
<text x="MID_X" y="Y-8" fill="#94a3b8" font-size="8" text-anchor="middle" font-style="italic">response</text>

<!-- Self-message (loop arrow) -->
<path d="M CX,Y L CX+40,Y L CX+40,Y+25 L CX,Y+25" fill="none" stroke="#94a3b8" stroke-width="1.5" marker-end="url(#arrow)"/>
<text x="CX+45" y="Y+15" fill="#e2e8f0" font-size="8">process()</text>
```

## Activation Bar

```svg
<rect x="CX-5" y="START_Y" width="10" height="H" rx="2" fill="rgba(8,51,68,0.6)" stroke="#22d3ee" stroke-width="1"/>
```

## Conditional / Loop Frames

```svg
<!-- Frame boundary -->
<rect x="X" y="Y" width="W" height="H" rx="4" fill="none" stroke="#64748b" stroke-width="1" stroke-dasharray="4,3"/>
<!-- Frame label tab -->
<rect x="X" y="Y" width="50" height="18" rx="4" fill="rgba(30,41,59,0.8)" stroke="#64748b" stroke-width="1"/>
<text x="X+25" y="Y+13" fill="#94a3b8" font-size="8" font-weight="600" text-anchor="middle">alt</text>
<!-- Condition text -->
<text x="X+60" y="Y+13" fill="#94a3b8" font-size="8" font-style="italic">[condition]</text>
<!-- Divider line for else -->
<line x1="X" y1="MID_Y" x2="X+W" y2="MID_Y" stroke="#64748b" stroke-width="1" stroke-dasharray="4,3"/>
<text x="X+10" y="MID_Y+13" fill="#94a3b8" font-size="8" font-style="italic">[else]</text>
```

## Numbering

For complex sequences (8+ messages), number each message:

```svg
<circle cx="FROM_CX-15" cy="Y" r="8" fill="rgba(59,130,246,0.3)" stroke="#60a5fa" stroke-width="1"/>
<text x="FROM_CX-15" y="Y+3" fill="#60a5fa" font-size="7" font-weight="600" text-anchor="middle">1</text>
```

## Color Assignment

Assign each actor a distinct color from the palette. Use that color for:
- Actor box stroke
- Activation bar on that lifeline
- Outgoing arrows from that actor (optional, for visual clarity in complex diagrams)
references/structural.md
# Structural Diagram Layout

Covers: class diagrams, ER diagrams, component diagrams, package diagrams, org charts.

## Class Diagram

### Class Box (3-compartment)

```svg
<g transform="translate(X, Y)">
  <!-- Mask -->
  <rect width="180" height="120" rx="6" fill="#0f172a"/>
  <!-- Box -->
  <rect width="180" height="120" rx="6" fill="rgba(8,51,68,0.4)" stroke="#22d3ee" stroke-width="1.5"/>
  <!-- Class name compartment -->
  <text x="90" y="24" fill="white" font-size="11" font-weight="700" text-anchor="middle">ClassName</text>
  <!-- Divider 1 -->
  <line x1="0" y1="35" x2="180" y2="35" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.5"/>
  <!-- Attributes -->
  <text x="10" y="52" fill="#94a3b8" font-size="8">- id: int</text>
  <text x="10" y="64" fill="#94a3b8" font-size="8">- name: string</text>
  <!-- Divider 2 -->
  <line x1="0" y1="75" x2="180" y2="75" stroke="#22d3ee" stroke-width="0.5" stroke-opacity="0.5"/>
  <!-- Methods -->
  <text x="10" y="92" fill="#94a3b8" font-size="8">+ getName(): string</text>
  <text x="10" y="104" fill="#94a3b8" font-size="8">+ setName(s: string)</text>
</g>
```

For abstract classes, italicize the class name. For interfaces, add `«interface»` above the name in smaller font.

### Relationship Lines

| Relationship | Line Style | Arrow/End |
|-------------|------------|-----------|
| Inheritance | Solid | Empty triangle (▷) pointing to parent |
| Implementation | Dashed | Empty triangle pointing to interface |
| Composition | Solid | Filled diamond (◆) at owner end |
| Aggregation | Solid | Empty diamond (◇) at owner end |
| Dependency | Dashed | Open arrowhead at dependency target |
| Association | Solid | Open arrowhead or none |

**Markers:**

```svg
<!-- Inheritance triangle -->
<marker id="inherit" markerWidth="12" markerHeight="10" refX="12" refY="5" orient="auto">
  <polygon points="0 0, 12 5, 0 10" fill="#0f172a" stroke="#94a3b8" stroke-width="1.5"/>
</marker>

<!-- Composition diamond -->
<marker id="composition" markerWidth="12" markerHeight="8" refX="0" refY="4" orient="auto">
  <polygon points="0 4, 6 0, 12 4, 6 8" fill="#94a3b8"/>
</marker>

<!-- Aggregation diamond -->
<marker id="aggregation" markerWidth="12" markerHeight="8" refX="0" refY="4" orient="auto">
  <polygon points="0 4, 6 0, 12 4, 6 8" fill="#0f172a" stroke="#94a3b8" stroke-width="1.5"/>
</marker>
```

### Cardinality Labels

Place at each end of the relationship line, offset 5-8px from the box edge:

```svg
<text x="X" y="Y" fill="#94a3b8" font-size="8">1..*</text>
```

## ER Diagram

Similar to class diagrams but:
- Use 2-compartment boxes (entity name + attributes)
- Mark primary keys with `PK` prefix and bold
- Mark foreign keys with `FK` prefix
- Relationship lines use crow's foot notation:

```svg
<!-- One end (single line) -->
<line x1="X1" y1="Y" x2="X1+15" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
<!-- Many end (crow's foot) -->
<line x1="X2-15" y1="Y-6" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
<line x1="X2-15" y1="Y+6" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
<line x1="X2-15" y1="Y" x2="X2" y2="Y" stroke="#94a3b8" stroke-width="1.5"/>
```

## Org Chart

- Top-down tree layout
- Root at top center
- Each level evenly spaced (100-120px vertical gap)
- Siblings evenly distributed horizontally
- Connection lines: vertical from parent bottom center to horizontal bar, then vertical down to each child top center
- Use color to indicate departments or hierarchy levels

## Layout Tips

- Start by counting the widest level to determine total diagram width
- Center the tree horizontally in the viewBox
- For deep trees (5+ levels), consider horizontal layout instead
scripts/main.ts
#!/usr/bin/env bun
import { existsSync, readFileSync, mkdirSync } from "fs";
import { basename, dirname, extname, join, resolve } from "path";

interface Options {
  input: string;
  output?: string;
  scale: number;
  json: boolean;
}

function parseViewBox(svg: string): { width: number; height: number } | null {
  const vb = svg.match(/viewBox\s*=\s*"([^"]+)"/);
  if (vb) {
    const parts = vb[1].split(/[\s,]+/).map(Number);
    if (parts.length >= 4 && parts[2] > 0 && parts[3] > 0) return { width: parts[2], height: parts[3] };
  }
  const w = svg.match(/\bwidth\s*=\s*"(\d+(?:\.\d+)?)"/);
  const h = svg.match(/\bheight\s*=\s*"(\d+(?:\.\d+)?)"/);
  if (w && h) return { width: Number(w[1]), height: Number(h[1]) };
  return null;
}

function getOutputPath(input: string, scale: number, custom?: string): string {
  if (custom) return resolve(custom);
  const dir = dirname(input);
  const base = basename(input, extname(input));
  const suffix = scale === 1 ? "" : `@${scale}x`;
  return join(dir, `${base}${suffix}.png`);
}

async function convert(input: string, opts: Options): Promise<{ output: string; width: number; height: number }> {
  const svg = readFileSync(input);
  const svgStr = svg.toString("utf-8");
  const dims = parseViewBox(svgStr);
  if (!dims) throw new Error("Cannot determine SVG dimensions from viewBox or width/height attributes");

  const width = Math.round(dims.width * opts.scale);
  const height = Math.round(dims.height * opts.scale);

  const sharp = (await import("sharp")).default;
  const output = getOutputPath(input, opts.scale, opts.output);
  mkdirSync(dirname(output), { recursive: true });

  await sharp(svg, { density: 72 * opts.scale })
    .resize(width, height)
    .png()
    .toFile(output);

  return { output, width, height };
}

function printHelp() {
  console.log(`Usage: bun main.ts <input.svg> [options]

Convert SVG to @2x PNG.

Options:
  -o, --output <path>   Output path (default: <input>@2x.png)
  -s, --scale <n>       Scale factor (default: 2)
      --json            JSON output
  -h, --help            Show help`);
}

function parseArgs(args: string[]): Options | null {
  const opts: Options = { input: "", scale: 2, json: false };
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    if (arg === "-h" || arg === "--help") { printHelp(); process.exit(0); }
    else if (arg === "-o" || arg === "--output") opts.output = args[++i];
    else if (arg === "-s" || arg === "--scale") {
      const s = Number(args[++i]);
      if (isNaN(s) || s <= 0) { console.error(`Invalid scale: ${args[i]}`); return null; }
      opts.scale = s;
    } else if (arg === "--json") opts.json = true;
    else if (!arg.startsWith("-") && !opts.input) opts.input = arg;
  }
  if (!opts.input) { console.error("Error: Input SVG file required"); printHelp(); return null; }
  return opts;
}

async function main() {
  const opts = parseArgs(process.argv.slice(2));
  if (!opts) process.exit(1);

  const input = resolve(opts.input);
  if (!existsSync(input)) { console.error(`Error: ${input} not found`); process.exit(1); }
  if (extname(input).toLowerCase() !== ".svg") { console.error("Error: Input must be an SVG file"); process.exit(1); }

  try {
    const r = await convert(input, opts);
    if (opts.json) console.log(JSON.stringify({ input, ...r }, null, 2));
    else console.log(`${input} → ${r.output} (${r.width}×${r.height})`);
  } catch (e) {
    console.error(`Error: ${(e as Error).message}`);
    process.exit(1);
  }
}

main();