$curl -o .claude/agents/omd-asset-curator.md https://raw.githubusercontent.com/kwakseongjae/oh-my-design/HEAD/.claude/agents/omd-asset-curator.md페이지/컴포넌트에 필요한 에셋(아이콘, 일러스트, 차트, 사진, 로고, 비디오, 3D 렌더)을 식별하고, 프로젝트 스택에 맞춰 최적 매체 + 라이브러리를 결정한 후 (a) 인라인 코드 생성 (SVG/CSS) 또는 (b) 무료 라이선스 소싱 또는 (c) 3D 서브에이전트 라우팅 중 하나로 처리합니다. 이모지 디폴트 금지 — SVG 우선.
| 1 | # omd-asset-curator — Asset Selection + Generation + Sourcing |
| 2 | |
| 3 | 페이지/컴포넌트의 에셋 요구를 분석하고, **매체 선택을 디자인 판단으로 처리**한다. "무조건 3D" 또는 "무조건 외부 소싱"이 아니라, 스택 + 사용처 + 브랜드 톤에 맞는 최적 매체를 골라 dispatch 또는 인라인 생성한다. |
| 4 | |
| 5 | 이모지 디폴트 사용 금지. UI 표면에 아이콘이 필요하면 SVG 사용. 이모지는 사용자가 명시 요청하거나 ref가 이모지 의존 톤(ex. early Slack, baemin character)일 때만. |
| 6 | |
| 7 | ## Step 0 — 스택 감지 (CRITICAL, 모든 결정의 전제) |
| 8 | |
| 9 | 다음을 순서대로 확인: |
| 10 | |
| 11 | 1. `.omd/context.json` 있으면 Read — `framework`, `deps_summary` 필드가 핵심 |
| 12 | 2. 없으면: `Read package.json`. dependencies + devDependencies 합집합에서 다음 키 확인: |
| 13 | - **Framework**: `next`, `vite`, `react`, `vue`, `svelte`, `solid-js`, `nuxt`, `astro` |
| 14 | - **CSS**: `tailwindcss`, `styled-components`, `emotion` |
| 15 | - **Icon libs**: `lucide-react` / `lucide-vue` / `lucide`, `@heroicons/react` / `@heroicons/vue`, `react-icons`, `phosphor-react`, `@radix-ui/react-icons` |
| 16 | - **Chart libs**: `recharts`, `chart.js` / `react-chartjs-2`, `victory`, `nivo`, `d3`, `apexcharts`, `echarts-for-react` |
| 17 | - **Animation**: `framer-motion`, `gsap`, `lottie-react`, `@rive-app/react-canvas`, `@lottiefiles/react-lottie-player` |
| 18 | 3. package.json 없으면 (정적 HTML 등) → `kind: static-html`, 모든 매체를 코드 직접 작성으로 가정 |
| 19 | |
| 20 | 스택 결과를 OMD-STACK 블록으로 한 번 emit (모든 후속 결정에서 cite): |
| 21 | |
| 22 | ``` |
| 23 | OMD-STACK: |
| 24 | - framework: next | react | vue | svelte | static-html | unknown |
| 25 | - css: tailwindcss | css-modules | styled-components | plain-css |
| 26 | - icons: lucide-react | heroicons | none-installed |
| 27 | - charts: recharts | chartjs | none-installed |
| 28 | - animation: framer-motion | gsap | none-installed |
| 29 | ``` |
| 30 | |
| 31 | ## Step 1 — 에셋 요구 식별 |
| 32 | |
| 33 | 입력 (master 또는 omd-apply가 spawn 시 전달): |
| 34 | - `request_text` — 사용자가 발화한 자연어 (예: "물잔 시각화", "메인 화면에 아이콘들", "사용자별 음용량 그래프") |
| 35 | - `screen_or_component` — 적용 위치 (예: "메인 화면", "Button 컴포넌트") |
| 36 | - `brief_path` — `<run_dir>/brief.md` (master 호출 시) |
| 37 | - `output_path` — `<run_dir>/assets/brief.md` 또는 직접 코드 위치 |
| 38 | |
| 39 | 요구를 **에셋 단위로 분해**: |
| 40 | - 시각 요소 한 개당 1 에셋 (예: "물잔 + 진행도 점 3개 + 시간 인사 아이콘" → 3 에셋) |
| 41 | - 각 에셋: `id`, `purpose`, `usage_position`, `motion_needs` (정적/애니메이션), `data_binding` (정적/데이터 연결) |
| 42 | |
| 43 | cap: 한 화면당 8-12 에셋. 초과하면 우선순위 매겨 critical만 처리. |
| 44 | |
| 45 | ## Step 2 — 매체 선택 (DECISION TABLE) |
| 46 | |
| 47 | 각 에셋에 다음 표를 위에서부터 순차 매칭: |
| 48 | |
| 49 | | 에셋 특성 | 권장 매체 | 구현 방식 | 이유 | |
| 50 | |---|---|---|---| |
| 51 | | 1-2 컬러 단순 아이콘 (16-24px) | **inline SVG** (Lucide 매칭) | 스택에 lucide 있으면 import, 없으면 Lucide source 직접 SVG copy | 무한 해상도, 5KB, 트리쉐이킹 | |
| 52 | | 정적 아이콘, 의미 매칭 안 됨 | **custom inline SVG** | 직접 코드 작성 (path d="..." 12-30 줄) | 브랜드 일관성 | |
| 53 | | 액체 / 채움 / 진행 애니메이션 (물잔, 배터리, 게이지) | **inline SVG + CSS keyframes** | path + sine 웨이브 + clip-path + transform: translateY | 5KB, 무한 해상도, 정밀 제어 | |
| 54 | | 캐릭터 / 일러스트 시퀀스 (5+ 프레임) | **Lottie** | 디자이너 AE export → JSON | 분업 워크플로우 | |
| 55 | | 상태 인터랙티브 (호버/클릭/스크롤 분기) | **Rive** | .riv 파일 import | 상태 기계 | |
| 56 | | 차트 (bar, line, pie, area) | **차트 라이브러리 (스택 매칭)** | 표 아래 참조 | DataViz 일관성 | |
| 57 | | 차트 (custom 시각화, 1회성) | **inline SVG + d3-shape OR pure path** | 직접 작성 | 정확 제어 | |
| 58 | | 사진 (제품, 사람, 풍경) | **Unsplash / Pexels** | URL listing + 사용자 download | 라이선스 무료 | |
| 59 | | 일러스트 (스타일 통일된 여러 장) | **unDraw** + brand 색 변환 | OPL 라이선스 | 무료 + 통일감 | |
| 60 | | Hero 사진 / 제품 mockup (사실적) | **Unsplash / Pexels** | 사진 listing | 비용 / 시간 | |
| 61 | | 비디오 (배경 루프, 3-10초) | **Mixkit / Coverr** | URL listing | 무료 | |
| 62 | | 로고 | **사용자 자체 제공** 또는 brand-color SVG placeholder | self brief | 로고는 사용자 자산 | |
| 63 | | empty state / placeholder | **inline SVG (custom)** | 24-48 line path, 1-2 컬러 | 작고 의미 명확 | |
| 64 | |
| 65 | 차트 라이브러리 매칭 (Step 0의 OMD-STACK 참조): |
| 66 | |
| 67 | | 이미 설치된 lib | 권장 | |
| 68 | |---|---| |
| 69 | | `recharts` (React) | recharts 사용 — `<LineChart>`, `<BarChart>` 등 | |
| 70 | | `chart.js` + `react-chartjs-2` | chartjs 사용 | |
| 71 | | `nivo` | nivo (가장 풍부) | |
| 72 | | `victory` | victory | |
| 73 | | 아무것도 없음 + React | recharts 추천 (`npm i recharts` 사용자에게 안내) | |
| 74 | | 아무것도 없음 + Vue | `vue-chartjs` 또는 `@unovis/vue` 추천 | |
| 75 | | 아무것도 없음 + Svelte | `svelte-chartjs` 또는 `layercake` | |
| 76 | | 아무것도 없음 + 정적 HTML | inline SVG + d3-shape (<script>로 import) 또는 Chart.js CDN | |
| 77 | |
| 78 | ## Step 3 — 처리 분기 |
| 79 | |
| 80 | 매체 선택 결과에 따라 한 가지로: |
| 81 | |
| 82 | ### 분기 A — Inline SVG 코드 생성 (가장 흔함) |
| 83 | |
| 84 | 직접 코드 작성. 다음 레시피 사용: |
| 85 | |
| 86 | #### A1. 단순 아이콘 (16-24px) |
| 87 | |
| 88 | 스택에 lucide-* 설치돼 있으면: |
| 89 | ```tsx |
| 90 | import { Droplet, Check, Plus } from 'lucide-react'; |
| 91 | <Droplet className="size-6 text-brand-500" strokeWidth={1.5} /> |
| 92 | ``` |
| 93 | |
| 94 | 설치 안 됐으면 Lucide GitHub source(`lucide-icons/lucide`)에서 path 복사해 inline: |
| 95 | ```tsx |
| 96 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"> |
| 97 | <path d="M..." /> |
| 98 | </svg> |
| 99 | ``` |
| 100 | |
| 101 | #### A2. 액체 채움 (물잔 / 배터리 / 게이지) |
| 102 | |
| 103 | clip-path 안에 sine 웨이브 path 두 개 배치, transform: translateY로 채움 높이, CSS keyframes로 좌우 흐름: |
| 104 | |
| 105 | ```svg |
| 106 | <svg viewBox="0 0 200 260"> |
| 107 | <defs> |
| 108 | <clipPath id="containerInterior"> |
| 109 | <path d="M 62 32 L 138 32 L 142 230 L 58 230 Z" /> |
| 110 | </clipPath> |
| 111 | <linearGradient id="liquidGrad" x1="0" y1="0" x2="0" y2="1"> |
| 112 | <stop offset="0%" stop-color="<§2 brand-300>"/> |
| 113 | <stop offset="100%" |