# UI Design System — SOCAR Frame

> 어떤 스택에서든 동일한 톤을 이식하기 위한 단일 소스. 쏘카의 브랜드 자산과 모빌리티 서비스 UI 관례를 웹 이식용으로 재해석한 문서다.
>
> **한 줄 정의**: **지도 위에서도 또렷한 비비드 블루(`#0078ff`)** 를 단일 액션 컬러로, 실시간으로 변하는 차량·예약 상태를 상태 칩과 바텀시트로 전달하는 **모빌리티** 시스템. 지도가 배경이 되는 화면에서는 UI가 카드로 떠 있고, 모든 상태는 "지금"을 기준으로 표현된다.

---

## 목차

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

---

## 1. 디자인 원칙

| 원칙 | 정의 |
|---|---|
| **지도 위 가독성** | 컴포넌트는 지도라는 복잡한 배경 위에 뜬다 — 흰 카드 + 그림자 + 충분한 대비가 기본값. |
| **단일 비비드 블루** | `#0078ff` 하나로 액션·선택·진행을 표현. 지도 위 마커·CTA가 한눈에 잡히는 이유. |
| **실시간 상태 우선** | 예약 가능/이용 중/반납 임박 등 상태가 UI의 중심. 상태 칩은 색+텍스트로 항상 명시. |
| **바텀시트 흐름** | 지도 탐색 → 차량 선택 → 예약 확정이 모두 바텀시트 단계로 이어진다. 페이지 전환 최소화. |
| **시간·가격은 정밀하게** | 대여 시간·요금은 `tabular-nums`로 정렬하고 즉시 갱신. 애니메이션으로 흐리지 않는다. |
| **부드러운 라운드** | 10px 내외 — 신뢰감 있는 소프트함. 지도 위 카드 곡률과 버튼 곡률을 통일. |

---

## 2. 색상 토큰

### 2-1. 브랜드 & 상태

```css
:root {
  --primary:        #0078ff;  /* 쏘카 블루 — CTA·선택·마커 */
  --primary-press:  #0063d1;
  --primary-surface:#e8f2ff;  /* 선택 영역·안내 면 */

  --state-active:   #00b272;  /* 이용 가능·운행 중 */
  --state-warning:  #ff9330;  /* 반납 임박·주의 */
  --state-error:    #ff4d4d;  /* 예약 불가·사고 접수 */
}
```

### 2-2. 뉴트럴

```css
:root {
  --bg:        #ffffff;
  --surface:   #f4f6f8;  /* 페이지 배경·입력 배경 */
  --text:      #1b1d1f;
  --text-sub:  #686d72;
  --text-hint: #a5aab0;
  --border:    #e9ebee;
  --overlay:   rgba(27, 29, 31, 0.5);
}
```

### 2-3. 사용 규칙

| 역할 | 토큰 |
|---|---|
| 차량명·요금·제목 | `var(--text)` |
| 주소·부가 설명 | `var(--text-sub)` |
| 플레이스홀더 | `var(--text-hint)` |
| 지도 위 카드 | `#ffffff` + 그림자 |
| 예약 CTA·선택 마커 | `var(--primary)` |
| 이용 가능 상태 | `var(--state-active)` |
| 반납 임박 | `var(--state-warning)` |
| 예약 불가 | `var(--state-error)` |

> **핵심 규칙**: 상태색 3종(그린/오렌지/레드)은 상태 칩·마커에만 쓴다. 버튼·링크는 블루 단일.

---

## 3. 타이포그래피

### 3-1. 폰트 — Spoqa Han Sans Neo

```css
:root {
  --font-sans: 'Spoqa Han Sans Neo', 'Noto Sans KR',
               -apple-system, 'Malgun Gothic', sans-serif;
}

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

```html
<link rel="stylesheet"
  href="https://spoqa.github.io/spoqa-han-sans/css/SpoqaHanSansNeo.css" />
```

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

| 역할 | `font-size` | `font-weight` | 용도 |
|---|---|---|---|
| Display | 26px | 700 | 홈 인사·요금 총액 |
| Heading | 20px | 700 | 시트 제목 |
| Subtitle | 17px | 500 | 차량명 |
| Body | 15px | 400 | 본문·안내 |
| Small | 13px | 400 | 주소·부가 정보 |
| Caption | 12px | 400 | 시간·메타 |
| Number | — | 700 | 요금·시간, `tabular-nums` 필수 |

```css
.fare {
  font-size: 1.625rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.time-range { font-variant-numeric: tabular-nums; color: var(--text-sub); }
```

---

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

```css
:root {
  --radius:       0.625rem;  /* 10px — 카드·버튼·입력 */
  --radius-sheet: 1.25rem;   /* 바텀시트 상단 */
  --shadow-float: 0 4px 16px rgba(27, 29, 31, 0.12);  /* 지도 위 부유 요소 */
}

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

- 지도 화면: 지도가 전체를 채우고, 검색 바·필터 칩·차량 카드가 `--shadow-float`로 떠 있다.
- 일반 화면: `--surface` 배경 위 흰 카드 구조.

---

## 5. 핵심 컴포넌트

### 5-1. 바텀시트 — 시그니처

```css
.sheet {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  background: var(--bg);
  border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
  box-shadow: 0 -4px 24px rgba(27, 29, 31, 0.16);
  padding: 0.5rem 1.25rem calc(1.25rem + env(safe-area-inset-bottom));
  transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.4, 1);
}
.sheet__grabber {
  width: 40px; height: 4px;
  border-radius: 62.4375rem;
  background: var(--border);
  margin: 0.375rem auto 0.75rem;
}
```

- 접힘(핸들만) → 반펼침(핵심 정보) → 전체(상세) 3단계 스냅.

### 5-2. 차량 카드

```html
<article class="car-card">
  <img class="car-card__img" src="..." alt="아반떼 CN7" />
  <div class="car-card__body">
    <span class="chip chip--active">이용 가능</span>
    <h3 class="car-card__name">아반떼 CN7</h3>
    <p class="car-card__meta">강남역 4번 출구 · 320m</p>
    <p class="car-card__fare">10분당 <strong>1,200원</strong></p>
  </div>
</article>
```

```css
.car-card {
  display: flex;
  gap: 1rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem;
}
.car-card__img  { width: 88px; height: 66px; object-fit: contain; }
.car-card__name { font-size: 1.0625rem; font-weight: 500; }
.car-card__meta { font-size: 0.8125rem; color: var(--text-sub); }
.car-card__fare { font-size: 0.9375rem; }
.car-card__fare strong { font-weight: 700; font-variant-numeric: tabular-nums; }
```

### 5-3. 상태 칩

```css
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1875rem 0.5rem;
  border-radius: 62.4375rem;
  font-size: 0.75rem;
  font-weight: 500;
}
.chip--active  { background: #e5f7ef; color: #008a58; }
.chip--warning { background: #fff2e5; color: #cc6a10; }
.chip--error   { background: #ffecec; color: #d63030; }
.chip--primary { background: var(--primary-surface); color: var(--primary); }
```

### 5-4. 예약 CTA

```css
.cta {
  position: sticky;
  bottom: 0;
  width: 100%;
  min-height: 3.25rem;
  border: none;
  border-radius: var(--radius);
  background: var(--primary);
  color: #ffffff;
  font-size: 1.0625rem;
  font-weight: 700;
  cursor: pointer;
}
.cta:active { background: var(--primary-press); }
.cta:disabled { background: var(--border); color: var(--text-hint); }
```

### 5-5. 시간 선택 (대여 구간)

```css
.time-picker {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--surface);
  border-radius: var(--radius);
  padding: 0.875rem 1rem;
}
.time-picker__slot { text-align: center; }
.time-picker__label { font-size: 0.75rem; color: var(--text-sub); }
.time-picker__value { font-size: 0.9375rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.time-picker__arrow { color: var(--text-hint); }
```

---

## 6. 인터랙션

- 바텀시트 스냅은 `0.25s` 스프링 감각 이징 — 모빌리티 앱의 핵심 촉감.
- 지도 마커 선택 시: 마커 확대 + 블루 강조 + 시트에 즉시 반영 (0.2s 이내).
- 상태 변화(예약 확정 등)는 색 전환 + 햅틱(모바일) + 텍스트 갱신 동시.
- 요금 계산 값은 애니메이션 없이 즉시 — 금액 신뢰.
- `prefers-reduced-motion` 시 시트 이동을 페이드로 대체.

---

## 7. 다크 모드

지도 다크 스타일과 함께 전환되는 것을 전제로 한다.

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

  --bg:        #1b1d1f;
  --surface:   #26292c;
  --text:      #f0f1f2;
  --text-sub:  #a5aab0;
  --text-hint: #686d72;
  --border:    #34383c;
  --overlay:   rgba(0, 0, 0, 0.6);

  --primary:        #4d9fff;   /* 블루 한 단계 밝게 */
  --primary-press:  #74b4ff;
  --primary-surface:#12283f;

  --state-active:  #33c58c;
  --state-warning: #ffa85c;
  --state-error:   #ff7070;

  --shadow-float: 0 4px 16px rgba(0, 0, 0, 0.4);
}
```

- 다크 지도 위에서도 카드가 배경보다 밝은 `--surface`로 떠 보이도록 유지.

---

## 8. 접근성

- `--text`(#1b1d1f) 대비 16.6:1, `--text-sub`(#686d72) 5.2:1 — AA 통과.
- 상태는 색+텍스트 칩으로 이중 전달 — 마커 색만으로 상태를 표현하지 않는다.
- 바텀시트는 키보드/스크린리더로도 단계 이동 가능해야 한다 (`aria-expanded`, 포커스 이동).
- 지도 화면의 모든 기능은 목록 뷰로도 제공 — 지도 접근이 어려운 사용자 대응.
- 터치 타깃 44px 이상, CTA는 52px.

---

## 9. 안티패턴

| 금지 | 이유 |
|---|---|
| 상태색을 버튼·링크에 사용 | 액션은 블루 단일 |
| 마커 색만으로 상태 전달 | 칩 텍스트 병기 필수 |
| 요금·시간에 카운트업 애니메이션 | 금액·시간은 즉시·정확 |
| 지도 위 그림자 없는 플랫 카드 | 배경과 분리 불가 |
| 페이지 전환 남용 | 바텀시트 단계 흐름 유지 |
| 비례 숫자(proportional nums) | 시간·요금 정렬 붕괴 — `tabular-nums` |
| 시트 2단계 초과 중첩 | 시트 위 시트 위 시트 금지 |
| 저대비 상태 칩 (파스텔 위 파스텔) | 지도 위 가독성 원칙 |

---

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

### 10-1. Tailwind CSS v4

```css
@import "tailwindcss";

@theme {
  --color-bg:        #ffffff;
  --color-surface:   #f4f6f8;
  --color-text:      #1b1d1f;
  --color-text-sub:  #686d72;
  --color-border:    #e9ebee;
  --color-primary:   #0078ff;
  --color-active:    #00b272;
  --color-warning:   #ff9330;
  --color-error:     #ff4d4d;
  --radius:          0.625rem;
}

@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)',
        text:      'var(--text)',
        'text-sub':'var(--text-sub)',
        border:    'var(--border)',
        primary:   'var(--primary)',
        active:    'var(--state-active)',
        warning:   'var(--state-warning)',
        error:     'var(--state-error)',
      },
      borderRadius: { DEFAULT: '0.625rem', sheet: '1.25rem' },
    },
  },
};
```

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

CSS 변수가 원천. 바텀시트 스냅 상태(접힘/반펼침/전체)를 전역 상태로 관리하고, 지도 SDK 다크 스타일과 `.dark` 토글을 동기화한다.

---

## 11. 이식 가이드

### Step 1 — 토큰 복사

§2 + §7 블록을 전역 CSS에 붙인다.

### Step 2 — 폰트 연결

Spoqa Han Sans Neo CSS 링크를 `<head>`에 추가한다.

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

| 패턴 | 핵심 CSS |
|---|---|
| **바텀시트** | 상단 20px 라운드 + 그래버 + 3단 스냅 |
| **상태 칩** | 파스텔 배경 + 진한 텍스트 pill — 상태는 항상 텍스트 병기 |
| **블루 CTA** | `background: #0078ff; min-height: 52px` — 하단 고정 |

### Step 4 — 실시간성 검증

상태 변경이 0.2s 이내 반영되는지, 요금 숫자가 `tabular-nums`로 정렬되는지 확인한다.

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

```
새 컴포넌트가 필요하다
  → 지도 위에 뜨는가?
    yes → 흰 카드 + --shadow-float + 10px 라운드 상속
    no  → 바텀시트 단계에 들어가는가?
      yes → 시트 내부 섹션으로 구현
  → 상태를 표현하는가?
    yes → 상태색 3종 + 텍스트 칩 규격 사용
```
