---
name: pickit-nudge-design
description: PICKIT 넛지 위젯 비주얼 디자인 전용 — 위젯 HTML/CSS 구조, 애니메이션, 쇼핑몰별 테마 토큰을 정의합니다. 디자이너가 직접 넛지 컴포넌트를 설계하고 pickit.js에 반영할 수 있는 출력을 만듭니다.
license: MIT
metadata:
  author: pickit
  version: "2.0"
  argument-hint: <shop_id or "default">
---

# PICKIT Nudge Design

넛지 위젯의 **시각적 디자인**을 정의한다. 카피(텍스트) 생성은 `/pickit:nudge-gen`, 구현은 `/pickit:build` 사용.

---

## 위젯 비주얼 언어

전체 레이아웃은 **가로형 카드** 하나로 통일한다. 넛지 타입에 따라 좌측 요소만 달라지고, 카드의 느낌(흰 배경·연한 테두리·둥근 모서리·우측 CTA·우상단 데코 원)은 항상 동일하게 유지한다.

```
┌──────────────────────────────────────────────────────────────┐●
│  [좌측]  본문 텍스트 — [키워드] bold 처리      [ CTA 버튼 ] │
└──────────────────────────────────────────────────────────────┘
```

**좌측 요소 — 넛지 타입별 자동 결정:**

| nudge_type | 좌측 요소 | 예시 |
|---|---|---|
| `coupon` | 숫자 + ↓ 아이콘 | `5%↓` |
| `scarcity_stock` | 숫자 + ↑ 아이콘 | `3개↑` |
| `social_proof` | 숫자 단독 | `127명` |
| `review_summary` | 이모지 아이콘 | `⭐` |
| `size_guide` | 이모지 아이콘 | `📏` |
| `shipping` | 이모지 아이콘 | `🚚` |
| `quality` | 이모지 아이콘 | `✅` |
| `personalized` | 이모지 아이콘 | `👤` |

좌측 요소가 **숫자**면 font-size 36px bold로 강조, **이모지**면 28px로 표시.  
본문·CTA·데코 원은 타입에 관계없이 동일 스타일 적용.

- **중앙 텍스트**: 핵심 키워드 `<strong>` bold
- **우측 CTA**: 테두리형 버튼, `>>` 접미사
- **우상단 데코**: 보라색 원형 장식 (position: absolute, 모서리에 걸침)
- 전체: 흰 배경, 연한 회색 테두리, 둥근 모서리

**DOM 제약 (pickit.js 규칙):**
- `innerHTML` 금지 → `createElement` + `textContent` 사용
- 외부 CSS 파일 로드 금지 → `<style>` 태그 1개 head에 주입 (최초 1회)
- `eval()` 금지

---

## 실행 절차

### 1. 디자인 요구사항 수집

인수로 `shop_id`가 전달되면 해당 쇼핑몰 브랜드 컬러 기준으로 accent 색상 조정.  
없으면 기본 테마(보라색 계열)로 진행. 다음을 확인 (없으면 기본값 사용):

- 쇼핑몰 브랜드 컬러 → accent 색상 (기본: `#7B68EE`)
- 애니메이션: slide-up(기본) / fade / none
- 지표 타입: 할인율(%) / 재고(개) / 리뷰 수(명) / 없음

### 2. 디자인 토큰 정의

```js
const PICKIT_THEME = {
  // 색상
  accent:        '#7B68EE',   // 데코 원, 아이콘, 키워드 강조
  surface:       '#FFFFFF',   // 위젯 배경
  border:        '#E0E0E0',   // 카드 테두리
  textPrimary:   '#1A1A1A',   // 본문, 지표 숫자
  textSecondary: '#555555',   // 부제
  ctaBorder:     '#C8C8C8',   // CTA 버튼 테두리
  ctaBg:         '#F5F5F5',   // CTA 버튼 배경
  ctaText:       '#1A1A1A',   // CTA 버튼 텍스트

  // 레이아웃
  borderRadius:    '14px',
  ctaRadius:       '8px',
  padding:         '14px 16px',
  shadow:          '0 2px 12px rgba(0,0,0,0.08)',
  metricFontSize:  '36px',    // 지표 숫자 크기
  metricFontWeight:'900',

  // 애니메이션
  animation:    'slide-up',
  animDuration: '260ms',
};
```

### 3. CSS 생성

`<style id="pickit-style">` 로 head에 1회 주입할 전체 CSS:

```css
@keyframes pickit-slide-up {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

.pickit-widget {
  display: flex;
  align-items: center;
  gap: 12px;
  position: relative;
  overflow: hidden;
  background: #FFFFFF;
  border: 1px solid #E0E0E0;
  border-radius: 14px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
  padding: 14px 16px;
  margin-bottom: 12px;
  font-family: inherit;
  box-sizing: border-box;
  animation: pickit-slide-up 260ms ease;
}

/* 우상단 데코 원 */
.pickit-widget::after {
  content: '';
  position: absolute;
  top: -14px;
  right: -14px;
  width: 48px;
  height: 48px;
  background: #7B68EE;
  border-radius: 50%;
}

/* 좌측 지표 블록 */
.pickit-metric {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  min-width: 64px;
}
.pickit-metric-value {
  font-size: 36px;
  font-weight: 900;
  color: #1A1A1A;
  line-height: 1;
  letter-spacing: -1px;
}
/* 방향 아이콘 (▼ 형태, SVG inline) */
.pickit-metric-icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: #9B8DC4;
  align-self: flex-end;
  margin-bottom: 4px;
}

/* 중앙 텍스트 */
.pickit-body {
  flex: 1;
  font-size: 14px;
  font-weight: 400;
  color: #1A1A1A;
  line-height: 1.4;
  word-break: keep-all;
}
.pickit-body strong {
  font-weight: 700;
}

/* 우측 CTA 버튼 */
.pickit-cta {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  background: #F5F5F5;
  border: 1px solid #C8C8C8;
  border-radius: 8px;
  padding: 8px 12px;
  font-size: 13px;
  font-weight: 400;
  color: #1A1A1A;
  cursor: pointer;
  text-decoration: none;
  z-index: 1;
}
.pickit-cta:hover {
  background: #EBEBEB;
}

/* 모바일: CTA 버튼 아래로 */
@media (max-width: 480px) {
  .pickit-widget {
    flex-wrap: wrap;
  }
  .pickit-cta {
    width: 100%;
    justify-content: center;
  }
}
```

### 4. `renderNudge()` 함수 생성

**반드시 `createElement` + `textContent` 패턴만 사용.**

nudge 객체 구조:
```js
{
  rule_id,       // "R007"
  shop_id,       // "teonteon20"
  nudge_type,    // "coupon" | "scarcity_stock" | "social_proof" | ...
  metric,        // 숫자 문자열 or null — LLM/폴백이 내려준 값
  metric_unit,   // "%" | "개" | "명" | null
  icon_direction,// "down" | "up" | null
  emoji,         // "⭐" | "🚚" | null — metric 없을 때 사용
  body,          // 본문 전체 텍스트
  body_emphasis, // body 안에서 bold 처리할 키워드 (없으면 null)
  cta,           // CTA 버튼 텍스트
  cta_target,    // "#actionCart" 등
}
```

```js
function renderNudge(nudge, theme) {
  if (!document.getElementById('pickit-style')) {
    const style = document.createElement('style');
    style.id = 'pickit-style';
    style.textContent = `/* 위 3단계 CSS 전체 */`;
    document.head.appendChild(style);
  }

  const widget = document.createElement('div');
  widget.className = 'pickit-widget';

  // 좌측 요소: 숫자(metric)가 있으면 숫자+아이콘, 없으면 이모지
  const left = document.createElement('div');
  left.className = 'pickit-left';

  if (nudge.metric) {
    // 숫자 강조 블록
    left.classList.add('pickit-left--metric');

    const val = document.createElement('span');
    val.className = 'pickit-metric-value';
    val.textContent = nudge.metric + (nudge.metric_unit || '');
    left.appendChild(val);

    if (nudge.icon_direction) {
      const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      icon.setAttribute('viewBox', '0 0 24 24');
      icon.setAttribute('class', 'pickit-metric-icon');
      icon.setAttribute('aria-hidden', 'true');
      const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
      path.setAttribute('fill', 'currentColor');
      path.setAttribute('d', nudge.icon_direction === 'down' ? 'M7 10l5 5 5-5H7z' : 'M7 14l5-5 5 5H7z');
      icon.appendChild(path);
      left.appendChild(icon);
    }
  } else if (nudge.emoji) {
    // 이모지 블록
    left.classList.add('pickit-left--emoji');
    left.textContent = nudge.emoji;
  }

  if (nudge.metric || nudge.emoji) widget.appendChild(left);

  // 중앙 본문 (키워드 bold)
  const body = document.createElement('p');
  body.className = 'pickit-body';
  if (nudge.body_emphasis && nudge.body.includes(nudge.body_emphasis)) {
    const parts = nudge.body.split(nudge.body_emphasis);
    if (parts[0]) body.appendChild(document.createTextNode(parts[0]));
    const strong = document.createElement('strong');
    strong.textContent = nudge.body_emphasis;
    body.appendChild(strong);
    if (parts[1]) body.appendChild(document.createTextNode(parts[1]));
  } else {
    body.textContent = nudge.body;
  }
  widget.appendChild(body);

  // CTA 버튼
  const cta = document.createElement('button');
  cta.className = 'pickit-cta';
  cta.setAttribute('type', 'button');
  cta.setAttribute('aria-label', nudge.cta);
  cta.textContent = nudge.cta + ' >>';
  cta.addEventListener('click', function () {
    const target = document.querySelector(nudge.cta_target);
    if (target) target.scrollIntoView({ behavior: 'smooth', block: 'center' });
    navigator.sendBeacon('/nudge/' + nudge.rule_id + '/feedback',
      JSON.stringify({ shop_id: nudge.shop_id, rule_id: nudge.rule_id, nudge_type: nudge.nudge_type, clicked: true })
    );
  });
  widget.appendChild(cta);

  return widget;
}
```

CSS에 `.pickit-left` 변형 추가:

```css
.pickit-left {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.pickit-left--metric {
  min-width: 64px;
}
.pickit-left--emoji {
  font-size: 28px;
  line-height: 1;
}
```

### 5. 백엔드 응답 → renderNudge 변환

LLM/폴백 응답(`POST /nudge`)을 `renderNudge()` 입력으로 변환하는 함수.  
**넛지 타입에 따라 metric 또는 emoji 중 하나만 채운다.**

```js
const EMOJI_MAP = {
  review_summary: '⭐',
  social_proof:   '👥',
  size_guide:     '📏',
  shipping:       '🚚',
  quality:        '✅',
  personalized:   '👤',
};

function toRenderNudge(apiResp, shopId, productCtx) {
  // productCtx: { discount_rate, stock_number, review_count } — pickit.js가 보유
  let metric = null, metric_unit = null, icon_direction = null, emoji = null;

  if (apiResp.nudge_type === 'coupon' && productCtx.discount_rate) {
    metric = String(productCtx.discount_rate);
    metric_unit = '%';
    icon_direction = 'down';
  } else if (apiResp.nudge_type === 'scarcity_stock' && productCtx.stock_number) {
    metric = String(productCtx.stock_number);
    metric_unit = '개';
    icon_direction = 'up';
  } else if (apiResp.nudge_type === 'social_proof' && productCtx.review_count) {
    metric = String(productCtx.review_count);
    metric_unit = '명';
    icon_direction = null;
  } else {
    emoji = EMOJI_MAP[apiResp.nudge_type] || null;
  }

  return {
    rule_id:        apiResp.rule_id,
    shop_id:        shopId,
    nudge_type:     apiResp.nudge_type,
    metric,
    metric_unit,
    icon_direction,
    emoji,
    body:           apiResp.body,
    body_emphasis:  apiResp.emphasis || null,
    cta:            apiResp.cta,
    cta_target:     apiResp.cta_target,
  };
}
```
```

### 6. 출력물 생성

다음 네 가지를 모두 작성해서 바로 사용 가능한 형태로 출력:

**① PICKIT_THEME** (shop_id 기반 토큰 조정 반영)
```js
const PICKIT_THEME = { ... }
```

**② `<style>` 주입용 전체 CSS** (실제 값으로 토큰 치환 완료)
```css
/* 완성된 CSS */
```

**③ `renderNudge(nudge, theme)` 함수** (복붙 가능한 완성 코드)
```js
function renderNudge(nudge, theme) { ... }
```

**④ `toRenderNudge(apiResp, shopId)` 변환 함수** (복붙 가능한 완성 코드)
```js
function toRenderNudge(apiResp, shopId) { ... }
```

마지막에:
> "이 코드를 pickit.js의 넛지 삽입 섹션에 붙여넣은 뒤 `/pickit:build frontend`로 통합하세요."

---

## 쇼핑몰별 accent 조정 예시

| 브랜드 톤 | accent | 데코 원 | 아이콘 |
|---|---|---|---|
| 기본 (보라) | `#7B68EE` | `#7B68EE` | `#9B8DC4` |
| 프리미엄 (딥 네이비) | `#1E3A5F` | `#1E3A5F` | `#4A6FA5` |
| 트렌디 (코랄) | `#FF6B6B` | `#FF6B6B` | `#FF9999` |
| 미니멀 (차콜) | `#333333` | `#333333` | `#888888` |
