Generate Neo-Brutalism styled figure images for blog posts: HTML → browser → PNG.
This skill's design tokens (1440×810 capture, retina 2x, Noto Sans KR weight 900, AGENTIC v1.2
palette — figure.css :root + design-rules.md "color grammar" is the SSoT) are blog-only, and are a different
generation from the PPT-oriented
old-generation neo-brutalism document (1920×1080, Black Han Sans, image-design-system.md) — do not use that document as reference material for modifying this skill.
Runtime notes: In this document, AskUserQuestionis a Claude Code tool name. In Codex, use request_user_inputwith the same
question/option structure, but preview in runtimes without the field, put the ASCII
preview in the question body as a code fence. If there is no structured question tool at all, present the same options as a numbered
list text and wait for the answer.
Workflow
When requesting modification of an existing figure: first look for {filename}.src.htmlnext to the target PNG. If it exists, do not go through the new
pipeline (steps 1–4) again; instead, briefly run: Edit that file → reinterpret the {SKILL_DIR}of the figure.css link
as the current skill's absolute path → Self-Check (step 5) → recapture (from step 6). If it does not exist,
use the new pipeline below.
- Understand context: Read blog MDX/MD or user description to decide what to visualize
- Content Brief: Extract the core concept to visualize and present to user for confirmation (see Content Brief below)
- Suggest patterns: Based on the confirmed brief, pick the 4 most fitting patterns from 30 available, and present them via
AskUserQuestionwith ASCII art previews (see Pattern Selection below) - Create HTML: Before writing HTML, read
references/design-rules.mdfor all design constraints. Write standalone HTML to/tmp/blog-figure-{name}.htmllinkingassets/figure.css - Self-check before capture: Run
scripts/validate_figure.pyon the HTML and fix every ERROR before spending a capture (see Self-Check below). A browser screenshot won't tell you a font dropped to 14px or a color is hardcoded — the linter does, in a second, for free. - Capture PNG: Open in browser, capture the 1440×810 layout at retina 2x (deviceScaleFactor 2) → save as 2880×1620 PNG. In the fallback path (Playwright) that cannot produce 2x, 1440×810 (1x) is the last resort (see Capture)
- Save to project: Move PNG to
web/public/blog/images/{slug}/(repo root인agentic30-greenfieldrelative path — with the 2026-07-05 web/ integration, the old pathblog/public/…is deprecated). If this directory does not exist in the running repo, do not guess arbitrarily; ask the user for the save location. When saving the PNG, also copy the source HTML to the same directory as{filename}.src.html. In the copy, restore the absolute path of the figure.css link to the literalfile://{SKILL_DIR}/assets/figure.css(so that the local user path is not exposed on the deployed site). This copy is what makes the next session's "just reduce the margins" a single Edit + recapture instead of a full rewrite - Insert into document: If user provided a
.mdor.mdxfile, insert the image tag at the contextually correct location (see Document Insertion below) - Verify: Create a 25% reduced version of the saved PNG and actually observe it — mobile screen width simulation:
Open the reduced version withsips -Z 360 {saved}.png --out /tmp/blog-figure-verify-{name}.png # macOS. 그 외: magick {saved}.png -resize 360x /tmp/blog-figure-verify-{name}.pngReadand judge: Is the pattern structure recognized as a form? Are the key keywords readable? If either is not, fix the HTML and rerun from step 5. Do not judge with the original-size PNG — what is readable in the original proves nothing.
Content Brief
Before choosing a pattern ,, extract the key concepts to visualize from the blog and confirm them with the user. This is a key step to prevent the figure's content from being disconnected from the blog or becoming too abstract.
Extraction items
| Item | Description | Example |
|---|---|---|
| Key message | One sentence this figure should convey | "What users say and what they actually do are different" |
| Keywords | 3–5 words that will actually go into the figure | "Sounds good", "0 cases", "words vs actions" |
| Structure | Type of relationship between concepts | Contrast (A vs B), order (A→B→C), hierarchy (A⊃B), cycle (A↻B) |
| Emphasis | What the viewer must recognize first | "The shock of the number 0 cases" |
Extraction method
Find the following in the blog post:
- The post's core claim/conclusion — found in the title, the last sentence of the introduction, and the first sentence of the conclusion
- Concrete examples/data — concrete numbers, quotes, and examples are more suitable for a Figure than abstract concepts
- Contrast/transition structure — turning points such as "but," "on the other hand," "previously ~ but now," "not X but Y"
- The reader's Aha moment — the point where the reader pauses with an "oh" while reading. That is the reason the Figure exists
What to avoid
- Listing the post's table of contents as is (a Figure is not a table of contents)
- Listing only abstract keywords ("strategy," "execution," "performance" → if it can be inserted into any post, it is a bad Brief)
- Attempting to summarize the entire post (a Figure captures only one sceneof the post)
User confirmation
AskUserQuestionpresent 2–3 visualization directions. Each option is a different interpretation of "what to make into a figure from this post."
AskUserQuestion({
questions: [{
question: "어떤 장면을 Figure로 만들까요?",
header: "장면 선택",
multiSelect: true,
options: [
{
label: "해석 A: {1줄 핵심 메시지}",
description: "키워드: {단어1}, {단어2}, {단어3}",
preview: "**구조**: {관계 유형}\n**강조점**: {가장 눈에 띄어야 할 것}\n**근거**: 블로그에서 이 부분이 Figure로 적합한 이유 1줄"
},
{
label: "해석 B: {1줄 핵심 메시지}",
description: "키워드: {단어1}, {단어2}, {단어3}",
preview: "**구조**: {관계 유형}\n**강조점**: {가장 눈에 띄어야 할 것}\n**근거**: 블로그에서 이 부분이 Figure로 적합한 이유 1줄"
},
{
label: "해석 C: {1줄 핵심 메시지}",
description: "키워드: {단어1}, {단어2}, {단어3}",
preview: "**구조**: {관계 유형}\n**강조점**: {가장 눈에 띄어야 할 것}\n**근거**: 블로그에서 이 부분이 Figure로 적합한 이유 1줄"
}
]
}]
})
Important: each interpretation must capture different parts/perspectivesof the post. Do not present 3 versions of the same content reworded, but 3 genuinely different scenes.
multiSelect: truethe reason — N scenes = N Figures: one Figure contains only one core concept (One Idea Per Figure). So instead of cramming multiple scenes into one, for the scenes the user selects, for each one separately , create them.
- Select 1 → 1 Figure.
- Select 2 or more → for each selected scene, repeat Pattern Selection → HTML → Self-Check → Capture → Save scene by scene. Add a suffix to the filename that distinguishes the scene to prevent collisions (
{slug}-{scene}.png). - If the user selects nothing or says "you decide," proceed with only the 1 scene with the strongest basis and state the reason in one line.
Content Brief → Pattern Selection connection
When the user confirms the Brief, the Brief's structurenaturally narrows the pattern selection:
| Brief structure | Suitable pattern (priority) |
|---|---|
| Contrast (A vs B) | Comparison, Flow (split), Matrix, Network |
| Sequence (A→B→C) | Flow, Journey, Timeline, Storyboard |
| Hierarchy (A⊃B⊃C) | Architecture, Hierarchy, Isometric, Schema |
| Cycle (A↻B) | Loop, State, Graph |
| Conceptual relationship/Venn diagram | Concept, Network, Matrix |
| Numerical comparison | Data Viz, Funnel, Timeline, Waffle, Dumbbell, Bullet |
| Ratio/composition | Waffle, Treemap, Funnel |
| Multidimensional evaluation | Radar, Matrix |
| Ranking change | Slope, Data Viz |
| Density/frequency | Heatmap, Waffle |
| Actual/target | Bullet, Data Viz |
| Trend summary | Sparkline Grid, Data Viz |
| Increase/decrease decomposition | Waterfall, Data Viz |
| Quote/declaration | Typographic Statement |
| Interaction | Interaction, Terminal, IconDiagram, Storyboard |
| Abstract/spatial structure | Isometric, Network, Graph |
| System connection | IconDiagram, Architecture, Interaction |
Pattern Selection
After understanding context, use AskUserQuestion to let the user pick from the 4 most relevant patterns. Each option MUST include a preview field with an ASCII art preview showing the pattern's layout structure. previewis rendered as markdown, so wrap ASCII art in triple backtick code fences (…) to preserve monospace alignment — if placed outside the code fence, it renders in a proportional font and the box gets misaligned.
How to pick the 4 patterns
Analyze the user's content and rank all 30 patterns by relevance:
- Comparison: X vs Y, left-right contrast, before/after
- Flow: process, step-by-step differences, methodology
- Timeline: time allocation, proportions, order
- Concept: relationship diagram, Venn diagram, concepts
- Architecture: system configuration, layers, components
- Interaction: sequence, request/response, API flow
- State: state transitions, lifecycle
- Schema: DB model, entity relationships
- Hierarchy: tree, org chart, classification
- Matrix: 2x2 analysis, comparison table
- Journey: user journey, touchpoints
- Funnel: conversion rate, step-by-step drop-off
- Loop: cyclical process, feedback
- Data Viz: numerical comparison, bar chart
- Storyboard: scenario, step-by-step scenes
- Terminal: CLI visualization, terminal UI, tool usage scenes
- Isometric: 3D blocks, wireframe, layer structure [SVG]
- IconDiagram: system diagram, icon connections, technical architecture diagram [SVG]
- Network: node network, determinism↔probability contrast, abstract relationships [Canvas]
- Graph: force-directed graph, automatic node-link layout [D3]
- Waffle: sense of proportion, percentage visualization, N out of 100 cells [SVG]
- Typographic Statement: editorial quote, key definition, declarative message [SVG]
- Slope: before/after ranking changes, ranking shifts, comparison of two points in time [SVG]
- Treemap: area-proportional composition, category distribution [D3]
- Radar: multi-axis profile comparison, competency assessment, multidimensional metrics [SVG+JS]
- Dumbbell: gap/range between two values, gap comparison [SVG]
- Heatmap: 2D frequency/density, distribution by time and category [Canvas]
- Bullet: actual vs target, KPI achievement rate [SVG]
- Sparkline Grid: trend summary for many items, grid of small line charts [SVG+JS]
- Waterfall: increase/decrease decomposition, cumulative change, contribution by factor [SVG]
Select Top 4 and call AskUserQuestion as follows:
AskUserQuestion({
questions: [{
question: "어떤 Figure 패턴이 가장 적합할까요?",
header: "패턴 선택",
multiSelect: false,
options: [
{
label: "{Pattern 1 이름}",
description: "{왜 이 패턴이 적합한지 1줄 설명}",
preview: "```\n{ASCII art preview}\n```"
},
// ... 3개 더
]
}]
})
ASCII Art Previews
After deciding on the 4 patterns, AskUserQuestion call before
references/pattern-previews.md, read it and fetch the ASCII art for that pattern.
Real preview (only upon request)
If the user hesitates to decide based on ASCII alone or requests "what it actually looks like" or "other patterns too",
do not create a new HTML — open the existing gallery — python3 {SKILL_DIR}/scripts/render_pattern_previews.py --clean
After running, file:///tmp/blog-figure-previews/index.htmlin the browser (same priority as the capture tool:
Chrome DevTools MCP → if that fails, tell the user the file:// path), and inform them of the pattern numbers/names
of the 4 candidates. The gallery uses generic content, but it faithfully shows the Neo-Brutalism colors, borders, typography, and 16:9 proportions,
so it is sufficient for pattern selection. After confirming, return to AskUserQuestionand get the final confirmation.
Example: AskUserQuestion Call
When the user requests "visualize the user interview process":
AskUserQuestion({
questions: [{
question: "어떤 Figure 패턴이 가장 적합할까요?",
header: "패턴 선택",
multiSelect: false,
options: [
{
label: "Flow (추천)",
description: "인터뷰 단계를 수직 플로우로 표현. 프로세스 시각화에 최적",
preview: "```\n┌──────────────────────────────────┐\n│ ┌───────────┐ │\n│ │ 맥락 확인 │ │\n│ └─────┬─────┘ │\n│ ▼ │\n│ ┌───────────┐ │\n│ │ 사례 복기 │ │\n│ └─────┬─────┘ │\n│ ▼ │\n│ ┌───────────┐ │\n│ │ 니즈 발견 │ │\n│ └───────────┘ │\n└──────────────────────────────────┘\n```"
},
{
label: "Journey",
description: "인터뷰이의 여정을 수평 터치포인트로 표현",
preview: "```\n┌──────────────────────────────────┐\n│ ①─────────②─────────③────────④ │\n│ 준비 라포 질문 정리│\n└──────────────────────────────────┘\n```"
},
{
label: "Timeline",
description: "인터뷰 시간 배분을 비율로 시각화",
preview: "```\n┌──────────────────────────────────┐\n│ ┌────────┬──────────┬────────┐ │\n│ │ 라포 │ 질문 │ 정리 │ │\n│ │ 3min │ 5min │ 2min │ │\n│ └────────┴──────────┴────────┘ │\n└──────────────────────────────────┘\n```"
},
{
label: "Comparison",
description: "좋은 인터뷰 vs 나쁜 인터뷰를 좌우 대비",
preview: "```\n┌──────────────────────────────────┐\n│ ┌────────────┐ ┌────────────┐ │\n│ │ 나쁜방법 │ VS │ 좋은방법 │ │\n│ │ ┌────────┐ │ │ ┌────────┐ │ │\n│ │ │평가요청│ │ │ │맥락확인│ │ │\n│ │ └────────┘ │ │ └────────┘ │ │\n│ └────────────┘ └────────────┘ │\n└──────────────────────────────────┘\n```"
}
]
}]
})
Important: preview Put actual keywords matching the context in the field. You must show a preview reflecting real content, not generic placeholders (Step 1, Card 1), so the user can judge. Full-width characters (Korean) are 2칸. After substitution, you must verify that the right side of the box on every line │is aligned in a straight line. A broken box is not a preview but noise.
Document Insertion
If the user provides a .md or .mdx file, save the PNG and then insert the image into that file.
Insertion rules
- Position determination: Insert immediately afterthe content the Figure describes. After the last paragraph of that section, before the next
##heading - MDX file (
.mdx):<Figure src="/blog/images/{slug}/{filename}.png" alt="설명" caption="캡션" /> - Markdown file (
.md): - 빈 줄: Ensure one blank line before and after the inserted tag
- Multiple Figures: When inserting multiple Figures into the same file, place each near its related section
Insertion position criteria
- Find the heading (##, ###) most related to the Figure content
- Insert after the last paragraph of that heading's body
- Never insert inside a code block (```)
- frontmatter(
---) do not insert inside - If a Figure/image with the same filename already exists, replace it (prevent duplication)
- Overwrite guard: If a file with the same name already exists at the save path, first check the existing file's resolution —
on macOS use
sips -g pixelWidth -g pixelHeight <path>, on other environments use ImageMagickidentify -format '%wx%h' <path>or Python PIL. If it is 2880×1620 (retina 2x) or 1440×810 (1x fallback capture), it is a normal blog-figure output. Any other size may be from another source (there have been cases where screenshots and OG cards were mixed in the same directory) — before overwriting, get user confirmation. If no resolution-checking tool is available at all, skip the guard but inform the user of that fact
HTML Template
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=1440">
<!-- blog-figure source
blog: {블로그 파일 경로}
scene: {Content Brief 핵심 메시지 1줄}
pattern: {패턴명}
-->
<link rel="stylesheet" href="file://{SKILL_DIR}/assets/figure.css">
</head>
<body>
<!-- figure content here -->
</body>
</html>
Replace {SKILL_DIR} with the absolute path to this skill's directory (the directory containing this SKILL.md). Resolve via the file path of this skill file, e.g. if SKILL.md is at /path/to/skills/blog-figure/SKILL.md, then {SKILL_DIR} = /path/to/skills/blog-figure.
Self-Check
Capture is expensive (browser launch + screenshot + retina processing). Running the HTML through a static linter once prevents the waste of "realizing only after capturing a figure that is unreadable on mobile." Among the design rules, check only those that can be checked mechanically and precisely.
python3 {SKILL_DIR}/scripts/validate_figure.py /tmp/blog-figure-{name}.html --pattern {PatternName}
- If even one ERROR remains, do not capture. Fix the HTML and run it again, and capture after it is clean. (If you discover an ERROR after already capturing, fix the HTML → recapture.)
- Check items:
small-font(ERROR) — Font smaller than 20px (1.25rem). It disappears when scaled down to 25%. Check all SVGfont-size, CSSpx/rem.hex-color(ERROR) — Hex hardcoded in the layout. Usefigure.css의var(--*). Hex inside SVG/Canvas/D3 graphics is normal, so do not flag it.gradient/blur-shadow(ERROR) — Neo-Brutalism uses only flat solid colors + blur 0 shadows.linear-gradient,box-shadow's 3rd value (blur) ≠ 0,filter:blur,createLinearGradient등.word-count/component-cap(WARNING) — Too many text/components. Not a blocker, but almost always better to reduce.
--pattern Terminalgives you a word limit increase from 20 to 35 (Terminal only exception). Other patterns are--patternoptional.- The linter does not render. It only catches rule violations — "Is the structure right, is it pretty, are the keywords noticeable at a glance" is the job of the 9-step visual inspection. The two are complementary, not substitutes.
Capture
At the capture stage, references/capture.mdread. Font load check (required), gallery review, per-tool procedures, and failure recovery are all there.
Tool priority: Chrome DevTools MCP → Chrome CLI (retina 2x, 2880×1620) → Playwright MCP/CLI (1x, last resort). retina 2x is the standard, and Playwright is the 1x fallback because it cannot set deviceScaleFactor.
Patterns
| Pattern | Use case | Key classes |
|---|---|---|
| Comparison | X vs Y, left-right contrast | .split, .vs-badge |
| Flow | Step-by-step process | .flow-card, .arrow-down, .icon |
| Timeline | Time allocation, proportions | .timeline, .tl-block |
| Concept | Relationship diagram, concept comparison | .concept-block |
| Architecture | System architecture diagram, layers | .arch, .arch-layer, .arch-node |
| Interaction | Sequence, request/response | .seq, .seq-msg |
| State | State transition, lifecycle | .state-chain, .state-node |
| Schema | DB model, entity relationships | .schema-table, .schema-field |
| Hierarchy | Tree, organizational chart | .tree, .tree-node, .tree-level |
| Matrix | 2x2 analysis, comparison table | .matrix, .matrix-cell |
| Journey | User journey, touchpoints | .journey, .journey-step |
| Funnel | Conversion rate, step-by-step drop-off | .funnel, .funnel-stage |
| Loop | Cyclical process, feedback | .loop-node, .loop-center, ring + corner arrowheads |
| Data Viz | Numeric comparison, bar chart | .bar-chart, .bar-row |
| Storyboard | Scenarios, step-by-step scenes | .storyboard, .story-panel |
| Terminal | CLI visualization, terminal UI | .terminal, .terminal-card, .terminal-option |
| Isometric | 3D blocks, layers, wireframe | SVG <polygon>, .iso |
| IconDiagram | Technical diagram, icon connections | SVG <rect>, <marker> |
| Network | Node network, abstract relationships | <canvas>, JS render |
| Graph | Force-directed graph | D3.js, <svg> |
| Waffle | Proportion perception, percentage | SVG <rect> 10x10 grid |
| Typographic Statement | Editorial quote, key definition | SVG <text>, quote card |
| Slope | Before/after ranking changes, ranking shifts | SVG <line>, <circle> |
| Treemap | Area-proportional composition | D3.js treemap |
| Radar | Multi-axis profile comparison | SVG + JS (cos/sin) |
| Dumbbell | Gap/range between two values | SVG <circle> x2 + <line> |
| Heatmap | 2D frequency/density distribution | <canvas>, JS lerp |
| Bullet | Actual vs target, KPI | SVG nested <rect> + <line> |
| Sparkline Grid | Trend summary for multiple items | SVG + JS, <polyline>, <polygon> |
| Waterfall | Increase/decrease decomposition, cumulative change | SVG floating <rect> + connector |
Full HTML examples by category — Read only the file containing the selected pattern:
| Category | File | Patterns |
|---|---|---|
| Layout | references/patterns-layout.md | Comparison, Flow, Timeline, Concept, Architecture, Interaction, State, Schema, Hierarchy, Matrix, Journey, Funnel, Loop, Storyboard, Terminal (15) |
| Data Viz (static) | references/patterns-dataviz-static.md | Data Viz, Waffle, Slope, Dumbbell, Bullet, Waterfall (6) |
| Data Viz (dynamic) | references/patterns-dataviz-dynamic.md | Treemap, Radar, Heatmap, Sparkline Grid (4) |
| Visual | references/patterns-visual.md | Isometric, IconDiagram, Network, Graph, Typographic Statement (5) |
Design Rules
Full design constraints are in references/design-rules.md. Read it before writing HTML.
Mobile readability — top priority principle
This PNG appears on mobile at 25% sizeof the original. Even at thumbnail size, the pattern structure and key keywordsmust be recognizable.
- Do not include text that cannot be read — it will not be visible anyway
- Color contrast and area, convey the structure
- Text should not be something you "read" but something you **"recognize"**
- Put only one core concept in one Figure (One Idea Per Figure)
Component count limit — a spacious layout
- Few large components > many small components — 3 is enough for comparison, many becomes a block of text
- Instead of increasing the quantity, size, spacing, label readabilitymake these larger
- The absolute limit per pattern (Flow 3 steps, Matrix 2×2, Waffle 2 categories, etc.) is
references/design-rules.md's table is the SSoT —
check that table before writing HTML.
validate_figure.pyalso checks the cap based on this table