# CSS Mind Map Rendering Engine > **⚠️ Before writing any code, read [`_rules.md`](references/_rules.md) — three non-negotiable rules on overlap, hierarchy, and color.** **Core principle: Content-driven, not template-driven. First analyze the content structure, then decide on the layout, and finally render.** --- ## Step 1: Content Analysis After receiving a mind map requirement, **don't write any HTML/CSS yet**. First parse the content into a tree structure, then calculate key metrics. ### 1.1 Build Tree JSON ``` Input: "How to work efficiently from home", branches include "Environment Setup", "Time Management", "Tool Selection"... ↓ Output: { "root": "在家如何高效办公", "branches": [ { "label": "环境准备", "children": ["独立工作区", "降噪耳机", "人体工学椅"] }, { "label": "时间管理", "children": [ { "label": "番茄工作法", "children": ["25分钟专注", "5分钟休息"] }, "日程规划", "避免多任务" ] }, ... ] } ``` ### 1.2 Key Metrics | Metric | Calculation Method | What It Affects | |------|---------|---------| | `branchCount` | Number of first-level branches | Choose single-sided/dual-sided expansion | | `maxDepth` | Maximum nesting depth | Canvas width, whether sub-branch is needed | | `maxChildren` | Maximum children per node | Vertical height of that branch | | `totalNodes` | Total number of all nodes | Overall canvas size | | `maxTextLen` | Longest text character count | Node width, whether line breaks are needed | | `branchWeights[]` | Total descendants per first-level branch | Left-right balance allocation | ### 1.3 Example Analysis ``` Content: "产品经理核心技能", 6 L1 branches, max depth 3, total 35 nodes, max text length 12 chars → branchCount=6, maxDepth=3, totalNodes=35, maxTextLen=12 → branchWeights=[8, 5, 7, 4, 6, 5] ``` --- ## Step 2: Layout Decision Based on the metrics from Step 1, choose a layout scheme. The following are reference suggestions, not hard rules—adjust flexibly according to actual content. ### 2.1 Layout Selection Guide **Core principle: Use simple layouts for less content, dual-sided layouts for more content, avoid one side becoming too long.** ``` Few branches (roughly ≤4), simple content? → Style A: Right-expanding tree (single-sided, compact) Many branches (roughly ≥5), or single side would be too long? → Style B: Left-right expanding tree (dual-sided balanced, most common) User explicitly requests a specific form? → "cards"/"modules" → Style C: Card grid → "fishbone"/"root cause analysis" → Style D: Fishbone diagram ``` **Why not use radial layout?** Radial layout (spreading from center outward) almost inevitably causes node overlap when there are more than 3-4 branches, and is extremely difficult to debug. Not recommended. ### 2.2 Canvas Size Calculation Don't use fixed sizes. Calculate based on content: ```python def calc_canvas(branch_count, max_depth, total_nodes, max_text_len, layout): # Width: depends on depth and text length node_width = max(120, max_text_len * 14) # ~14px per CJK char (including padding) if layout == 'left-right': width = node_width * max_depth * 2 + 200 # Dual-sided, 200px in center for root node else: width = node_width * max_depth + 200 # Single-sided # Height: depends on max branch vertical expansion if layout == 'left-right': max_side_nodes = total_nodes // 2 + 2 # rough estimate of single-side nodes else: max_side_nodes = total_nodes height = max_side_nodes * 32 + 200 # ~32px per leaf (including gap) # Lower bounds width = max(width, 1200) height = max(height, 600) return width, height ``` ### 2.3 Left-Right Branch Allocation (Style B) Goal: Achieve similar visual weight on both sides. ```python def balance_branches(branch_weights): """Greedy bin-packing: sort by weight descending, alternate left/right""" indexed = sorted(enumerate(branch_weights), key=lambda x: -x[1]) left, right = [], [] left_sum, right_sum = 0, 0 for idx, weight in indexed: if left_sum <= right_sum: left.append(idx) left_sum += weight else: right.append(idx) right_sum += weight return left, right # e.g.: weights=[8,5,7,4,6,5] → left=[0,3,5] right=[2,4,1] → 17 vs 18 ``` ### 2.4 Node Styling Decision **Principle: The deeper the level, the lighter the visual weight.** This way readers can distinguish main branches from details at a glance. ``` Root node → most prominent: dark solid background, large font (~20px) L1 branches → next prominent: light fill + colored border, medium font (~15px) L2 nodes → lighter still: paler fill + thin border, medium-small font (~13px) Leaf nodes → lightest: capsule frame or plain text, small font (~12-13px) ``` **Key: Leaves should not have the same visual weight as first-level branches.** Leaves' padding, gap, and border thickness should all be significantly smaller than their parent. Otherwise the chart will be vertically too long and lack hierarchy. --- ## Step 3: Rendering Based on the decisions from Step 2, generate HTML + CSS + JS. ### 3.1 Playwright Screenshot (Universal) ```python import asyncio from playwright.async_api import async_playwright async def mindmap_to_png(html_path, png_path, width=1600): async with async_playwright() as p: browser = await p.chromium.launch(headless=True) page = await browser.new_page(viewport={'width': width, 'height': 1200}, device_scale_factor=2) await page.goto(f'file://{html_path}', wait_until='networkidle') await page.wait_for_timeout(500) el = page.locator('#mindmap') bbox = await el.bounding_box() # First expansion: ensure content is not clipped expand_w = max(width, int(bbox['width'] + 100)) expand_h = int(bbox['height'] + 100) await page.set_viewport_size({'width': expand_w, 'height': expand_h}) await page.wait_for_timeout(200) # Call connector script await page.evaluate('if(typeof drawAllLines==="function") drawAllLines()') await page.wait_for_timeout(200) # Second contraction: measure actual content right edge, trim right-side blank space trim = await page.evaluate('''() => { const map = document.getElementById('mindmap'); const nodes = map.querySelectorAll('.root-node,.branch-node,.sub-node,.leaf,.deep-node'); const mapRect = map.getBoundingClientRect(); let maxR = 0, maxB = 0; nodes.forEach(n => { const r = n.getBoundingClientRect(); maxR = Math.max(maxR, r.right - mapRect.left); maxB = Math.max(maxB, r.bottom - mapRect.top); }); return { contentW: Math.ceil(maxR) + 80, contentH: Math.ceil(maxB) + 80 }; }''') await page.set_viewport_size({'width': trim['contentW'], 'height': trim['contentH']}) await page.wait_for_timeout(200) # Redraw connectors (viewport changed so coordinates change) await page.evaluate('if(typeof drawAllLines==="function") drawAllLines()') await page.wait_for_timeout(200) await el.screenshot(path=png_path) await browser.close() import os print(f'✅ {png_path} ({os.path.getsize(png_path)/1024:.0f}KB)') ``` ### 3.2 Universal Recursive Connector Script v4-fix (All Styles) This script automatically handles tree connectors of **any depth** (3, 4, 5 levels... all work). HTML for styles A and B should include this script at the end. **⚠️ DOM Structure Convention (connector script depends on this structure):** ``` #mindmap .tree-layout ← flex container (for left-right tree) .left-side ← left branch area .branch.c-{color} ← L1 branch (must have color class) .branch-node ← L1 node .children ← L2 container div ← child wrapper .sub-node ← L2 node .leaf-group ← L3 container div / .leaf ← leaf or wrapper with deeper levels .leaf ← L3 leaf .deep-group ← L4 container (recursive, same as above) .deep-node ← L4/L5 node .center-root .root-node ← root node .right-side ← right branches (same structure as .left-side) ``` **Also supports legacy structure (.branches/.right-branches/.left-branches), backward compatible.** **⚠️ CSS Indentation Rules (connectors depend on child nodes having offset relative to parent, no indentation = broken connectors):** ```css /* .tree-layout structure (new version) must include these paddings */ .left-side .children, .left-side .leaf-group, .left-side .deep-group { align-items: flex-end; padding-right: 16px; } .right-side .children, .right-side .leaf-group, .right-side .deep-group { align-items: flex-start; padding-left: 16px; } ``` **Common causes of missing connectors/layout errors:** - **⚠️ `.sub-branch` missing `display: flex`** (most critical! Without flex, `flex-direction: row-reverse` doesn't work, left-side leaves won't expand left, instead all pile up on the right) - **⚠️ Child node containers missing padding-left/padding-right** (without indentation, child nodes align with parent, midX is outside child nodes, connectors break) - **⚠️ `.lr-tree` / `.tree` should not have `z-index`** (creates stacking context, covers SVG connectors) - **⚠️ Leaf nodes must NOT stretch to equal width** — each leaf should size to its own text content (`white-space: nowrap` or `width: fit-content`). Never add `width: 100%`, `flex-grow: 1`, or `align-items: stretch` to leaf containers. Leaves with shorter text should be narrower than leaves with longer text. - Leaf container not named `.leaf-group` (using `.child-list`, `.sub-items`, etc.) - Deep-level container not named `.deep-group` - `#mindmap` missing `position: relative` **Key improvements (v4-fix vs v2):** - **No transparency**—use solid color blend for fading (`color + '80'` is almost invisible on white background) - **Recursively process `.children`, `.leaf-group`, `.deep-group`**—no longer limited to 3 levels - **Vertical line draws complete range**—one line from `min(Y)` to `max(Y)`, instead of drawing per child node ```javascript /** * Universal recursive connector script v4-fix * - Supports any nesting depth (recursively processes .children + .leaf-group + .deep-group) * - Unified gray-tone connectors (#64748B → #94A3B8 → #A8B4C2), visually clean * - Direction logic: * dir='left': startX=parent.left → midX(left-biased) → endX=child.right * dir='right': startX=parent.right → midX(right-biased) → endX=child.left */ function drawAllLines() { const map = document.getElementById('mindmap'); if (!map) { console.error('❌ #mindmap not found'); return; } const cRect = map.getBoundingClientRect(); const old = map.querySelector('svg.lines'); if (old) old.remove(); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.classList.add('lines'); svg.setAttribute('width', cRect.width); svg.setAttribute('height', cRect.height); svg.style.cssText = 'position:absolute;top:0;left:0;pointer-events:none;z-index:1;'; let lineCount = 0; function rel(el) { const r = el.getBoundingClientRect(); return { cx: r.left - cRect.left + r.width/2, cy: r.top - cRect.top + r.height/2, left: r.left - cRect.left, right: r.right - cRect.left, }; } function drawLine(x1, y1, x2, y2, color, width) { const l = document.createElementNS('http://www.w3.org/2000/svg', 'line'); // Round to nearest pixel to prevent sub-pixel misalignment (visual kinks) l.setAttribute('x1', Math.round(x1)); l.setAttribute('y1', Math.round(y1)); l.setAttribute('x2', Math.round(x2)); l.setAttribute('y2', Math.round(y2)); l.setAttribute('stroke', color); l.setAttribute('stroke-width', width); l.setAttribute('stroke-linecap', 'round'); svg.appendChild(l); lineCount++; } // ─── Unified gray connectors (decreasing by depth, visually clean) ─── const lineStyles = [ { color: '#64748B', width: 2.5 }, // root → L1 { color: '#94A3B8', width: 2 }, // L1 → L2 { color: '#A8B4C2', width: 1.5 }, // L2 → L3 { color: '#B8C2CC', width: 1.2 }, // L3 → L4 { color: '#CBD5E1', width: 1 }, // L4 → L5 ]; function getLineStyle(branchColor, depth) { const s = lineStyles[Math.min(depth, lineStyles.length - 1)]; return { color: s.color, width: s.width }; } // ─── Connector direction ─── // dir='left': parent.left → midX → child.right (each child gets its own polyline) // dir='right': parent.right → midX → child.left // // [Principle] midX is the X coordinate of the vertical line; it must be in the gap between parent and children. // Use a fraction of the parent-to-nearest-child distance as offset, so: // - Large node spacing → large offset, lines spread out // - Small node spacing → small offset, lines compact without crossing text // All children in the same connect() call share one midX, ensuring vertical line alignment. function connect(parentEl, childEls, color, width, dir) { if (!childEls.length) return; const p = rel(parentEl); const startX = dir === 'left' ? p.left : p.right; const startY = p.cy; // Special case: single child — draw one straight horizontal line, no vertical spine if (childEls.length === 1) { const c = rel(childEls[0]); const endX = dir === 'left' ? c.right : c.left; const midY = Math.round((startY + c.cy) / 2); drawLine(startX, midY, endX, midY, color, width); return; } // Find the closest child edge to calculate available space let closestEdge; if (dir === 'left') { closestEdge = Math.max(...childEls.map(ch => rel(ch).right)); } else { closestEdge = Math.min(...childEls.map(ch => rel(ch).left)); } // Place midX at the midpoint between parent edge and closest child edge, // but guarantee at least 16px clearance from child nodes const childClearance = 16; const midpoint = startX + (closestEdge - startX) / 2; const midFromChild = dir === 'left' ? closestEdge + childClearance : closestEdge - childClearance; // Use the position that's further from children (safer) const midX = dir === 'left' ? Math.min(midpoint, midFromChild) : Math.max(midpoint, midFromChild); drawLine(startX, startY, midX, startY, color, width); // Draw ONE continuous vertical line spanning from parent to the last child const allCYs = childEls.map(ch => rel(ch).cy); const minY = Math.min(startY, ...allCYs); const maxY = Math.max(startY, ...allCYs); drawLine(midX, minY, midX, maxY, color, width); // Then draw horizontal lines from the vertical spine to each child childEls.forEach(ch => { const c = rel(ch); const endX = dir === 'left' ? c.right : c.left; const endY = c.cy; drawLine(midX, endY, endX, endY, color, width); }); } // ─── Recursively process subtree (supports .children + .leaf-group + .deep-group) ─── const NODE_SEL = '.branch-node, .sub-node, .leaf, .deep-node'; const CONTAINER_SEL = ':scope > .children, :scope > .leaf-group, :scope > .deep-group'; function processChildren(parentNodeEl, containerEl, branchColor, depth, dir) { if (!containerEl) return; const childNodeEls = []; for (const wrapper of containerEl.children) { const nodeEl = wrapper.matches?.(NODE_SEL) ? wrapper : wrapper.querySelector(NODE_SEL); if (nodeEl) childNodeEls.push(nodeEl); } if (!childNodeEls.length) return; const style = getLineStyle(branchColor, depth); connect(parentNodeEl, childNodeEls, style.color, style.width, dir); for (const wrapper of containerEl.children) { const nodeEl = wrapper.matches?.(NODE_SEL) ? wrapper : wrapper.querySelector(NODE_SEL); if (!nodeEl) continue; wrapper.querySelectorAll(CONTAINER_SEL).forEach(nc => processChildren(nodeEl, nc, branchColor, depth + 1, dir) ); } } // ─── Main flow ─── const rootNode = map.querySelector('.root-node'); if (!rootNode) { console.error('❌ .root-node not found'); return; } const rp = rel(rootNode); // Left side: collect all L1 branch-nodes, draw polylines with connect() (with vertical spine) const leftSel = '.left-side > .branch, .left-branches > .left-branch, .left-branches > div'; const leftBranches = map.querySelectorAll(leftSel); const leftBranchNodes = []; leftBranches.forEach(branch => { const bNode = branch.querySelector('.branch-node'); if (bNode) leftBranchNodes.push(bNode); }); if (leftBranchNodes.length) { connect(rootNode, leftBranchNodes, lineStyles[0].color, lineStyles[0].width, 'left'); } leftBranches.forEach(branch => { const bNode = branch.querySelector('.branch-node'); if (!bNode) return; processChildren(bNode, branch.querySelector(':scope > .children'), null, 1, 'left'); }); // Right side: same as above const rightSel = '.right-side > .branch, .branches > .branch, .right-branches > .right-branch'; const rightBranches = map.querySelectorAll(rightSel); const rightBranchNodes = []; rightBranches.forEach(branch => { const bNode = branch.querySelector('.branch-node'); if (bNode) rightBranchNodes.push(bNode); }); if (rightBranchNodes.length) { connect(rootNode, rightBranchNodes, lineStyles[0].color, lineStyles[0].width, 'right'); } rightBranches.forEach(branch => { const bNode = branch.querySelector('.branch-node'); if (!bNode) return; processChildren(bNode, branch.querySelector(':scope > .children'), null, 1, 'right'); }); map.insertBefore(svg, map.firstChild); console.log(`✅ Drew ${lineCount} lines`); } ``` **How to call (at end of HTML):** ```html ``` ### 3.3 Universal CSS Base (Shared by All Styles) ```css * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #FFFFFF; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'SimHei', sans-serif; } #mindmap { padding: 60px; display: inline-block; min-width: 100%; position: relative; } #mindmap > svg.lines { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; } /* ─── Root Node ─── */ /* ─── Topic Intent Color System ─── */ /* Model picks ONE intent based on content semantics. Add data-intent="xxx" to #mindmap. DO NOT manually write hex colors for root node — use intent system only. */ /* Intent → Root Node + Branch Palette mapping */ #mindmap[data-intent="professional"] .root-node { background: linear-gradient(135deg, #1E3A5F, #2D4A6F); box-shadow: 0 4px 12px rgba(30,58,95,0.25); } #mindmap[data-intent="technical"] .root-node { background: linear-gradient(135deg, #334155, #475569); box-shadow: 0 4px 12px rgba(51,65,85,0.25); } #mindmap[data-intent="medical"] .root-node { background: linear-gradient(135deg, #0F766E, #0D9488); box-shadow: 0 4px 12px rgba(15,118,110,0.25); } #mindmap[data-intent="education"] .root-node { background: linear-gradient(135deg, #9A3412, #B45309); box-shadow: 0 4px 12px rgba(154,52,18,0.25); } #mindmap[data-intent="creative"] .root-node { background: linear-gradient(135deg, #7C3AED, #8B5CF6); box-shadow: 0 4px 12px rgba(124,58,237,0.25); } #mindmap[data-intent="finance"] .root-node { background: linear-gradient(135deg, #1E3A5F, #1E40AF); box-shadow: 0 4px 12px rgba(30,58,95,0.25); } #mindmap[data-intent="nature"] .root-node { background: linear-gradient(135deg, #166534, #15803D); box-shadow: 0 4px 12px rgba(22,101,52,0.25); } #mindmap[data-intent="warning"] .root-node { background: linear-gradient(135deg, #991B1B, #B91C1C); box-shadow: 0 4px 12px rgba(153,27,27,0.25); } #mindmap[data-intent="neutral"] .root-node { background: linear-gradient(135deg, #334155, #475569); box-shadow: 0 4px 12px rgba(51,65,85,0.25); } /* Intent Selection Guide (for model): professional → corporate reports, strategy, management, business plans technical → software, engineering, architecture, systems, AI/ML medical → healthcare, clinical, pharmaceutical, nursing, anatomy education → teaching, learning, curriculum, training, academic creative → design, art, marketing, branding, media finance → banking, investment, accounting, economics, trading nature → environment, ecology, agriculture, biology, geography warning → risk analysis, safety, incident review, compliance, audit neutral → general topics, mixed content, unclear domain Recommended branch color combos per intent: professional → [blue, teal, cyan] technical → [blue, purple, cyan] medical → [teal, green, blue] education → [amber, green, blue] creative → [purple, amber, cyan] finance → [blue, green, cyan] nature → [green, teal, amber] warning → [red, amber, cyan] neutral → [blue, green, purple] */ .root-node { color: white; font-size: 20px; font-weight: 700; padding: 18px 28px; border-radius: 12px; white-space: nowrap; flex-shrink: 0; align-self: center; } /* Fallback if no intent specified — defaults to professional blue */ #mindmap:not([data-intent]) .root-node { background: linear-gradient(135deg, #1E3A5F, #2D4A6F); box-shadow: 0 4px 12px rgba(30,58,95,0.25); } /* ─── Container Layout ─── */ .tree { display: flex; align-items: flex-start; gap: 0; position: relative; } .branches { display: flex; flex-direction: column; gap: 16px; margin-left: 60px; } .branch, .sub-branch { display: flex; align-items: flex-start; gap: 0; } /* ─── First-Level Branch Node ─── */ .branch-node { font-size: 15px; font-weight: 600; padding: 10px 20px; border-radius: 8px; white-space: nowrap; flex-shrink: 0; border: 2px solid; } /* ─── Second-Level Sub-Node (when has children) ─── */ .sub-node { font-size: 13px; font-weight: 500; padding: 7px 14px; border-radius: 6px; white-space: nowrap; flex-shrink: 0; border: 1.5px solid; background: #F8FAFC; } /* ─── Leaf Node ─── */ /* Default: lightweight capsule frame */ .leaf { font-size: 13px; font-weight: 400; color: #475569; padding: 4px 10px; background: #FAFAFA; border-radius: 12px; border: 1px solid #E5E7EB; white-space: nowrap; line-height: 1.5; } /* Text-only mode (more compact, add class="leaf text-only") */ .leaf.text-only { background: none; border: none; padding: 2px 0; border-radius: 0; } /* Leaf supplementary description */ .leaf-desc { font-size: 12px; color: #94A3B8; font-weight: 400; margin-left: 8px; } .leaf-desc::before { content: '— '; color: #CBD5E1; } /* ─── Child Node Container ─── */ /* Principle: The deeper the level, the smaller the gap, but not so small that connectors and text overlap */ .children { display: flex; flex-direction: column; gap: 6px; margin-left: 48px; align-items: flex-start; } .children:has(.sub-branch) { gap: 10px; } /* sub-branch is larger than leaf, needs more spacing */ .sub-branch .children { gap: 5px; margin-left: 40px; align-items: flex-start; } .sub-branch .sub-branch .children { gap: 4px; margin-left: 36px; align-items: flex-start; } /* Tip: If leaf text and connectors overlap, prioritize increasing gap */ /* ─── Color System (for distinguishing different branches) ─── */ .c-blue { background: #EFF6FF; border-color: #3B82F6; color: #1E40AF; } .c-green { background: #F0FDF4; border-color: #10B981; color: #065F46; } .c-amber { background: #FFF7ED; border-color: #F59E0B; color: #92400E; } .c-purple { background: #F5F3FF; border-color: #8B5CF6; color: #5B21B6; } .c-red { background: #FEF2F2; border-color: #EF4444; color: #991B1B; } .c-cyan { background: #ECFEFF; border-color: #06B6D4; color: #155E75; } .c-teal { background: #F0FDFA; border-color: #14B8A6; color: #134E4A; } /* Second-level sub-node inherits branch color scheme (lighter) */ .c-blue .sub-node { background: #F0F7FF; border-color: #93C5FD; color: #1E40AF; } .c-green .sub-node { background: #F2FDF6; border-color: #6EE7B7; color: #065F46; } .c-amber .sub-node { background: #FFF9F0; border-color: #FCD34D; color: #92400E; } .c-purple .sub-node { background: #F8F6FF; border-color: #C4B5FD; color: #5B21B6; } .c-red .sub-node { background: #FFF5F5; border-color: #FCA5A5; color: #991B1B; } .c-cyan .sub-node { background: #F0FEFF; border-color: #67E8F9; color: #155E75; } .c-teal .sub-node { background: #F2FDFA; border-color: #5EEAD4; color: #134E4A; } ``` --- ## Style A: Right-Expanding Tree (Simple scenarios with branchCount ≤ 4 or maxDepth ≤ 2) ### HTML Structure ```html
中心主题
一级分支A
叶子1
叶子2
一级分支B
二级有下级
三级叶子1
三级叶子2
二级叶子
``` --- ## Style B: Left-Right Expanding Tree (Standard scenario with branchCount ≥ 5, most common) ### Additional CSS (append after universal base) ```css /* ─── Left-Right Expanding Layout ─── */ .lr-tree { display: flex; align-items: center; gap: 0; position: relative; } .lr-tree .root-node { padding: 20px 32px; border-radius: 14px; margin: 0 60px; text-align: center; } /* ⚠️ sub-branch must be flex, otherwise flex-direction: row-reverse won't work, leaves won't expand left */ .sub-branch { display: flex; align-items: flex-start; gap: 0; } .left-branches { display: flex; flex-direction: column; gap: 16px; } .left-branch { display: flex; align-items: flex-start; flex-direction: row-reverse; gap: 0; } .left-branch .children { display: flex; flex-direction: column; gap: 6px; margin-right: 48px; align-items: flex-end; } .left-branch .children:has(.sub-branch) { gap: 10px; } .left-branch .sub-branch .children { gap: 5px; margin-right: 40px; margin-left: 0; align-items: flex-end; } .left-branch .sub-branch { flex-direction: row-reverse; } .right-branches { display: flex; flex-direction: column; gap: 16px; } .right-branch { display: flex; align-items: flex-start; gap: 0; } .right-branch .children { display: flex; flex-direction: column; gap: 6px; margin-left: 48px; align-items: flex-start; } .right-branch .children:has(.sub-branch) { gap: 10px; } .right-branch .sub-branch .children { gap: 5px; margin-left: 40px; align-items: flex-start; } ``` ### HTML Structure ```html
分支D(放左边)
叶子1
叶子2
中心主题
分支A(放右边)
叶子1
叶子2
``` --- ## Style C: Card Grid (when user explicitly requests "cards" / "modules") > ⚠️ This is not a mind map — it's a modular display. No connectors; use cards + color coding to show relationships. ```html
标题
副标题
📋
模块A
  • 条目1
  • 条目2
``` --- ## Style D: Fishbone Diagram (Problem Analysis / Root Cause) Best for problem analysis, root cause tracing, quality management (Ishikawa diagram). ```html
问题/结果
子原因1
子原因2
原因类别A
原因类别B
子原因1
子原因2
``` --- ## Quality Checklist After rendering, verify against this checklist: 1. **Content complete** — Every node from the original requirement is in the map, nothing missing 2. **Clear hierarchy** — L1 branches and leaves are instantly distinguishable (different bg/border/font-weight) 3. **No overlap** — No boxes covering boxes, no lines through text 4. **Connectors visible** — Every parent-child pair has a connector, no orphan leaves. Connector color ≥ `#94A3B8` (too light = invisible) 5. **Connector direction correct** — Left-side leaves extend left, right-side extends right 6. **Proportions reasonable** — Map is not extremely narrow/tall (target aspect ratio 1:1 to 3:1), visually comfortable 7. **Text readable** — At final output size, smallest text is legible. Reference: root 18px+, L1 15px+, L2 13px+, leaves 12px+ 8. **Roughly balanced** (Style B) — Visual weight approximately equal on both sides, doesn't need to be symmetric. Target ≤30% difference 9. **Canvas large enough** — No nodes clipped, sufficient padding on all sides (reference 60px+) 10. **No large blank areas on right** — Screenshot trimmed to content edge --- ## ⛔ Radial Layout Warning **Radial layout is strongly discouraged.** Using `position: absolute` for fixed branch positions leads to overlap when branches increase, and deep levels cannot be handled. If content is very simple (reference: ≤3 branches, ≤3 children per branch, text ≤6 chars, depth ≤2, total ≤12 nodes), it's technically possible, but tree layout is always the safer choice. **No code template provided.** If conditions are met, manually adjust based on Style A.