# maging — AI Output Formatter

> **AI 답변을, 쓸 수 있는 결과물로.**
> 코딩 없이, 디자인 없이 — LLM이 31개 위젯·35개 브랜드 테마로 단일 HTML 대시보드를 바로 생성합니다.

---

## Setup (always include — one-liner)

```html
<script src="https://cdn.jsdelivr.net/npm/@m1kapp/maging@0.1.13/dist/maging-all.js"></script>
<body class="mw-themed">
```

`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 `<html data-theme="NAME">`. 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
<div id="page-hero" class="pt-12 pb-4"></div>

<div class="mt-8 pt-6" style="border-top:1px solid var(--mw-border)">
  <div id="section-01"></div>
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4" style="grid-auto-rows:400px">
    <div id="chart-a"></div><div id="chart-b"></div>
  </div>
</div>
```
```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.<name>(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: `<main class="max-w-[1280px] mx-auto p-6">`. Stack sections with `mt-4`.

### 1 · KPI Row
```html
<div class="grid grid-cols-2 md:grid-cols-4 gap-4" style="grid-auto-rows:192px">
  <div id="kpi-1"></div><div id="kpi-2"></div><div id="kpi-3"></div><div id="kpi-4"></div>
</div>
```
4× `kpiCard`. For 6 compact: `md:grid-cols-6 gap-3` + `grid-auto-rows:110px` + `compact:true`.

### 2 · Hero + Side (2:1)
```html
<div class="grid grid-cols-1 lg:grid-cols-[2fr_1fr] gap-4" style="grid-auto-rows:400px">
  <div id="lead"></div><div id="side"></div>
</div>
```
Left: `lineChart`/`barChart`/`funnelChart`. Right: `donutChart`/`leaderboard`/`statusGrid`/`metricStack`.

### 3 · Equal Split (1:1)
```html
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4" style="grid-auto-rows:400px">
  <div id="left"></div><div id="right"></div>
</div>
```

### 4 · Metric Trio (1:1:1)
```html
<div class="grid grid-cols-1 md:grid-cols-3 gap-4" style="grid-auto-rows:240px">
  <div id="a"></div><div id="b"></div><div id="c"></div>
</div>
```
3× `gaugeChart` or `compareCard`.

### 5 · Full-Width Detail
```html
<div class="grid grid-cols-1 gap-4" style="grid-auto-rows:520px">
  <div id="detail"></div>
</div>
```
`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 `<html data-theme="…">`.
3. `<body class="mw-themed">`.
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 `<style>` with `:root { --bg, --card, --text … }`**. maging.css already defines all `--mw-*` tokens per theme. Redefining them breaks theme switching.
- **No invented class names** like `.shell`, `.card`, `.dashboard`. Grid layout + Maging API only.
- **No background/color on body/html** beyond `<body class="mw-themed">`. The theme controls it.
- If you need a color token, use existing: `var(--mw-accent)`, `var(--mw-text-muted)`, `var(--mw-surface-2)`, `var(--mw-border)`.
- `<html data-theme="…">` is the single source of truth for styling. Trust it completely.
- `pageHeader` / `sectionHead` are structural — mount on plain divs **outside** grid cells, not inside widget slots.

---

## Utility

```js
window.Maging  // main API object
window.mw      // short alias (mw.kpiCard(…))

Maging.fmt.krw(value)   // ₩X.X억 formatter
Maging.setTheme(name)   // programmatically switch theme
```

---

## License

MIT
