Skill

blog-figure

Generate a static PNG figure for a blog post using Neo-Brutalism style (30 patterns: flow, comparison, architecture, data viz, charts, infographic, concept diagram, etc.). Invoke when: user wants any visual — figure, image, diagram, chart, graph, infographic, illustration, 시각화, 개념도, 도표, 그래프, 차트, 인포그래픽, 도식, 그림 — and context is a blog post or MDX file. Also invoke when an MDX file references a missing image in /blog/images/, or when user says "이 섹션에 이미지", "한 장으로 보여줘", "블로그 그림", "figure 만들어", "블로그 이미지", "다이어그램". NOT for interactive UI components, web pages, or app screens — those go to frontend-design.

From blog-figure
Install
1
Run in your terminal
$
npx claudepluginhub october-academy/agent-plugins --plugin blog-figure
Tool Access

This skill uses the workspace's default tool permissions.

Supporting Assets
View in Repository
assets/figure.css
evals/evals.json
references/design-rules.md
references/pattern-previews.md
references/patterns-dataviz-dynamic.md
references/patterns-dataviz-static.md
references/patterns-layout.md
references/patterns-visual.md
scripts/build_workspace_review_site.py
scripts/render_pattern_previews.py
Skill Content

Blog Figure Generator

Generate Neo-Brutalism styled figure images for blog posts: HTML → browser → PNG.

Workflow

  1. Understand context: Read blog MDX/MD or user description to decide what to visualize
  2. Content Brief: Extract the core concept to visualize and present to user for confirmation (see Content Brief below)
  3. Suggest patterns: Based on the confirmed brief, pick the 4 most fitting patterns from 30 available, and present them via AskUserQuestion with ASCII art previews (see Pattern Selection below)
  4. Create HTML: Before writing HTML, read references/design-rules.md for all design constraints. Write standalone HTML to /tmp/blog-figure-{name}.html linking assets/figure.css
  5. Capture PNG: Open in browser, screenshot at 1440×810, save PNG
  6. Save to project: Move PNG to apps/content/src/content/blog/images/{slug}/
  7. Insert into document: If user provided a .md or .mdx file, insert the image tag at the contextually correct location (see Document Insertion below)
  8. Verify: Read the saved PNG to visually confirm

Content Brief

패턴을 고르기 전에, 블로그에서 시각화할 핵심 개념을 추출하여 사용자에게 확인받는다. Figure의 내용이 블로그와 동떨어지거나 너무 추상적이 되는 것을 방지하는 핵심 단계.

추출 항목

항목설명예시
핵심 메시지이 Figure가 전달해야 할 한 문장"사용자가 말하는 것과 실제 행동은 다르다"
키워드Figure에 실제로 들어갈 단어 3~5개"좋은데요", "0건", "말 vs 행동"
구조개념 간 관계 유형대비(A vs B), 순서(A→B→C), 계층(A⊃B), 순환(A↻B)
강조점보는 사람이 가장 먼저 인식해야 할 것"0건이라는 숫자의 충격"

추출 방법

블로그 글에서 다음을 찾는다:

  1. 글의 핵심 주장/결론 — 제목, 서론 마지막 문장, 결론 첫 문장에서 발견됨
  2. 구체적 사례/데이터 — 추상적 개념보다 구체적 숫자, 인용, 사례가 Figure에 적합
  3. 대비/전환 구조 — "하지만", "반면", "이전에는 ~했지만 지금은", "X가 아니라 Y" 같은 전환점
  4. 독자의 Aha moment — 글을 읽다가 "오" 하고 멈칫할 지점. 그것이 Figure의 존재 이유

피해야 할 것

  • 글의 목차를 그대로 나열 (Figure는 목차가 아님)
  • 추상적 키워드만 나열 ("전략", "실행", "성과" → 어떤 글에나 끼워넣을 수 있으면 나쁜 Brief)
  • 글 전체를 요약하려는 시도 (Figure는 글의 한 장면을 포착할 뿐)

사용자 확인

AskUserQuestion으로 2~3가지 시각화 방향을 제시한다. 각 옵션은 "이 글에서 무엇을 figure로 만들지"에 대한 서로 다른 해석이다.

AskUserQuestion({
  questions: [{
    question: "어떤 장면을 Figure로 만들까요?",
    header: "Content Brief",
    multiSelect: true,
    options: [
      {
        label: "해석 A: {1줄 핵심 메시지}",
        description: "키워드: {단어1}, {단어2}, {단어3}",
        markdown: "**구조**: {관계 유형}\n**강조점**: {가장 눈에 띄어야 할 것}\n**근거**: 블로그에서 이 부분이 Figure로 적합한 이유 1줄"
      },
      {
        label: "해석 B: {1줄 핵심 메시지}",
        description: "키워드: {단어1}, {단어2}, {단어3}",
        markdown: "**구조**: {관계 유형}\n**강조점**: {가장 눈에 띄어야 할 것}\n**근거**: 블로그에서 이 부분이 Figure로 적합한 이유 1줄"
      },
      {
        label: "해석 C: {1줄 핵심 메시지}",
        description: "키워드: {단어1}, {단어2}, {단어3}",
        markdown: "**구조**: {관계 유형}\n**강조점**: {가장 눈에 띄어야 할 것}\n**근거**: 블로그에서 이 부분이 Figure로 적합한 이유 1줄"
      }
    ]
  }]
})

중요: 각 해석은 글의 서로 다른 부분/관점을 포착해야 한다. 같은 내용을 다른 말로 바꾼 3개가 아니라, 진짜로 다른 장면 3개를 제시하라.

Content Brief → Pattern Selection 연결

사용자가 Brief를 확인하면, 그 Brief의 구조가 패턴 선택을 자연스럽게 좁힌다:

Brief 구조적합한 패턴 (우선순위)
대비 (A vs B)Comparison, Flow (split), Matrix, Network
순서 (A→B→C)Flow, Journey, Timeline, Storyboard
계층 (A⊃B⊃C)Architecture, Hierarchy, Isometric, Schema
순환 (A↻B)Loop, State, Graph
수치 비교Data Viz, Funnel, Timeline, Waffle, Dumbbell, Bullet
비율/구성비Waffle, Treemap, Funnel
다차원 평가Radar, Matrix
순위 변화Slope, Data Viz
밀도/빈도Heatmap, Waffle
실적/목표Bullet, Data Viz
트렌드 요약Sparkline Grid, Data Viz
증감 분해Waterfall, Data Viz
인용/선언Typographic Statement
상호작용Interaction, Terminal, IconDiagram, Storyboard
추상/공간 구조Isometric, Network, Graph
시스템 연결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 markdown field with an ASCII art preview showing the pattern's layout structure.

How to pick the 4 patterns

Analyze the user's content and rank all 30 patterns by relevance:

  • Comparison: X vs Y, 좌우 대비, before/after
  • Flow: 프로세스, 단계별 차이, 방법론
  • Timeline: 시간 배분, 비율, 순서
  • Concept: 관계도, 벤 다이어그램, 개념
  • Architecture: 시스템 구성, 레이어, 컴포넌트
  • Interaction: 시퀀스, 요청/응답, API 플로우
  • State: 상태 전이, 라이프사이클
  • Schema: DB 모델, 엔티티 관계
  • Hierarchy: 트리, 조직도, 분류
  • Matrix: 2x2 분석, 비교표
  • Journey: 사용자 여정, 터치포인트
  • Funnel: 전환율, 단계별 감소
  • Loop: 순환 프로세스, 피드백
  • Data Viz: 수치 비교, 바 차트
  • Storyboard: 시나리오, 단계별 장면
  • Terminal: CLI 시각화, 터미널 UI, 도구 사용 장면
  • Isometric: 3D 블록, 와이어프레임, 레이어 구조 [SVG]
  • IconDiagram: 시스템 다이어그램, 아이콘 연결, 기술 구성도 [SVG]
  • Network: 노드 네트워크, 결정론↔확률 대비, 추상 관계 [Canvas]
  • Graph: 포스-다이렉티드 그래프, 노드-링크 자동 레이아웃 [D3]
  • Waffle: 비율 체감, 퍼센트 시각화, 100칸 중 N칸 [SVG]
  • Typographic Statement: 에디토리얼 인용, 핵심 정의, 선언적 메시지 [SVG]
  • Slope: 전후 순위 변화, 랭킹 이동, 두 시점 비교 [SVG]
  • Treemap: 면적 비례 구성비, 카테고리 분포 [D3]
  • Radar: 다축 프로파일 비교, 역량 평가, 다차원 지표 [SVG+JS]
  • Dumbbell: 두 값 사이 갭/범위, 격차 비교 [SVG]
  • Heatmap: 2D 빈도/밀도, 시간별·카테고리별 분포 [Canvas]
  • Bullet: 실적 vs 목표, KPI 달성률 [SVG]
  • Sparkline Grid: 다수 항목 트렌드 요약, 소형 라인차트 그리드 [SVG+JS]
  • Waterfall: 증감 분해, 누적 변화, 요인별 기여도 [SVG]

Top 4를 선택하여 아래처럼 AskUserQuestion 호출:

AskUserQuestion({
  questions: [{
    question: "어떤 Figure 패턴이 가장 적합할까요?",
    header: "패턴 선택",
    multiSelect: false,
    options: [
      {
        label: "{Pattern 1 이름}",
        description: "{왜 이 패턴이 적합한지 1줄 설명}",
        markdown: "{ASCII art preview}"
      },
      // ... 3개 더
    ]
  }]
})

ASCII Art Previews

4개 패턴을 결정한 뒤 AskUserQuestion 호출 전에 references/pattern-previews.md를 읽고 해당 패턴의 ASCII art를 가져와라.

Example: AskUserQuestion Call

User가 "사용자 인터뷰 프로세스를 시각화해줘"라고 요청한 경우:

AskUserQuestion({
  questions: [{
    question: "어떤 Figure 패턴이 가장 적합할까요?",
    header: "패턴 선택",
    multiSelect: false,
    options: [
      {
        label: "Flow (추천)",
        description: "인터뷰 단계를 수직 플로우로 표현. 프로세스 시각화에 최적",
        markdown: "┌──────────────────────────────────┐\n│        ┌──────────────┐          │\n│        │  맥락 확인    │          │\n│        └──────┬───────┘          │\n│               ▼                  │\n│        ┌──────────────┐          │\n│        │  사례 복기    │          │\n│        └──────┬───────┘          │\n│               ▼                  │\n│        ┌──────────────┐          │\n│        │  니즈 발견    │          │\n│        └──────────────┘          │\n└──────────────────────────────────┘"
      },
      {
        label: "Journey",
        description: "인터뷰이의 여정을 수평 터치포인트로 표현",
        markdown: "┌──────────────────────────────────┐\n│  ①─────────②─────────③────────④  │\n│  준비     라포     질문     정리  │\n└──────────────────────────────────┘"
      },
      {
        label: "Timeline",
        description: "인터뷰 시간 배분을 비율로 시각화",
        markdown: "┌──────────────────────────────────┐\n│ ┌────────┬──────────┬────────┐   │\n│ │  라포   │   질문    │  정리  │   │\n│ │  3min  │   5min   │  2min  │   │\n│ └────────┴──────────┴────────┘   │\n└──────────────────────────────────┘"
      },
      {
        label: "Comparison",
        description: "좋은 인터뷰 vs 나쁜 인터뷰를 좌우 대비",
        markdown: "┌──────────────────────────────────┐\n│  ┌──────────┐  ┌──────────┐     │\n│  │ 나쁜방법  │VS│ 좋은방법  │     │\n│  │ ┌──────┐ │  │ ┌──────┐ │     │\n│  │ │평가요청│ │  │ │맥락확인│ │     │\n│  │ └──────┘ │  │ └──────┘ │     │\n│  └──────────┘  └──────────┘     │\n└──────────────────────────────────┘"
      }
    ]
  }]
})

중요: markdown 필드에는 해당 컨텍스트에 맞는 실제 키워드를 넣어라. 제네릭 플레이스홀더(Step 1, Card 1)가 아닌 실제 내용을 반영한 프리뷰를 보여줘야 사용자가 판단할 수 있다.

Document Insertion

사용자가 .md 또는 .mdx 파일을 제공한 경우, PNG 저장 후 해당 파일에 이미지를 삽입한다.

삽입 규칙

  1. 위치 결정: Figure가 설명하는 컨텐츠의 직후에 삽입. 해당 섹션의 마지막 문단 뒤, 다음 ## 헤딩 전
  2. MDX 파일 (.mdx):
    <Figure src="/blog/images/{slug}/{filename}.png" alt="설명" caption="캡션" />
    
  3. Markdown 파일 (.md):
    ![설명](/blog/images/{slug}/{filename}.png)
    
  4. 빈 줄: 삽입된 태그 앞뒤로 빈 줄 1개씩 확보
  5. 복수 Figure: 같은 파일에 여러 Figure를 삽입할 경우, 각각 관련 섹션 근처에 배치

삽입 위치 판단 기준

  • Figure 내용과 가장 관련 높은 헤딩(##, ###) 을 찾는다
  • 해당 헤딩의 본문 마지막 문단 뒤에 삽입
  • 코드 블록(```) 내부에는 절대 삽입하지 않는다
  • frontmatter(---) 내부에는 삽입하지 않는다
  • 이미 동일 파일명의 Figure/image가 있으면 교체(중복 방지)

HTML Template

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=1440">
  <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.

Capture (pick whichever is available)

30개 패턴 전체 검수용 gallery:

python3 {SKILL_DIR}/scripts/render_pattern_previews.py --clean --output-dir /tmp/blog-figure-previews

검수용 gallery는 file:///tmp/blog-figure-previews/index.html 이다. Playwright MCP/CLI는 file://를 차단할 수 있으니 필요하면 python3 -m http.server 8123 --directory /private/tmphttp://127.0.0.1:8123/blog-figure-previews/index.html로 연다. 상세 검수는 ?density=detail 쿼리를 붙여 2열 확대 모드로 본다. 상단 ready counter가 30 / 30 ready가 된 뒤 캡처하라.

Chrome DevTools MCP (preferred when Chrome is open):

  1. mcp__chrome-devtools__emulate → viewport {width:1440, height:810, deviceScaleFactor:2} (retina 2880×1620)
  2. mcp__chrome-devtools__navigate_pagefile:///tmp/blog-figure-{name}.html
  3. mcp__chrome-devtools__take_screenshotfilePath: {target}.png
  4. After capture: mcp__chrome-devtools__emulate → viewport null (reset)

Playwright MCP:

  1. mcp__playwright__browser_resize → 1440×810
  2. mcp__playwright__browser_navigatefile:///tmp/blog-figure-{name}.html
  3. mcp__playwright__browser_take_screenshotfilename: {target}.png

Playwright CLI:

npx playwright screenshot --viewport-size="1440,810" file:///tmp/blog-figure-{name}.html {target}.png
npx playwright screenshot --viewport-size="1600,4200" --wait-for-selector="body[data-gallery-ready='1']" --wait-for-timeout=3000 "http://127.0.0.1:8123/blog-figure-previews/index.html?density=detail" /tmp/blog-figure-previews/gallery-playwright.png

Chrome CLI:

google-chrome --headless --disable-gpu --hide-scrollbars --virtual-time-budget=5000 --window-size=1600,4200 --screenshot=/tmp/blog-figure-previews/gallery-chrome.png "file:///tmp/blog-figure-previews/index.html?density=detail"

캡처 실패 시 복구:

  1. Chrome DevTools 연결 실패 → Playwright MCP 시도 → Fallback CLI 시도
  2. 빈 PNG / 흰 화면 → HTML 파일을 Read로 확인 후 file:// 경로가 올바른지 점검. {SKILL_DIR} 경로가 실제 figure.css 위치와 일치하는지 확인
  3. 폰트 깨짐 → Canvas/D3 패턴에서 document.fonts.ready.then() 래핑 누락 여부 확인
  4. 모든 방법 실패 → HTML 파일 경로를 사용자에게 알려주고 수동 캡처 요청

Patterns

PatternUse caseKey classes
ComparisonX vs Y, 좌우 대비.split, .vs-badge
Flow단계별 프로세스.flow-card, .arrow-down, .icon
Timeline시간 배분, 비율.timeline, .tl-block
Concept관계도, 개념 비교.concept-block
Architecture시스템 구성도, 레이어.arch, .arch-layer, .arch-node
Interaction시퀀스, 요청/응답.seq, .seq-msg
State상태 전이, 라이프사이클.state-chain, .state-node
SchemaDB 모델, 엔티티 관계.schema-table, .schema-field
Hierarchy트리, 조직도.tree, .tree-node, .tree-level
Matrix2x2 분석, 비교표.matrix, .matrix-cell
Journey사용자 여정, 터치포인트.journey, .journey-step
Funnel전환율, 단계별 감소.funnel, .funnel-stage
Loop순환 프로세스, 피드백.loop, .loop-node
Data Viz수치 비교, 바 차트.bar-chart, .bar-row
Storyboard시나리오, 단계별 장면.storyboard, .story-panel
TerminalCLI 시각화, 터미널 UI.terminal, .terminal-card, .terminal-option
Isometric3D 블록, 레이어, 와이어프레임SVG <polygon>, .iso
IconDiagram기술 다이어그램, 아이콘 연결SVG <rect>, <marker>
Network노드 네트워크, 추상 관계<canvas>, JS render
Graph포스-다이렉티드 그래프D3.js, <svg>
Waffle비율 체감, 퍼센트SVG <rect> 10x10 grid
Typographic Statement에디토리얼 인용, 핵심 정의SVG <text>, quote card
Slope전후 순위 변화, 랭킹 이동SVG <line>, <circle>
Treemap면적 비례 구성비D3.js treemap
Radar다축 프로파일 비교SVG + JS (cos/sin)
Dumbbell두 값 사이 갭/범위SVG <circle> x2 + <line>
Heatmap2D 빈도/밀도 분포<canvas>, JS lerp
Bullet실적 vs 목표, KPISVG nested <rect> + <line>
Sparkline Grid다수 항목 트렌드 요약SVG + JS, <polyline>, <polygon>
Waterfall증감 분해, 누적 변화SVG floating <rect> + connector

Full HTML examples by category — 선택한 패턴이 속한 파일만 읽어라:

CategoryFilePatterns
Layoutreferences/patterns-layout.mdComparison, Flow, Timeline, Concept, Architecture, Interaction, State, Schema, Hierarchy, Matrix, Journey, Funnel, Loop, Storyboard, Terminal (15)
Data Viz (static)references/patterns-dataviz-static.mdData Viz, Waffle, Slope, Dumbbell, Bullet, Waterfall (6)
Data Viz (dynamic)references/patterns-dataviz-dynamic.mdTreemap, Radar, Heatmap, Sparkline Grid (4)
Visualreferences/patterns-visual.mdIsometric, IconDiagram, Network, Graph, Typographic Statement (5)

Design Rules

Full design constraints are in references/design-rules.md. Read it before writing HTML.

모바일 가독성 — 최우선 원칙

이 PNG는 모바일에서 원본의 25% 크기로 보인다. 손톱만한 크기에서도 패턴 구조와 핵심 키워드가 인식되어야 한다.

  • 읽을 수 없는 텍스트는 넣지 마라 — 어차피 안 보인다
  • 색상 대비와 면적으로 구조를 전달하라
  • 텍스트는 "읽는 것"이 아니라 **"인식하는 것"**이어야 한다
  • 한 Figure에 한 개의 핵심 개념만 담아라 (One Idea Per Figure)

컴포넌트 수량 제한 — 시원시원한 배치

적은 수의 큰 컴포넌트 > 많은 수의 작은 컴포넌트

패턴최대 수량이유
Flow3단계3개면 비교 충분, 5개면 텍스트 덩어리
Timeline3블록블록 크기↑, 라벨 가독성↑
Storyboard4패널 (2×2)패널 크기 2배 확보
Bar chart4행바 높이 충분히 확보
Architecture3레이어, 레이어당 3노드 (2노드면 너무 빈약)시스템 구조의 풍부함
Split 비교양쪽 각 2~3카드카드 크기 유지
Journey4단계dot 간 여백 확보
Schema3테이블, 테이블당 3~4필드글자 크기 유지
Terminal3카드, 카드당 옵션 2개 (max 3)질문 max 12자 1줄, <br> 금지
Matrix2×2. 코너 4자 이내 또는 빈칸축 라벨이 의미 전달
Waffle1 grid (10x10), 2 카테고리총 ~8단어 (범례 포함)
Typographic1 primary text (max 8단어)attribution max 4단어
Slopemax 5항목, 2시점항목명 1단어
Treemapmax 6~8 cells라벨 1단어, 80px 미만 숨김
Radarmax 5축, 1~2 series축 라벨 1단어
Dumbbellmax 5 rows라벨 max 3단어
Heatmapmax 7x5 grid (35 cells)셀 ~110x90px
Bulletmax 3~4 charts범위 3단계 + 타겟 마커
Sparkline Gridmax 6 sparklines (3x2)항목당 라벨 1단어 + 값 1개
Waterfallmax 6~8 bars라벨 1~2단어, 시작/합계 포함
Similar Skills
cache-components

Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.

138.5k
Stats
Parent Repo Stars0
Parent Repo Forks1
Last CommitMar 11, 2026