# Mindmap

> CRITICAL — if the user asks for a mind map, concept map, knowledge map, visual summary, or says "map this out", "map this", "mind map this", "visualize the structure", "break this down visually", or "turn this into a mind map", you MUST use this skill. Stop everything and read this SKILL.md immediately. The default output is a React (.jsx) artifact with an interactive radial mind map. If the user says "markmap", "markdown map", "--md", "portable", or "I want to open it outside Claude", output Markmap-flavored Markdown (.md) instead. Do NOT respond with a plain text summary or outline. Do NOT use Mermaid.js or Excalidraw.

- Skill: `0x-man/mindmap` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add 0x-man/mindmap`
- Raw SKILL.md: https://api.skillmd.com/api/skills/0x-man/mindmap/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: 0x-man (https://skillmd.com/u/0x-man)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/0x-man/mindmap

---


# Mind Map Generator

Generate interactive mind maps as React (.jsx) artifacts or portable Markmap (.md) files.

## Output Modes

**React (default):** Interactive artifact with pan, zoom, collapse, palette switching,
save button, edge-anchored connectors, and hover tooltips. Best for exploring in Claude.

**Markmap (on request):** Portable Markdown file that renders with markmap.js. Works in
VS Code (Markmap extension), markmap.js.org, or any browser via `npx markmap-cli`.
Best for sharing, embedding, and use outside Claude.

**Trigger Markmap mode when the user says any of:**
"markmap", "markdown map", "--md", "portable format", "I want to open it outside Claude",
"export as markdown map", "make it a .md", "markmap format"

If unclear, default to React.

## When You Receive a Request

1. Determine output mode — React (default) or Markmap (if triggered above)
2. Check for **deep mode**: if the user says "--deep", "deep mode", "think harder", or
   "best possible structure", read `references/judge-panel.md` and follow that workflow.
   For rich documents (3000+ words with competing themes), you may proactively suggest it.
3. Analyze the input content — extract the central topic and 4–7 main branches
4. For **React mode**: Build the `mindmapData` object and generate a React artifact
   with ALL of the Mandatory Code below
5. For **Markmap mode**: Generate a Markmap-flavored `.md` file following the
   Markmap Output section below
6. Optionally read `references/mindmap-best-practices.md` for keyword compression examples

## Content Analysis Strategy

- **Central topic**: One noun phrase, 2–5 words max.
- **Main branches**: 4–7 categories. 5–6 is the sweet spot.
- **Sub-branches**: 2–5 items per branch. Keywords only, not full sentences.
- **Depth limit**: 3 levels max (central → branch → sub-branch).

For long content: identify the main argument → group into thematic clusters → extract
key evidence → discard redundancy. For broad topics: educational overview, foundational
concepts first.

**Branch balance**: Thin branches (1 item) → merge or expand. Fat branches (6+) → split.

### Content Intelligence

**Contradiction detection**: Sources disagree → mark both with ⚡ prefix, add `conflict` field.
**Gap analysis**: Thin evidence → add ❓ prefix, set `type: "inquiry"`.
**Frequency weighting**: Multi-source → `weight` field (1–5).
**Cross-branch links**: Meaningful relationships → `crossLinks` array. Max 3–4.

## Data Structure

```javascript
const mindmapData = {
  central: "Topic Name",
  branches: [
    {
      label: "🎯 Branch Name",  // emoji + 2-5 words
      group: 0,                  // semantic grouping
      children: [
        { label: "Sub-item", detail: "Hover tooltip text" },
        { label: "❓ Gap node", type: "inquiry" },
      ],
    },
  ],
};
```

Fields: `label` (2-5 words), `group` (int, adjacent placement), `detail` (hover text),
`weight` (1-5), `type: "inquiry"`, `conflict` (node ID), `sourceId`, `sources[]`, `crossLinks[]`.

## ═══ MANDATORY CODE — Include ALL of This in Every Artifact ═══

The sections below contain exact code that MUST appear in every generated mind map.
Copy these functions and UI elements verbatim. Do not paraphrase, simplify, or omit them.

### Pill Sizing

```jsx
function getPill(label, depth) {
  var fs = depth === 0 ? 17 : depth === 1 ? 14 : 12;
  var pad = depth === 0 ? 34 : depth === 1 ? 22 : 18;
  return {
    w: Math.max(label.length * fs * 0.62 + pad * 2, 90),
    h: depth === 0 ? 50 : depth === 1 ? 38 : 32,
  };
}
```

### Edge-Anchored Connectors

Lines MUST start and end at the pill EDGE, not the center. Use these two functions:

```jsx
function edgePoint(cx, cy, hw, hh, tx, ty) {
  var dx = tx - cx, dy = ty - cy;
  var dist = Math.sqrt(dx * dx + dy * dy);
  if (dist < 0.01) return { x: cx + hw, y: cy };
  var nx = dx / dist, ny = dy / dist;
  var t = 1.0 / Math.sqrt((nx * nx) / (hw * hw) + (ny * ny) / (hh * hh));
  return { x: cx + nx * t, y: cy + ny * t };
}

function buildCurve(ax, ay, aLabel, aDepth, bx, by, bLabel, bDepth) {
  var pa = getPill(aLabel, aDepth), pb = getPill(bLabel, bDepth);
  var s = edgePoint(ax, ay, pa.w / 2, pa.h / 2, bx, by);
  var e = edgePoint(bx, by, pb.w / 2, pb.h / 2, ax, ay);
  var qx = (s.x + e.x) / 2 + (s.y - e.y) * 0.18;
  var qy = (s.y + e.y) / 2 + (e.x - s.x) * 0.18;
  return "M " + s.x + " " + s.y + " Q " + qx + " " + qy + " " + e.x + " " + e.y;
}
```

When rendering edges, call `buildCurve` with the actual node positions and labels:

```jsx
{edges.map(function(edge, i) {
  var fromN = nodesMap.get(edge.from);
  var toN = nodesMap.get(edge.to);
  if (!fromN || !toN) return null;
  var d = buildCurve(fromN.x, fromN.y, fromN.label, fromN.depth,
                     toN.x, toN.y, toN.label, toN.depth);
  return <path key={"e-" + i} d={d} fill="none" stroke={color}
    strokeWidth={edge.depth === 1 ? 2.5 : 1.6}
    strokeOpacity={edge.depth === 1 ? 0.45 : 0.25} strokeLinecap="round" />;
})}
```

NEVER write `M ${a.x} ${a.y} Q ... ${b.x} ${b.y}` — that draws center-to-center.

### Node Rendering — Fully Opaque Pills

Pill `<rect>` elements MUST be 100% opaque so edges do not bleed through.
CRITICAL: Do NOT use `opacity`, `fillOpacity`, `rgba()`, or 8-digit hex codes to lighten
colors. If you need a lighter color, calculate a solid hex mixed with white. Force `fillOpacity={1}`.

Copy this pattern exactly:

```jsx
{nodes.map(function(node) {
  var pill = getPill(node.label, node.depth);
  var w = pill.w, h = pill.h;
  // Use SOLID colors only. No rgba, no opacity.
  return (
    <g key={node.id} transform={"translate(" + node.x + "," + node.y + ")"}>
      {/* Pill — Forced 100% opacity */}
      <rect x={-w/2} y={-h/2} width={w} height={h}
        rx={node.depth === 0 ? 13 : 19} fill={bgColor} fillOpacity={1} />
      <text textAnchor="middle" dy="0.35em" fontSize={fontSize}
        fontWeight={fontWeight} fill={textColor}
        style={{ fontFamily: "system-ui, sans-serif", pointerEvents: "none" }}>
        {node.label}
      </text>
    </g>
  );
})}
```

### 💾 Save Button & Component Structure

Every mind map MUST include a Save button. To ensure this is not skipped, you MUST use
this exact structural skeleton for your main component:

```jsx
export default function MindMap() {
  var [saveMsg, setSaveMsg] = useState(null);
  var [palName, setPalName] = useState("Bauhaus");

  // 1. Paste handleSave function here exactly as written:
  async function handleSave() {
    var slug = mindmapData.central.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
    var key = "maps:" + slug;
    var existing = null;
    try { existing = await window.storage.get(key); } catch(e) {}
    var now = new Date().toISOString();
    var record = {
      data: mindmapData, palette: palName, layout: "radial",
      createdAt: existing ? JSON.parse(existing.value).createdAt : now,
      updatedAt: now, tags: [],
    };
    try {
      await window.storage.set(key, JSON.stringify(record));
      setSaveMsg(existing ? "Updated ✓" : "Saved ✓");
    } catch(e) { setSaveMsg("Error"); }
    setTimeout(function() { setSaveMsg(null); }, 2000);
  }

  return (
    <div style={{ position: "relative", width: "100%", height: "100vh" }}>
      {/* 2. Toolbar MUST contain the Save Button */}
      <div style={{ position: "absolute", top: 16, left: 16, display: "flex", gap: 8, zIndex: 10 }}>
        {/* Palette Picker goes here */}
        
        {/* MANDATORY SAVE BUTTON */}
        <button onClick={handleSave} style={{
          height: 32, padding: "0 16px", border: "1px solid #ddd", borderRadius: 7,
          background: saveMsg ? "#e8f5ee" : "#fff", cursor: "pointer",
          fontSize: 13, fontWeight: 700, fontFamily: "system-ui, sans-serif",
          color: saveMsg ? "#2D6A4F" : "#555",
        }}>
          {saveMsg || "💾 Save"}
        </button>
      </div>

      {/* 3. SVG Canvas goes here */}
    </div>
  );
}
```

### Palette System

Include all four palettes and a runtime switcher:

```jsx
var PALETTES = {
  Bauhaus: ["#D64045","#1D3557","#E9B44C","#2D6A4F","#7B2D8E","#E07A5F","#457B9D"],
  "Ocean Sunset": ["#E76F51","#2A9D8F","#E9C46A","#264653","#F4A261","#287271","#BC4749"],
  "Nordic Forest": ["#5B8C5A","#4A6FA5","#C17C74","#7B6D8D","#D4A574","#4D8B8B","#8B6F47"],
  "Pastel Garden": ["#7EB8DA","#B5C99A","#E8A87C","#9B8EC1","#F2CC8F","#81B29A","#E07A5F"],
};
```

Add a 🎨 button that shows a dropdown to switch palettes at runtime.

### Layout Algorithm

Radial layout with semantic gravity. Branches sorted by `group`, distributed around
a circle with gaps between groups:

```jsx
function buildLayout(data) {
  var nodes = [], edges = [], cx = 600, cy = 450;
  nodes.push({ id: "root", label: data.central, x: cx, y: cy, depth: 0,
    childIds: [], collapsed: false, bi: -1, detail: null });
  var branches = data.branches || [];
  var sorted = branches.map(function(b, i) { return Object.assign({}, b, { oi: i }); });
  sorted.sort(function(a, b) { return (a.group || 0) - (b.group || 0); });
  var groups = [], lastG = null;
  sorted.forEach(function(b, i) { if (b.group !== lastG) { groups.push(i); lastG = b.group; } });
  var gapExtra = 0.14, totalGap = Math.max(0, groups.length - 1) * gapExtra;
  var baseStep = (2 * Math.PI - totalGap) / sorted.length;
  var angle = -Math.PI / 2;
  sorted.forEach(function(branch, i) {
    if (i > 0 && groups.indexOf(i) !== -1) angle += gapExtra;
    var bAngle = angle + (i * 0.05 - 0.08);
    var bx = cx + Math.cos(bAngle) * 260, by = cy + Math.sin(bAngle) * 260;
    var bId = "b-" + branch.oi;
    nodes.push({ id: bId, label: branch.label, x: bx, y: by, depth: 1,
      childIds: [], collapsed: false, bi: branch.oi, detail: branch.detail || null });
    nodes[0].childIds.push(bId);
    edges.push({ from: "root", to: bId, bi: branch.oi, depth: 1 });
    var kids = branch.children || [];
    kids.forEach(function(child, j) {
      var cc = kids.length, spread = Math.PI * (cc > 4 ? 0.65 : 0.5);
      var sa = bAngle - spread / 2;
      var ca = cc === 1 ? bAngle : sa + (j / (cc - 1)) * spread;
      var sx = bx + Math.cos(ca) * 150, sy = by + Math.sin(ca) * 150;
      var cId = "s-" + branch.oi + "-" + j;
      nodes.push({ id: cId, label: child.label, x: sx, y: sy, depth: 2,
        childIds: [], collapsed: false, bi: branch.oi, detail: child.detail || null });
      nodes.find(function(n) { return n.id === bId; }).childIds.push(cId);
      edges.push({ from: bId, to: cId, bi: branch.oi, depth: 2 });
    });
    angle += baseStep;
  });
  return { nodes: nodes, edges: edges };
}
```

### Required Interaction Features

- **Pan**: mousedown drag on canvas background
- **Zoom**: mouse wheel, plus +/− buttons in toolbar
- **Collapse/expand**: click branch nodes to toggle children
- **Hover tooltip**: fixed position (top-left), shows `detail` text if present
- **Fullscreen**: ⛶ button, fills viewport, Escape to exit

## ═══ END MANDATORY CODE ═══

## Markmap Output Mode

When the user requests Markmap format, output a `.md` file artifact (not React).
The same content analysis rules apply — 4–7 branches, keyword compression, content
intelligence. Only the output format changes.

### Markmap Format

```markdown
---
title: Topic Name
markmap:
  colorFreezeLevel: 2
  maxWidth: 300
---

# Topic Name

## 🧠 Branch One
- Sub-item alpha
- Sub-item beta
  - Detail or nested point
- ❓ *Gap: under-explored area*

## ⚡ Branch Two
- Sub-item delta
- **Key metric: 42%** ← bold for emphasis
- Sub-item zeta

## 🔮 Branch Three
- Sub-item eta
- Sub-item theta
```

### Markmap Rules

- `#` = central topic (one only)
- `##` = main branches (4–7, emoji prefix)
- `-` = sub-items (2–5 per branch)
- `- -` = nested detail (depth 3, use sparingly)
- `**bold**` for key metrics or emphasis
- `*italic*` for inquiry/gap nodes
- `❓` prefix for gaps, `⚡` prefix for contradictions
- Keep all labels to 2–5 words — same compression rules as React mode
- Include YAML frontmatter with `title` and `markmap` config

### Markmap Content Intelligence

Same rules as React mode, adapted for Markdown syntax:

- **Contradictions**: `⚡ Claim conflicts with [Branch X](#)` — bold both, add note
- **Gaps**: `❓ *Under-explored: [topic]*` — italic signals uncertainty
- **Sources**: Add a `## 📚 Sources` branch at the end with numbered references
- **Cross-links**: Add a note like `→ see also: [Branch X, Sub-item Y]`

### When to Suggest Markmap

If the user generates a React mind map and then asks to share it, export it, or use
it outside Claude, offer: "I can also generate this as a Markmap .md file — it works
in VS Code, any browser, and markmap.js.org. Want me to convert it?"

## Conversational Editing

After first generation, users refine through conversation. Only change `mindmapData`.
Do NOT re-read references. Do NOT regenerate engine code.

| User says | Operation |
|---|---|
| "Add X under Y" | Add child node |
| "Remove X" | Delete node |
| "Move X to Y" | Remove from old parent, add to new |
| "Rename X to Y" | Update label |
| "Merge X and Y" | Combine children |
| "Expand X" | Add 2–4 sub-items |
| "Simplify" | Prune depth-3, merge thin branches |
| "Change palette" | Change default palette |
| "Save this map" | Already handled by 💾 button |
| "Show my atlas" | Generate atlas per `references/atlas-storage.md` |
| "Convert to markmap" | Re-output current map as Markmap .md |
| "Convert to React" | Re-output current Markmap as React artifact |
| "--deep" / "think harder" | Run judge panel per `references/judge-panel.md` |

Edit response: State change (1 sentence) → regenerate → stop.

## Edge Cases

- **Very short input**: Brainstorm-style map exploring facets.
- **Very long input**: Compress to 4–7 branches. Mention what was cut.
- **Ambiguous**: Generate first, ask second.
- **Non-English**: Same language as input.

## What NOT To Do

- Do NOT generate connectors using center coordinates — use `edgePoint()` + `buildCurve()`
- Do NOT omit the 💾 Save button — use the component skeleton above with `handleSave`
- Do NOT use `opacity`, `rgba()`, or alpha channels to lighten pill colors — use solid hex only, force `fillOpacity={1}`
- Do NOT respond with a plain text summary — always produce either a React artifact or a Markmap .md file
- Do NOT use Mermaid.js or Excalidraw
- Do NOT skip the palette switcher (React mode)
- Do NOT mix modes — React is an artifact, Markmap is a .md file. Never put Markmap syntax in a React artifact or vice versa

## Output Pattern

**React mode (default):**
1. Brief explanation of extracted structure (1–2 sentences)
2. React artifact with mindmapData + all mandatory code above
3. Always end with this exact offer, so users discover the portable option:
   "💾 The **Save** button (in the toolbar) stores this to your atlas. Want a **portable
   version** you can open outside Claude? Just say *'convert to markmap'* and I'll generate
   a Markdown file that works in VS Code, any browser, or markmap.js.org."

**Markmap mode:**
1. Brief explanation of extracted structure (1–2 sentences)
2. Markmap .md file artifact
3. Note: "Open this in VS Code (Markmap extension), paste at markmap.js.org, or run
   `npx markmap-cli yourfile.md` to view interactively. Want the interactive React
   version instead? Say *'convert to React'*."

**Edits (either mode):**
1. State the change (1 sentence)
2. Regenerate with updated data only
3. Stop.

