# UI Design System — 당근 SEED

> 어떤 스택에서든 동일한 톤을 이식하기 위한 단일 소스. 당근 SEED Design(github.com/daangn/seed-design, seed-design.io)의 공개 구조를 웹 이식용으로 재해석한 문서다.
>
> **한 줄 정의**: **하나의 토큰 소스, 모든 플랫폼.** 캐럿 오렌지(`#ff6f0f`)를 유일한 브랜드 포인트로, 담백한 그레이 스케일과 6px 라운드 위에서 동네 이웃의 콘텐츠(글·사진·가격)가 주인공이 되는 **로컬 커뮤니티** 시스템. 토큰은 rootage(스키마) → CSS/React/iOS/Android로 파생되며, 손으로 쓴 색상값은 존재하지 않는다.

---

## 목차

1. [디자인 원칙](#1-디자인-원칙)
2. [색상 토큰](#2-색상-토큰)
3. [타이포그래피](#3-타이포그래피)
4. [레이아웃 & 라운드](#4-레이아웃--라운드)
5. [핵심 컴포넌트](#5-핵심-컴포넌트)
6. [인터랙션](#6-인터랙션)
7. [다크 모드](#7-다크-모드)
8. [접근성](#8-접근성)
9. [안티패턴](#9-안티패턴)
10. [프레임워크 어댑터](#10-프레임워크-어댑터)
11. [이식 가이드](#11-이식-가이드)

---

## 1. 디자인 원칙

| 원칙 | 정의 |
|---|---|
| **단일 토큰 소스** | 색·간격·라운드는 토큰 정의 파일이 원천. React/iOS/Android/Web이 같은 소스에서 파생된다. |
| **콘텐츠가 주인공** | 이웃이 올린 사진·글·가격이 화면의 중심. UI는 무채색으로 물러나고 오렌지는 액션에만. |
| **담백한 뉴트럴** | 장식 없는 그레이 스케일. 그림자·그라디언트 최소화, 구분은 얇은 보더와 여백으로. |
| **친근한 밀도** | 리스트 한 줄에 필요한 정보만 — 제목·동네·시간·가격. 과밀한 메타데이터 금지. |
| **낮은 진입 장벽** | 큰 터치 타깃, 명확한 라벨, 짧은 문장. 전 연령 사용자가 헤매지 않는 UI. |
| **시맨틱 우선 명명** | `gray-900`이 아니라 `text-primary`처럼 역할로 소비 — 다크 모드가 공짜가 된다. |

---

## 2. 색상 토큰

### 2-1. 브랜드 & 시맨틱

```css
:root {
  --carrot:         #ff6f0f;  /* 캐럿 오렌지 — 주 CTA·브랜드 */
  --carrot-press:   #e8630c;
  --carrot-surface: #fff5ec;  /* 옅은 오렌지 면 */

  --success: #00a05b;
  --error:   #f04452;
  --info:    #0a7aff;
}
```

### 2-2. 뉴트럴 스케일

```css
:root {
  --gray-00:  #ffffff;
  --gray-100: #f8f9fa;
  --gray-200: #f2f3f6;  /* 섹션·입력 배경 */
  --gray-300: #dcdee3;  /* 보더 */
  --gray-400: #d1d3d8;
  --gray-500: #adb1ba;  /* 플레이스홀더 */
  --gray-600: #868b94;  /* 보조 텍스트 */
  --gray-700: #4d5159;
  --gray-800: #393b40;
  --gray-900: #212124;  /* 기본 텍스트 */
}
```

### 2-3. 시맨틱 별칭 — 컴포넌트가 소비하는 층

```css
:root {
  --bg:            var(--gray-00);
  --surface:       var(--gray-200);
  --text-primary:  var(--gray-900);
  --text-secondary:var(--gray-600);
  --text-hint:     var(--gray-500);
  --divider:       var(--gray-300);
  --primary:       var(--carrot);
}
```

### 2-4. 사용 규칙

| 역할 | 토큰 |
|---|---|
| 글 제목·가격 | `var(--text-primary)` |
| 동네·시간·조회수 | `var(--text-secondary)` |
| 플레이스홀더 | `var(--text-hint)` |
| 페이지 배경 | `var(--bg)` |
| 입력·섹션 배경 | `var(--surface)` |
| 리스트 구분선 | `var(--divider)` 1px |
| 글쓰기 FAB·주 버튼 | `var(--primary)` |
| 판매완료·상태 | `var(--success)` 등 시맨틱 |

> **핵심 규칙**: 오렌지는 화면당 한두 곳(FAB, 주 CTA)에만. 오렌지가 흔해지면 "지금 할 일"이 사라진다.

---

## 3. 타이포그래피

### 3-1. 폰트

```css
:root {
  --font-sans: 'Pretendard Variable', Pretendard,
               -apple-system, 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
}

body {
  font-family: var(--font-sans);
  color: var(--text-primary);
  background: var(--bg);
  word-break: keep-all;
}
```

```html
<link rel="stylesheet"
  href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" />
```

### 3-2. 타입 스케일

| 역할 | `font-size` | `font-weight` | 용도 |
|---|---|---|---|
| Title 1 | 24px | 700 | 페이지 제목 |
| Title 2 | 20px | 700 | 시트·섹션 제목 |
| Subtitle | 17px | 600 | 글 제목(상세) |
| Body | 16px | 400 | 본문 |
| List Title | 15px | 500 | 리스트 항목 제목 |
| Price | 15px | 700 | 가격, `tabular-nums` |
| Caption | 13px | 400 | 동네·시간 |
| Label | 12px | 500 | 뱃지·칩 |

- 위계는 크기 반 단계 + 웨이트 반 단계로 잘게 — 큰 점프 없이 자연스러운 흐름.

---

## 4. 레이아웃 & 라운드

```css
:root {
  --radius:      0.375rem;  /* 6px — 버튼·입력·썸네일 */
  --radius-chip: 62.4375rem;
  --spacing:     0.25rem;   /* 4px 배수 */
}

.container { max-width: 40rem; margin-inline: auto; }
```

- 모바일 우선 단일 컬럼 리스트. 항목 사이는 `--divider` 1px.
- 콘텐츠 좌우 패딩 16px 고정.

---

## 5. 핵심 컴포넌트

### 5-1. 중고거래 리스트 항목 — 시그니처

```html
<a class="feed-item" href="...">
  <img class="feed-item__thumb" src="..." alt="" />
  <div class="feed-item__body">
    <p class="feed-item__title">아이폰 14 프로 팝니다</p>
    <p class="feed-item__meta">역삼동 · 10분 전</p>
    <p class="feed-item__price">850,000원</p>
    <p class="feed-item__counts">관심 3 · 채팅 2</p>
  </div>
</a>
```

```css
.feed-item {
  display: flex;
  gap: 1rem;
  padding: 1rem;
  border-bottom: 1px solid var(--divider);
  text-decoration: none;
}
.feed-item__thumb {
  width: 108px; height: 108px;
  border-radius: var(--radius);
  border: 1px solid rgba(0, 0, 0, 0.04);
  object-fit: cover;
}
.feed-item__title  { font-size: 0.9375rem; font-weight: 500; color: var(--text-primary);
                     display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.feed-item__meta   { font-size: 0.8125rem; color: var(--text-secondary); margin-top: 0.25rem; }
.feed-item__price  { font-size: 0.9375rem; font-weight: 700; margin-top: 0.25rem;
                     font-variant-numeric: tabular-nums; }
.feed-item__counts { font-size: 0.8125rem; color: var(--text-secondary); margin-top: auto; }
.feed-item:active  { background: var(--gray-100); }
```

### 5-2. 글쓰기 FAB

```css
.fab {
  position: fixed;
  right: 1rem;
  bottom: calc(1rem + env(safe-area-inset-bottom));
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: 3rem;
  padding: 0 1.25rem;
  border: none;
  border-radius: var(--radius-chip);
  background: var(--primary);
  color: #ffffff;
  font-size: 0.9375rem;
  font-weight: 700;
  box-shadow: 0 4px 12px rgba(255, 111, 15, 0.35);
  cursor: pointer;
}
.fab:active { background: var(--carrot-press); }
```

### 5-3. 상태 뱃지

```css
.badge {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius);
  font-size: 0.75rem;
  font-weight: 500;
}
.badge--reserved { background: var(--info);    color: #fff; }  /* 예약중 */
.badge--sold     { background: var(--gray-600); color: #fff; } /* 거래완료 */
.badge--carrot   { background: var(--carrot-surface); color: var(--carrot); }
```

### 5-4. 버튼 & 칩

```css
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  padding: 0.75rem 1.25rem;
  border-radius: var(--radius);
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.btn--primary   { background: var(--primary); color: #fff; border: none; }
.btn--primary:active { background: var(--carrot-press); }
.btn--secondary { background: var(--surface); color: var(--text-primary); border: none; }
.btn:disabled   { background: var(--surface); color: var(--text-hint); }

/* 카테고리 필터 칩 */
.filter-chip {
  padding: 0.375rem 0.875rem;
  border: 1px solid var(--divider);
  border-radius: var(--radius-chip);
  background: var(--bg);
  font-size: 0.875rem;
  color: var(--text-primary);
  cursor: pointer;
}
.filter-chip[aria-pressed="true"] {
  background: var(--gray-900);
  border-color: var(--gray-900);
  color: #fff;
}
```

---

## 6. 인터랙션

- 탭 피드백: 배경 `--gray-100` 즉시 적용. 스케일 변형 없음 — 담백함 유지.
- 전환 `0.15s ease` 이내. 화면 전환은 플랫폼 네이티브(스택 전환)를 따른다.
- 새 글 로딩은 스켈레톤. 당겨서 새로고침 유지.
- FAB는 스크롤 다운 시 축소(아이콘만), 업 시 확장(아이콘+라벨).

---

## 7. 다크 모드

시맨틱 별칭만 뒤집는다 — 단일 토큰 소스 구조 덕에 컴포넌트 수정은 0.

```css
.dark {
  color-scheme: dark;

  --bg:            #17171c;
  --surface:       #212127;
  --text-primary:  #eaebee;
  --text-secondary:#868b94;
  --text-hint:     #5a5e66;
  --divider:       #2c2e33;

  --carrot:         #ff7e29;   /* 오렌지 한 단계 밝게 */
  --carrot-press:   #ff934d;
  --carrot-surface: #33210f;

  --success: #2cc27a;
  --error:   #ff6673;
  --info:    #4d9fff;
}
```

---

## 8. 접근성

- `--text-primary`(#212124) 대비 16.1:1, `--text-secondary`(#868b94) 3.5:1 — 보조 텍스트는 13px 이상 + 비필수 정보에만.
- 터치 타깃 최소 48px — 리스트 항목·버튼 모두 패딩으로 보장.
- 상태(예약중/거래완료)는 뱃지 텍스트로 명시 — 색·흐림 처리만으로 전달 금지.
- 이미지 항목에는 의미 있는 `alt`(상품명), 장식 이미지는 `alt=""`.
- 짧고 쉬운 문장 — 전 연령 사용자가 이해하는 라벨("판매하기", "관심").

---

## 9. 안티패턴

| 금지 | 이유 |
|---|---|
| 오렌지 남용 (화면당 3곳 이상) | 주 액션 신호 희석 |
| 하드코딩 색상값 | 단일 토큰 소스 원칙 — 별칭만 소비 |
| 그림자·그라디언트 장식 | 담백한 뉴트럴 원칙 (FAB 예외) |
| 리스트 메타 정보 과밀 | 제목·동네·시간·가격 4요소로 제한 |
| 큰 라운드(12px+) | 6px 절제 — 콘텐츠 우선 |
| 탭 시 스케일 애니메이션 | 배경 전환만 — 담백함 |
| 원시 그레이 직접 참조 | 시맨틱 별칭 층 우회 시 다크 모드 붕괴 |
| 어려운 한자어·영문 라벨 | 낮은 진입 장벽 원칙 |

---

## 10. 프레임워크 어댑터

### 10-1. Tailwind CSS v4

```css
@import "tailwindcss";

@theme {
  --color-bg:             #ffffff;
  --color-surface:        #f2f3f6;
  --color-text-primary:   #212124;
  --color-text-secondary: #868b94;
  --color-divider:        #dcdee3;
  --color-carrot:         #ff6f0f;
  --radius:               0.375rem;
}

@custom-variant dark (&:where(.dark, .dark *));
```

### 10-2. Tailwind CSS v3

```js
module.exports = {
  darkMode: 'class',
  theme: {
    extend: {
      colors: {
        bg:      'var(--bg)',
        surface: 'var(--surface)',
        primary: 'var(--carrot)',
        't-primary':   'var(--text-primary)',
        't-secondary': 'var(--text-secondary)',
        divider: 'var(--divider)',
      },
      borderRadius: { DEFAULT: '0.375rem' },
    },
  },
};
```

### 10-3. React

실제 SEED Design은 `@seed-design/css`(토큰·테마 CSS)와 React 컴포넌트 패키지를 제공한다. 직접 구축 시에도 같은 구조 — 토큰 CSS 한 파일 + 그것만 참조하는 컴포넌트 — 를 유지한다.

---

## 11. 이식 가이드

### Step 1 — 토큰 복사

§2의 브랜드/뉴트럴/별칭 + §7 다크 블록을 전역 CSS에 붙인다.

### Step 2 — Pretendard 연결

CDN 링크 추가, `word-break: keep-all` 설정.

### Step 3 — 시그니처 3종 적용

| 패턴 | 핵심 CSS |
|---|---|
| **피드 리스트 항목** | 108px 정방 썸네일 + 제목/동네·시간/가격 + 1px divider |
| **캐럿 FAB** | pill 오렌지 + 오렌지 그림자 — 화면당 유일한 강조 |
| **필터 칩** | 보더 pill, 선택 시 gray-900 반전 |

### Step 4 — 토큰 규율 검증

컴포넌트 CSS에서 hex 검색 — 별칭(`var(--...)`) 외 색상값이 나오면 실패.

### 신규 컴포넌트 결정 트리

```
새 컴포넌트가 필요하다
  → 콘텐츠(글·사진·가격)를 담는가?
    yes → 피드 항목 변형으로 — 4요소 제한 유지
    no  → 색이 필요한가?
      → 주 액션인가? → 오렌지 (화면당 1~2곳 총량 확인)
      → 상태인가? → success/error/info
      → 그 외 → 뉴트럴 별칭만
```
