# maging — AI Output Formatter
> **AI 답변을, 쓸 수 있는 결과물로.**
> 코딩 없이, 디자인 없이 — LLM이 31개 위젯·35개 브랜드 테마로 단일 HTML 대시보드를 바로 생성합니다.
---
## Setup (always include — one-liner)
```html
```
`maging-all.js` bundles in order: Pretendard (Korean font) + maging.css + Tailwind Play CDN + ECharts 5 + maging.js. Dispatches `'maging:ready'` on `window` when all deps are ready.
**You MUST mount ALL widgets inside `maging:ready`:**
```js
window.addEventListener('maging:ready', () => {
Maging.kpiCard('#el', { label: '매출', value: '₩128억', delta: 8.3 });
});
```
**DO NOT use `DOMContentLoaded`** — it fires before ECharts/maging.js finish loading and throws `"Maging is not defined"`. This is non-negotiable.
---
## Themes (35)
Set on ``. Each theme bundles palette, typography, radius, shadow. Changing the attribute instantly re-styles every widget.
**Light (18):** `claude` (warm cream+terracotta) · `linear` (indigo minimal) · `stripe` (payment purple) · `notion` (off-white) · `airbnb` (coral) · `linkedin` (corporate blue) · `instagram` (vivid) · `youtube` (red) · `reddit` (orange) · `medium` (editorial green serif) · `apple` (system blue) · `duolingo` (owl green) · `tiffany` (robin-egg blue+gold) · `mailchimp` (cavendish yellow) · `tmobile` (magenta) · `fedex` (purple+orange) · `hermes` (cream serif) · `barbie` (pastel pink)
**Dark (17):** `vercel` (pure black) · `github` (dimmed) · `x` (sharp mono) · `slack` (aubergine) · `discord` (blurple) · `openai` (AI teal) · `spotify` (neon green) · `twitch` (gaming purple) · `netflix` (cinematic red) · `figma` (multi) · `amazon` (navy) · `adobe` (creative red) · `bloomberg` (terminal amber) · `nasa` (worm blue+red) · `heineken` (bottle green) · `deere` (tractor green+yellow) · `ups` (pullman brown)
Pick by intent: minimal→`linear`/`vercel` · warm→`claude`/`notion` · corporate→`linkedin`/`stripe` · bold→`netflix`/`adobe` · luxury→`hermes`/`tiffany` · playful→`barbie`/`duolingo` · terminal→`bloomberg` · engineering→`nasa`. Default: `claude`.
---
## Layout Primitives
These are structural elements mounted on plain divs **outside** grid cells — not card widgets.
```js
Maging.pageHeader(selector, { kicker?, title, subtitle?, meta? })
```
Full-width H1 block: dot·kicker line → large title → subtitle + mono-meta row. Use once at the top of the page.
```js
Maging.sectionHead(selector, { index?, kicker?, title, tag? })
```
Flex row: `"index · kicker" + H2` on the left, tag badge on the right. Use as labeled dividers between grid sections.
**Pattern:**
```html
```
```js
Maging.pageHeader('#page-hero', { kicker: 'REPORT', title: '월간 현황', subtitle: '2025년 4월', meta: 'Last updated 09:00 KST' });
Maging.sectionHead('#section-01', { index: '01', kicker: 'TRENDS', title: '처리 추이', tag: 'Line · 6M' });
```
---
## Widgets (31)
Mount: `window.Maging.(selector, config)`. All auto-refresh on theme change.
### METRIC TILES
**`kpiCard`**
```js
Maging.kpiCard(sel, {
label, // string — metric name
value, // string — formatted value e.g. '₩128억'
delta?, // number — MoM % change
deltaGoodWhen?, // 'positive' (default) | 'negative'
sparkline?, // number[] — mini line chart
icon?, // string — emoji or symbol
compact?, // boolean — hides sparkline, smaller padding (for 110px rows)
})
```
**`metricChart`**
```js
Maging.metricChart(sel, {
label, value,
delta?, // number — % change
deltaGoodWhen?, // 'positive' (default) | 'negative'
icon?,
context?, // caption below delta
categories, // string[] — x-axis labels
series: [
{ name, data: number[] }, // [0] current period (accent line + area)
{ name, data: number[] }, // [1] previous period (dashed muted) — optional
],
target?, // number — horizontal dashed goal line
yFormatter?, // (v: number) => string
})
```
Hero number + labeled mini line chart. Shows current vs previous period with x/y axis labels. Use when the user needs to understand scale and compare periods.
**`heroTile`**
```js
Maging.heroTile(sel, {
kicker?, // string — small uppercase label e.g. '이번 달 · Q2 2026'
value, // string — the hero number e.g. '₩128억'
tagline?, // string — narrative sentence below the number
stats?, // [{ label, value }] — 2–4 micro-stats below a divider
})
```
Landing-page style hero. Typography-first — no chart. Use for the single most important number on a dashboard.
**`ringProgress`**
```js
Maging.ringProgress(sel, {
value, max, unit, label, context?,
thresholds: [[ratio, level], …] // e.g. [[0.5,'danger'],[0.85,'warning'],[1,'good']]
})
```
**`bulletChart`**
```js
Maging.bulletChart(sel, {
value, target?, benchmark?, max, min?,
ranges?: [{ value, label }],
valueFormatter?, unit?
})
```
**`compareCard`**
```js
Maging.compareCard(sel, {
title,
left: { label, value },
right: { label, value },
delta, deltaLabel?
})
```
**`metricStack`**
```js
Maging.metricStack(sel, {
title,
main: { label, value, delta? },
items: [{ label, value }]
})
```
**`countdownTile`**
```js
Maging.countdownTile(sel, { title, target, label?, context? })
// target = ISO datetime string e.g. '2025-12-31T00:00:00'
```
**`sparklineList`**
```js
Maging.sparklineList(sel, {
title,
items: [{ label, value, delta, sparkline: number[], deltaGoodWhen? }]
})
```
**`goalGrid`**
```js
Maging.goalGrid(sel, {
title,
items: [{ label, value, max, unit?, sublabel? }],
thresholds?
})
```
---
### CHARTS
**`lineChart`**
```js
Maging.lineChart(sel, {
title, subtitle?,
categories: string[],
series: [{ name, data: number[] }],
stack?, area?, yFormatter?, yMin?, yMax?
})
```
**`barChart`**
```js
Maging.barChart(sel, {
title, subtitle?,
items: [{ label, value }],
horizontal?, // boolean — use for long Korean labels
yFormatter?, showLabels?
})
```
**`donutChart`**
```js
Maging.donutChart(sel, {
title, subtitle?,
slices: [{ label, value, color? }],
centerLabel?, centerValue?
})
```
**`funnelChart`**
```js
Maging.funnelChart(sel, { title, stages: [{ label, value }], valueSuffix? })
```
**`gaugeChart`**
```js
Maging.gaugeChart(sel, {
title, label,
value, max, unit,
thresholds: [[ratio, level], …] // e.g. [[0.7,'danger'],[0.9,'warning'],[1,'good']]
})
```
**`radarChart`**
```js
Maging.radarChart(sel, {
title,
indicators: [{ name, max }],
series: [{ name, data: number[] }]
})
```
**`heatmapChart`**
```js
Maging.heatmapChart(sel, {
title,
xAxis: string[], yAxis: string[],
matrix: number[][], // matrix[row][col]
tooltipFormatter?
})
```
**`treemapChart`**
```js
Maging.treemapChart(sel, { title, items: [{ name, value }], valueFormatter? })
```
**`scatterChart`**
```js
Maging.scatterChart(sel, {
title,
points: [{ label, x, y, size }],
xLabel?, yLabel?
})
```
**`sankeyChart`**
```js
Maging.sankeyChart(sel, {
title,
nodes: [{ name }],
links: [{ source, target, value }],
valueFormatter?
})
```
**`waterfallChart`**
```js
Maging.waterfallChart(sel, {
title,
items: [{ label, value, type? }],
valueFormatter?
})
// type auto-detected: first item=start, last=total, positive=gain, negative=loss
```
**`mapChart`**
```js
Maging.mapChart(sel, { title, items: [{ region, value }], valueFormatter? })
// region = Korean province name: 서울 경기 부산 인천 대구 대전 광주 울산 세종
// 강원 충북 충남 전북 전남 경북 경남 제주
```
**`cohortMatrix`**
```js
Maging.cohortMatrix(sel, {
title,
cohorts: string[], // row labels e.g. ['Jan','Feb']
periods: string[], // col labels e.g. ['M0','M1','M2']
data: number[][], // data[i][j] = retention %, null = blank cell
sizes?: number[],
cohortLabel?, sizeLabel?, valueFormatter?
})
```
---
### LISTS & STATUS
**`leaderboard`**
```js
Maging.leaderboard(sel, {
title,
items: [{ name, initial, percent, meta }]
})
```
**`activityTable`**
```js
Maging.activityTable(sel, {
title, subtitle?,
columns: [{ key, label, align?, render? }],
rows: [...],
live?
})
// render signature: (value, row) => string
// SAFER: pre-format rows as plain strings and omit render entirely
```
**`timeline`**
```js
Maging.timeline(sel, {
title,
items: [{ time, text, type? }] // type: 'success'|'warning'|'danger'|'info'
})
```
**`inboxPreview`**
```js
Maging.inboxPreview(sel, {
title, subtitle?,
items: [{ icon, text, time, type }],
footer?
})
```
**`statusGrid`**
```js
Maging.statusGrid(sel, {
title, subtitle?,
columns?, // number of grid columns, default 3
items: [{ label, status, value? }] // status: 'ok'|'warning'|'danger'
})
```
---
### CALENDAR & PROJECT
**`calendarHeatmap`**
```js
Maging.calendarHeatmap(sel, {
title, year,
values: [[date, value], …], // date = 'YYYY-MM-DD'
max?, valueSuffix?, cellSize?
})
```
**`eventCalendar`**
```js
Maging.eventCalendar(sel, {
title, year, month,
events: [{ date, label, type? }],
startOfWeek?, showList?, listFilter?
})
```
**`progressStepper`**
```js
Maging.progressStepper(sel, {
title, kicker?, status?, meta,
steps: [{ label, date, status, badge? }]
// step status: 'done'|'active'|'pending'
})
```
---
### CONTROL & MESSAGING
**`alertBanner`**
```js
Maging.alertBanner(sel, {
type, // 'info'|'warning'|'danger'|'success'
title, message?,
icon?,
action?: { label, href? },
dismissable?
})
```
Horizontal stripe — NOT a card. Place before the grid.
---
## Canonical Layouts
Outer wrapper: ``. Stack sections with `mt-4`.
### 1 · KPI Row
```html
```
4× `kpiCard`. For 6 compact: `md:grid-cols-6 gap-3` + `grid-auto-rows:110px` + `compact:true`.
### 2 · Hero + Side (2:1)
```html
```
Left: `lineChart`/`barChart`/`funnelChart`. Right: `donutChart`/`leaderboard`/`statusGrid`/`metricStack`.
### 3 · Equal Split (1:1)
```html
```
### 4 · Metric Trio (1:1:1)
```html
```
3× `gaugeChart` or `compareCard`.
### 5 · Full-Width Detail
```html
```
`treemapChart` · `sankeyChart` · `calendarHeatmap` · `cohortMatrix` · `mapChart` (use 600px).
### Height tokens
`mini 110px` · `tile 192px` · `gauge 240px` · `card 400px` · `detail 520px` · `tall 600px`
---
## Generation Rules
0. **Default = static snapshot.** You are the analyst — pick the story, arrange widgets, no interactive state. No JS state objects, re-render functions, or event handlers (beyond widget internals) unless user explicitly asks for "interactive" / "filterable" / "실시간".
1. Include the one-liner setup.
2. Pick ONE theme via ``.
3. ``.
4. Compose layout from the 5 canonical patterns. Pick heights from the token list.
5. Mount ALL widgets inside `window.addEventListener('maging:ready', () => { … })`. Never use `DOMContentLoaded`.
6. Widget choice by data shape:
- Time series → `lineChart` · Categorical → `barChart` · Share → `donutChart`
- Funnel → `funnelChart` · Target vs actual → `bulletChart`/`ringProgress`
- Distribution → `treemapChart`/`heatmapChart` · Flow → `sankeyChart`
- Correlation → `scatterChart` · Korean geo → `mapChart`
- Retention → `cohortMatrix` · P&L → `waterfallChart` · Rankings → `leaderboard`
- Events → `timeline`/`activityTable` · Health → `statusGrid`+`gaugeChart`
- OKR → `goalGrid` · Multi-trend → `sparklineList` · Hero metric → `heroTile`
7. KRW: `v => '₩' + (v/1e8).toFixed(1) + '억'` or `Maging.fmt.krw(v)`.
8. Korean labels OK. Use `word-break: keep-all` for Korean text.
9. Section order: at-a-glance → real-time → trends → deep-dive → operations.
10. Output one fenced code block: ` ```html … ``` `. No text outside it.
---
## NEVER DO
- **No code comments** (`//`, `/* */`, ``). Every comment burns output tokens. Widget calls + named IDs are self-documenting.
- **No custom `