# DESIGN.md — 디자인 재료 사용법

> **매번 찾지 말고 여기서 고르세요.** 색·글자·여백·아이콘이 전부 저장소 안에 들어 있습니다.
> 앱을 켜고 **`/design`** 을 열면 이 문서의 내용을 눈으로 보고 클릭해서 복사할 수 있습니다.
> 아이콘 이름 대응표는 [ICONS.md](./ICONS.md)에 있습니다.

---

## 1. 세 가지 재료가 어디 있나

| 재료 | 어디 | 매번 받아야 하나 |
|---|---|---|
| **아이콘** 1,993개 | `lucide-react` (node_modules) | ❌ `npm install` 한 번으로 끝. 오프라인 동작 |
| **폰트** Pretendard | `frontend/public/fonts/` | ❌ 저장소에 들어 있음. CDN 의존 없음 |
| **색·여백·글자 토큰** | `frontend/src/styles/global.css` 의 `:root` | ❌ 이미 정의돼 있음 |

셋 다 **이미 받아 놨습니다.** 새로 검색하거나 다운로드할 일은 없습니다. 고르기만 하세요.

---

## 2. 폰트 — 이름을 부른다고 폰트가 오지 않는다

⚠️ **실제로 있었던 문제입니다.** CSS에 이렇게 적혀 있었습니다.

```css
font-family: "Pretendard", "Apple SD Gothic Neo", "Malgun Gothic", system-ui, sans-serif;
```

그런데 Pretendard 파일을 **어디서도 받아오지 않았습니다.** `@font-face`도, CDN 링크도 없었습니다. 그래서 그 폰트가 따로 깔린 사람 외에는 전원 **맑은 고딕**으로 보고 있었습니다. 앱과 학습 문서 19개 전부요.

> 학습 포인트: `font-family`는 **"이 순서대로 시도해 봐"라는 희망 목록**일 뿐입니다. 브라우저는 목록의 이름을 (1) 내 컴퓨터에 깔린 폰트와 (2) `@font-face`로 알려 준 웹폰트에서만 찾습니다. 둘 다 없으면 조용히 다음 후보로 넘어갑니다. **에러가 안 나기 때문에** 몇 달 동안 아무도 모를 수 있습니다. "될 것 같은데 왜 안 되지?"의 절반은 이런 조용한 실패입니다.

지금은 이렇게 고쳤습니다.

```css
@font-face {
  font-family: "Pretendard";
  src: url("/fonts/PretendardStdVariable.woff2") format("woff2-variations");
  font-weight: 45 920;      /* 파일 하나가 모든 굵기를 담는다 (variable font) */
  font-display: swap;       /* 받는 동안 기본 글씨로 먼저 보여 준다 */
}
```

**왜 이 선택인가**

- **self-host** (CDN 아님) — 외부 서비스가 죽어도, 인터넷이 없어도 글씨가 안 깨집니다. 사내 학습 앱엔 이쪽이 맞습니다.
- **variable font** — 굵기별 파일 9개 대신 파일 1개. 요청 수와 용량이 함께 줄어듭니다.
- **Std 판(285KB)** — 상용 한글 2,350자 판입니다. 전체 한글 11,172자 판은 2MB로 7배입니다.
  이 앱이 쓰는 한글 **1,276자가 Std에 100% 들어 있는지 실제로 측정해서 확인**했습니다(폴백된 글자 0개).
- **font-display: swap** — 폰트를 받는 동안 글자를 숨기면 흰 화면이 됩니다(FOIT). 기본 글씨로 먼저 보여 주고 나중에 바꿔 끼웁니다.

---

## 3. 토큰 — 값 대신 이름을 쓴다

`global.css`의 `:root`에 정의돼 있습니다. **값을 직접 적지 마세요.**

```css
/* ❌ 이렇게 쓰면 */
padding: 10px;  color: #46536A;  font-size: 13.5px;

/* ✅ 이렇게 쓰세요 */
padding: var(--space-3);  color: var(--muted);  font-size: var(--text-sm);
```

| 종류 | 토큰 | 쓰는 곳 |
|---|---|---|
| 색 | `--bg` `--card` `--ink` `--muted` `--line` `--primary` `--teal` `--amber` `--rose` … | 전부 |
| 여백 | `--space-1`(4px) ~ `--space-7`(48px) | padding·margin·gap |
| 글자 크기 | `--text-xs`(12) `--text-sm`(13) `--text-md`(15, 본문) `--text-lg` `--text-xl` `--text-2xl` | font-size |
| 굵기 | `--weight-regular` `--weight-medium` `--weight-bold` `--weight-black` | font-weight |
| 모서리 | `--radius-sm` `--radius` `--radius-lg` `--radius-full` | border-radius |

**왜 이름을 쓰나 — 이유 두 개**

1. **다크 모드가 공짜로 따라옵니다.** `--ink`는 밝은 모드에서 거의 검정, 어두운 모드에서 거의 흰색입니다. `#101A28`이라고 직접 박으면 다크 모드에서 검은 배경에 검은 글씨가 됩니다.
2. **어긋남이 사라집니다.** 여백을 그때그때 10px, 14px, 6px로 찍으면 화면이 미묘하게 틀어집니다. 사람 눈은 그 차이를 "다름"이 아니라 "실수"로 읽습니다. 4의 배수 사다리만 밟으면 저절로 맞습니다.

---

## 4. 아이콘

규약과 132개 대응표는 [ICONS.md](./ICONS.md)에 있습니다. 요약하면:

```jsx
import { Rocket } from 'lucide-react';          // 개별 import (통째로 import 금지)

<Rocket size={18} strokeWidth={1.5} aria-hidden="true" />
```

- 굵기는 **항상 `strokeWidth={1.5}`** — 섞이면 통일감이 깨집니다.
- 아이콘은 장식이므로 `aria-hidden="true"`. 글자 없이 아이콘만 쓸 땐 부모에 `aria-label`.
- ⚠️ `lucide-react`는 아이콘 하나를 이름 3개로 내보냅니다(`Rocket`, `RocketIcon`, `LucideRocket`). 전부 세면 5,981개지만 **실제 아이콘은 1,993개**입니다. `/design`은 별칭을 걸러서 보여 줍니다.

이름은 `/design`에서 검색해 클릭하면 import 구문째로 복사됩니다.

---

## 5. 비용은 얼마나 드나 — 재 봤습니다

| | 크기 | 언제 받나 |
|---|---|---|
| 폰트 | 285 KB | 첫 방문 1회, 이후 캐시 |
| 아이콘 132개 (앱에서 실제 사용) | gzip +14 KB | 첫 화면 번들에 포함 |
| `/design` 페이지 | gzip 164 KB | **그 페이지에 들어갈 때만** (lazy) |

`/design`은 아이콘 1,993개를 전부 훑어야 해서 무겁습니다. 그래서 `App.jsx`에서 `lazy()`로 분리했습니다. 덕분에 이 페이지를 추가하고도 **첫 화면 번들은 1.9KB만 늘었습니다.**

> 학습 포인트: "무거운 기능을 넣으면 앱이 느려진다"는 **꼭 그렇지 않습니다.** 그 기능을 필요한 순간에만 내려받게 하면 됩니다. 무엇이 첫 화면에 실려야 하고 무엇이 나중에 와도 되는지를 나누는 것이 프론트엔드 성능 설계의 핵심입니다.

---

AWESOMEDEV · 디자인 재료 v1
