# UI Design System — Yeogi YDS

> 어떤 스택에서든 동일한 톤을 이식하기 위한 단일 소스. 여기어때 Design Library(designlibrary.yeogi.com) 공개 문서의 토큰 체계를 웹 이식용으로 재해석한 문서다.
>
> **한 줄 정의**: **라이블리 레드(`#f94239`)는 브랜드·프로모션, 시안 블루(`#1d8bff`)는 정보·액션.** 두 색의 역할을 엄격히 분리한 위에, `yds-` 접두사의 라디우스 8단계·섀도 6종 토큰과 Pretendard 5웨이트로 여행 콘텐츠(숙소·가격·날짜)를 경쾌하게 정돈하는 **여행 플랫폼** 시스템.

---

## 목차

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

---

## 1. 디자인 원칙

| 원칙 | 정의 |
|---|---|
| **A Visual Language for Travel** | 모든 시각 요소는 여행의 설렘을 전달한다 — 밝은 배경, 경쾌한 라운드, 생생한 포인트. |
| **레드/시안 역할 분리** | 라이블리 레드는 브랜드·프로모션·혜택, 시안 블루는 링크·선택·정보성 액션. 서로의 영역을 침범하지 않는다. |
| **스케일 기반 팔레트** | 모든 색은 10~900 스케일로 정의(Lively Red 50~900, Cyan 50~900, Neutral 10~900). 스케일 밖 색 금지. |
| **토큰 네이밍 규율** | 라디우스·섀도·알파까지 전부 `yds-` 토큰. 컴포넌트에 리터럴 값을 쓰지 않는다. |
| **콘텐츠(숙소)가 주인공** | UI는 사진과 가격이 돋보이게 물러선다. 카드 밖 장식 최소화. |
| **가격은 정확하게** | 할인·가격 표기는 즉시·정밀하게. `tabular-nums` 정렬, 애니메이션 금지. |

---

## 2. 색상 토큰

### 2-1. 브랜드 스케일 — Lively Red

```css
:root {
  --lively-red-50:  #fff5f5;
  --lively-red-100: #ffedea;
  --lively-red-200: #fedfde;
  --lively-red-500: #fb7f79;
  --lively-red-800: #f94239;   /* 브랜드 기준색 */
  --lively-red-900: #ea2013;   /* 프레스·강조 */
}
```

### 2-2. 액션 스케일 — Cyan

```css
:root {
  --cyan-50:  #f2f8ff;
  --cyan-100: #e3f0ff;
  --cyan-200: #d0e6ff;
  --cyan-500: #64a9ff;
  --cyan-800: #1d8bff;   /* 링크·선택·정보 액션 */
  --cyan-900: #006ce0;   /* 프레스 */
}
```

### 2-3. 뉴트럴 & 보조 팔레트

```css
:root {
  --common-100: #ffffff;
  --common-0:   #000000;

  --neutral-10:  #fafafa;
  --neutral-50:  #f5f5f5;   /* 섹션·입력 배경 */
  --neutral-100: #e6e6e6;
  --neutral-200: #dcdcdc;   /* 보더 */
  --neutral-300: #bbbbbb;
  --neutral-500: #8b8b8b;   /* 플레이스홀더 */
  --neutral-600: #707070;   /* 보조 텍스트 */
  --neutral-800: #474747;
  --neutral-900: #222222;   /* 기본 텍스트 */

  --navy-10:  #f5f7fa;      /* 정보 면 */
  --navy-600: #49627a;

  /* 의미·보조 스케일 (50 → 800 대표값) */
  --yellow-50: #fff4d8;  --yellow-800: #ffb803;   /* 평점·리워드 */
  --green-50:  #e7f7f3;  --green-800:  #01a484;   /* 성공·확정 */
  --orange-50: #ffe7d6;  --orange-600: #ff7847;   /* 타임세일 */
  --violet-10: #f6f5ff;  --violet-600: #7059fa;   /* 멤버십·프리미엄 */
}
```

### 2-4. 알파 토큰

오버레이·딤 처리는 알파 토큰으로 규격화한다 — 임의 rgba 금지.

```css
:root {
  --lively-red-800-a24: rgba(249, 66, 57, 0.24);
  --cyan-800-a20:       rgba(29, 139, 255, 0.20);
  --neutral-dim:        rgba(0, 0, 0, 0.40);
}
```

### 2-5. 사용 규칙

| 역할 | 토큰 |
|---|---|
| 숙소명·가격·본문 | `var(--neutral-900)` |
| 보조 정보·주소 | `var(--neutral-600)` |
| 플레이스홀더 | `var(--neutral-500)` |
| 페이지 배경 | `var(--common-100)` |
| 섹션·검색 바 배경 | `var(--neutral-50)` |
| 보더 | `var(--neutral-200)` 1px |
| 브랜드 CTA·할인율·쿠폰 | `var(--lively-red-800)` |
| 링크·선택·지도 마커 | `var(--cyan-800)` |
| 평점 별 | `var(--yellow-800)` |
| 예약 확정 | `var(--green-800)` |

> **핵심 규칙**: 레드와 시안이 한 컴포넌트 안에서 만나지 않게 한다. 혜택은 레드, 행동은 시안 — 신호가 섞이면 둘 다 죽는다.

---

## 3. 타이포그래피

### 3-1. 폰트 — Pretendard 5웨이트

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

body {
  font-family: var(--font-sans);
  color: var(--neutral-900);
  background: var(--common-100);
  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" />
```

- 웨이트는 Light(300) / Regular(400) / Medium(500) / SemiBold(600) / Bold(700) 5단계.
- 가격·할인율은 Bold, 본문은 Regular, 라벨은 Medium이 기본 짝.

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

| 역할 | `font-size` | `font-weight` | 용도 |
|---|---|---|---|
| Display | 24px | 700 | 홈 타이틀·이벤트 헤드라인 |
| Title 1 | 20px | 700 | 화면 제목 |
| Title 2 | 17px | 600 | 숙소명(상세) |
| List Title | 15px | 600 | 숙소명(목록) |
| Body | 15px | 400 | 본문·설명 |
| Price | 17px | 700 | 가격, `tabular-nums` |
| Caption | 13px | 400 | 주소·체크인 정보 |
| Label | 11px | 500 | 뱃지·태그 |

---

## 4. 라디우스 & 섀도

### 4-1. 라디우스 토큰 — 8단계

```css
:root {
  --yds-radius-02: 2px;    /* 미니 뱃지 */
  --yds-radius-03: 3px;
  --yds-radius-04: 4px;    /* 뱃지·태그 */
  --yds-radius-08: 8px;    /* 버튼·입력·카드 기본 */
  --yds-radius-10: 10px;
  --yds-radius-12: 12px;   /* 큰 카드·시트 */
  --yds-radius-16: 16px;   /* 이미지 카드 */
  --yds-radius-20: 20px;   /* 바텀시트 상단 */
}
```

- 컴포넌트는 반드시 8단계 중 하나를 사용한다. `border-radius: 7px` 같은 값은 존재할 수 없다.

### 4-2. 섀도 토큰 — 고정 2종 + 강조 4단계

```css
:root {
  /* 위치 고정 요소 전용 */
  --shadow-header: 0 1px 4px rgba(0, 0, 0, 0.08);    /* 상단 고정 헤더 */
  --shadow-dock:   0 -2px 8px rgba(0, 0, 0, 0.08);   /* 하단 고정 독·CTA */

  /* 강조 단계 — 숫자가 클수록 부상 */
  --shadow-1: 0 1px 3px rgba(0, 0, 0, 0.08);
  --shadow-2: 0 2px 8px rgba(0, 0, 0, 0.10);
  --shadow-3: 0 4px 16px rgba(0, 0, 0, 0.12);
  --shadow-4: 0 8px 24px rgba(0, 0, 0, 0.16);
}
```

- `shadow-header`/`shadow-dock`은 스크롤 고정 요소 전용 — 카드에 쓰지 않는다.
- 카드 1, 드롭다운 2, 플로팅 버튼 3, 모달 4.

---

## 5. 핵심 컴포넌트

### 5-1. 숙소 카드 — 시그니처

```html
<a class="stay-card" href="...">
  <div class="stay-card__thumb">
    <img src="..." alt="호텔 이름" />
    <span class="badge badge--timesale">타임세일</span>
  </div>
  <div class="stay-card__body">
    <p class="stay-card__name">시그니처 호텔 강남</p>
    <p class="stay-card__meta">
      <span class="rating">★ 9.2</span> · 강남역 3분 · 리뷰 1,204
    </p>
    <p class="stay-card__price">
      <span class="discount">32%</span>
      <span class="price">89,000원</span>
      <span class="original">129,000원</span>
    </p>
  </div>
</a>
```

```css
.stay-card { display: block; text-decoration: none; }
.stay-card__thumb {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--yds-radius-16);
  overflow: hidden;
}
.stay-card__name {
  margin-top: 0.625rem;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--neutral-900);
}
.stay-card__meta { font-size: 0.8125rem; color: var(--neutral-600); margin-top: 0.25rem; }
.rating { color: var(--yellow-800); font-weight: 600; }
.stay-card__price { display: flex; align-items: baseline; gap: 0.375rem; margin-top: 0.375rem; }
.discount { color: var(--lively-red-800); font-weight: 700; font-size: 1.0625rem; }
.price    { color: var(--neutral-900); font-weight: 700; font-size: 1.0625rem;
            font-variant-numeric: tabular-nums; }
.original { color: var(--neutral-500); font-size: 0.8125rem; text-decoration: line-through; }
```

### 5-2. 검색 바

```css
.search-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--neutral-50);
  border-radius: var(--yds-radius-08);
  padding: 0.875rem 1rem;
  font-size: 0.9375rem;
  color: var(--neutral-500);
}
.search-bar--active {
  background: var(--common-100);
  border: 1px solid var(--neutral-900);
  color: var(--neutral-900);
}
```

### 5-3. 버튼

```css
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  padding: 0.75rem 1.25rem;
  border-radius: var(--yds-radius-08);
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.btn--brand { background: var(--lively-red-800); color: #fff; border: none; }   /* 예약 CTA */
.btn--brand:active { background: var(--lively-red-900); }
.btn--action { background: var(--cyan-800); color: #fff; border: none; }        /* 정보성 액션 */
.btn--action:active { background: var(--cyan-900); }
.btn--outline { background: #fff; color: var(--neutral-900);
                border: 1px solid var(--neutral-200); }
.btn:disabled { background: var(--neutral-100); color: var(--neutral-300); border: none; }
```

### 5-4. 뱃지 & 필터 칩

```css
.badge {
  display: inline-block;
  padding: 0.1875rem 0.5rem;
  border-radius: var(--yds-radius-04);
  font-size: 0.6875rem;
  font-weight: 500;
}
.badge--timesale { background: var(--lively-red-800); color: #fff; }
.badge--coupon   { background: var(--lively-red-50);  color: var(--lively-red-800); }
.badge--info     { background: var(--cyan-50);   color: var(--cyan-800); }
.badge--member   { background: var(--violet-10); color: var(--violet-600); }
.badge--done     { background: var(--green-50);  color: var(--green-800); }

.filter-chip {
  padding: 0.4375rem 0.875rem;
  border: 1px solid var(--neutral-200);
  border-radius: 62.4375rem;
  background: var(--common-100);
  font-size: 0.875rem;
  color: var(--neutral-900);
  cursor: pointer;
}
.filter-chip[aria-pressed="true"] {
  border-color: var(--cyan-800);
  background: var(--cyan-50);
  color: var(--cyan-800);
  font-weight: 600;
}
```

### 5-5. 하단 예약 독(Dock)

```css
.dock {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--common-100);
  box-shadow: var(--shadow-dock);
  padding: 0.75rem 1.25rem calc(0.75rem + env(safe-area-inset-bottom));
}
.dock__price { font-size: 1.125rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.dock__unit  { font-size: 0.75rem; color: var(--neutral-600); }
.dock .btn--brand { flex: 1; }
```

---

## 6. 인터랙션

- 전환 `0.15~0.2s ease`. 탭 피드백은 배경 한 단계 어둡게(스케일의 이웃 단계).
- 카드 목록은 스켈레톤 로딩(썸네일 면 + 텍스트 바).
- 가격·잔여 객실 수 갱신은 즉시 — 카운트 애니메이션 금지.
- 바텀시트는 `--yds-radius-20` 상단 라운드로 아래에서 등장, 0.25s.
- `prefers-reduced-motion` 시 이동 전환을 페이드로 대체.

---

## 7. 다크 모드

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

  --common-100: #171719;         /* 배경 반전 */
  --neutral-10:  #1f1f22;
  --neutral-50:  #26262a;
  --neutral-100: #333338;
  --neutral-200: #3f3f45;
  --neutral-300: #55555c;
  --neutral-500: #8b8b8b;
  --neutral-600: #a6a6ad;
  --neutral-900: #f0f0f2;

  --lively-red-800: #ff5a50;     /* 레드 한 단계 밝게 */
  --lively-red-900: #ff7a72;
  --lively-red-50:  #33201f;

  --cyan-800: #4da3ff;
  --cyan-900: #74b8ff;
  --cyan-50:  #142838;

  --yellow-800: #ffc83b;
  --green-800:  #2cbf9e;
  --violet-600: #a799ff;
  --violet-10:  #232033;

  --shadow-header: 0 1px 4px rgba(0, 0, 0, 0.4);
  --shadow-dock:   0 -2px 8px rgba(0, 0, 0, 0.4);
}
```

- 숙소 사진은 다크에서 더 살아난다 — 썸네일에 얇은 보더 대신 라운드 유지로 경계를 잡는다.
- 파스텔 뱃지 면(50 스케일)은 다크 전용 딥 톤으로 교체 — 형광화 금지.

---

## 8. 접근성

- `--neutral-900`(#222) 대비 15.9:1, `--neutral-600`(#707070) 5.3:1 — AA 통과.
- `--neutral-500`(#8b8b8b)은 플레이스홀더·취소선 정가 전용 (대비 3.5:1).
- 할인율·가격은 색+굵기+취소선 삼중 구분 — 색약 사용자 대응.
- 평점은 별 아이콘 + 숫자 병기 (`aria-label="평점 9.2점"`).
- 타임세일 카운트다운은 색만이 아니라 남은 시간 텍스트로 전달.
- 터치 타깃 48px, 하단 독 CTA는 52px.

---

## 9. 안티패턴

| 금지 | 이유 |
|---|---|
| 레드와 시안을 한 컴포넌트에 혼용 | 혜택/행동 신호 분리 원칙 |
| 스케일 밖 임의 색 | 10~900 스케일이 팔레트의 전부 |
| 토큰 밖 라디우스 값 | yds-radius 8단계만 |
| 카드에 shadow_header/dock 사용 | 고정 요소 전용 섀도 |
| 임의 rgba 오버레이 | 알파 토큰 사용 |
| 가격 카운트업 애니메이션 | 가격은 즉시·정확 — 신뢰 요소 |
| 레드 남용 (혜택 아닌 곳) | 프로모션 신호 희석 |
| 썸네일 비율 혼용 | 16:10 고정 — 그리드 정렬 |

---

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

### 10-1. Tailwind CSS v4

```css
@import "tailwindcss";

@theme {
  --color-bg:          #ffffff;
  --color-surface:     #f5f5f5;
  --color-text:        #222222;
  --color-text-sub:    #707070;
  --color-border:      #dcdcdc;
  --color-brand:       #f94239;
  --color-action:      #1d8bff;
  --color-rating:      #ffb803;
  --color-success:     #01a484;
  --radius:            0.5rem;
  --radius-image:      1rem;
  --radius-sheet:      1.25rem;
}

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

### 10-2. Tailwind CSS v3

```js
module.exports = {
  darkMode: 'class',
  theme: {
    extend: {
      colors: {
        bg:       'var(--common-100)',
        surface:  'var(--neutral-50)',
        text:     'var(--neutral-900)',
        'text-sub': 'var(--neutral-600)',
        border:   'var(--neutral-200)',
        brand:    'var(--lively-red-800)',
        action:   'var(--cyan-800)',
        rating:   'var(--yellow-800)',
      },
      borderRadius: {
        DEFAULT: 'var(--yds-radius-08)',
        image:   'var(--yds-radius-16)',
        sheet:   'var(--yds-radius-20)',
      },
      boxShadow: {
        header: 'var(--shadow-header)',
        dock:   'var(--shadow-dock)',
        1: 'var(--shadow-1)', 2: 'var(--shadow-2)',
        3: 'var(--shadow-3)', 4: 'var(--shadow-4)',
      },
    },
  },
};
```

### 10-3. React / Vue / Svelte

CSS 변수가 원천. 숙소 카드를 단일 컴포넌트로 만들고 뱃지·가격 위계를 데이터로 제어한다. 다크 전환은 `.dark` 토글 한 줄.

---

## 11. 이식 가이드

### Step 1 — 토큰 복사

§2 팔레트 + §4 라디우스/섀도 + §7 다크 블록을 전역 CSS에 붙인다.

### Step 2 — Pretendard 연결

CDN 링크를 `<head>`에 추가하고 `word-break: keep-all`을 설정한다.

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

| 패턴 | 핵심 CSS |
|---|---|
| **숙소 카드** | 16:10 썸네일 `radius-16` + 평점(옐로)·가격(레드 할인율+블랙 가격) |
| **레드/시안 분리** | 예약 CTA·혜택=레드, 링크·선택·필터=시안 |
| **하단 독** | `shadow-dock` + 가격 + 풀폭 레드 CTA |

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

라디우스·섀도·색상에 토큰 밖 리터럴 값이 없는지 grep으로 검사한다.

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

```
새 컴포넌트가 필요하다
  → 색이 필요한가?
    → 혜택·프로모션인가? → Lively Red 스케일
    → 행동·선택·정보인가? → Cyan 스케일
    → 상태 표현인가? → Yellow/Green/Orange/Violet 대표값
    → 그 외 → Neutral 스케일만
  → 라디우스는 yds 8단계 중, 섀도는 강조 4단계 중 선택
  → 고정 요소인가? → shadow_header / shadow_dock
```
