# Design System — Bento Grid (벤토 그리드)

> 크기가 서로 다른 타일을 한 판에 촘촘히 담는 모듈형 레이아웃. Apple 기능 소개 페이지, Linear·Vercel 랜딩, 개인 대시보드에서 쓰인다.
> 공통 규칙은 `anti-slop.md`를 함께 적용한다. 미리보기: `preview/bento.html` (러닝 기록 대시보드 예시)

---

## 1. 분위기

도시락 칸막이처럼 크기가 다른 칸에 서로 다른 반찬을 하나씩 담는다. 핵심은 **비대칭과 크기 위계**다. 같은 크기 카드 3개를 늘어놓은 것은 벤토가 아니다. 화면에 들어오자마자 가장 큰 타일 하나가 눈을 잡고, 나머지 타일은 그 주변에서 보조 정보를 하나씩 맡는다.

각 타일은 **아이디어 하나**만 담는다. 숫자 하나, 미니 차트 하나, 목록 하나, 지도 하나, 인용 하나. 타일 안에 "아이콘 + 제목 + 설명 두 줄"만 넣으면 기능 소개 카드가 되고 벤토의 밀도가 사라진다. 타일은 실제 데이터 시각화(스파크라인, 진행 링, 히트맵 달력, 막대)를 품어야 한다.

표면은 조용하다. 따뜻한 회백색 바탕 위에 거의 흰 타일, 한 단계 가라앉은 회색 타일, 그리고 히어로용 먹색 타일 하나. 그림자 대신 **표면 톤 차이**로 칸을 나누고, 간격(12px)과 반경(20px)은 모든 타일이 똑같이 쓴다. 색은 절제한다. 액센트(시그널 오렌지)는 데이터의 "지금" 지점에만 칠한다.

**핵심 특징**
- 12열 CSS Grid + `grid-template-areas`로 2x2 / 2x1 / 1x1 블록을 섞는다
- 히어로 타일 1개 (가장 크고, 유일한 먹색 반전 타일)
- 단일 간격 `12px`, 단일 반경 `20px`, 내부 요소는 동심 반경 `12px`
- 깊이는 그림자가 아니라 표면 톤 3단계로
- 숫자는 모노 서체 + `tabular-nums`, 본문은 Pretendard
- 데스크톱에서는 빈틈없이 꽉 차고, 모바일에서는 한 줄로 접힌다

---

## 2. 컬러 팔레트 & 역할

웜 뉴트럴 + 액센트 1색. 파랑·보라 계열은 쓰지 않는다. 의미 색(성공 초록)은 증감 표시처럼 작은 곳에만 쓴다.

### Light (기본)

```css
:root {
  /* 표면 */
  --bg:           oklch(0.945 0.005 85);  /* #EEEDE9 페이지 바탕 */
  --tile:         oklch(0.995 0.002 85);  /* #FEFDFB 기본 타일 */
  --tile-2:       oklch(0.915 0.006 85);  /* #E5E3DE 가라앉은 타일, 트랙, 칩 */
  --ink:          oklch(0.215 0.005 85);  /* #1C1B19 히어로 타일, 주 버튼 */
  --ink-2:        oklch(0.285 0.006 85);  /* #2B2A27 히어로 안 구분선 */

  /* 텍스트 */
  --text:         oklch(0.215 0.005 85);  /* #1C1B19 */
  --muted:        oklch(0.53 0.008 85);   /* #6F6D67 라벨, 보조 */
  --faint:        oklch(0.72 0.008 85);   /* #A9A7A0 축 눈금, 메타 */
  --on-ink:       oklch(0.96 0.004 85);   /* #F2F1ED 먹색 위 본문 */
  --on-ink-muted: oklch(0.68 0.006 85);   /* #9E9C96 먹색 위 라벨 */

  /* 선 */
  --line:         oklch(0.89 0.006 85);   /* #DCDAD4 목록 구분선 */

  /* 액센트 (화면당 '지금'을 가리키는 곳에만) */
  --accent:       oklch(0.665 0.185 42);  /* #EE6A2E 시그널 오렌지 */
  --accent-soft:  oklch(0.93 0.045 55);   /* #F8DFCF 영역 채움, 히트맵 1단계 */
  --positive:     oklch(0.52 0.12 150);   /* #2E8752 증감 표시 전용 */
}
```

### Dark

다크 모드에서는 히어로가 "먹색"이 아니라 한 단계 **밝은** 타일이 된다. 반전 관계를 유지하는 것이 목적이다.

```css
:root[data-theme="dark"] {
  --bg:           oklch(0.165 0.004 85);  /* #121210 */
  --tile:         oklch(0.215 0.005 85);  /* #1C1B19 */
  --tile-2:       oklch(0.255 0.005 85);  /* #252421 */
  --ink:          oklch(0.93 0.006 85);   /* #EAE8E3 히어로 (반전) */
  --ink-2:        oklch(0.85 0.006 85);   /* #D3D1CB */
  --text:         oklch(0.93 0.004 85);   /* #EBEAE6 */
  --muted:        oklch(0.66 0.007 85);   /* #9A9892 */
  --faint:        oklch(0.50 0.007 85);   /* #6B6964 */
  --on-ink:       oklch(0.215 0.005 85);  /* #1C1B19 */
  --on-ink-muted: oklch(0.45 0.007 85);   /* #5E5C57 */
  --line:         oklch(0.29 0.005 85);   /* #2D2C29 */
  --accent:       oklch(0.72 0.17 45);    /* #F57E45 다크에서 명도 +0.05 */
  --accent-soft:  oklch(0.32 0.06 45);    /* #4A2C1E */
  --positive:     oklch(0.72 0.13 150);   /* #5DBB81 */
}
```

### 역할 규칙

| 토큰 | 쓰는 곳 | 쓰지 않는 곳 |
|---|---|---|
| `--accent` | 차트의 현재값·최신 지점, 다음 일정 1건, 진행 링, 경고 수준 진행바 | 버튼 배경, 제목 텍스트, 타일 배경 전체 |
| `--ink` | 히어로 타일 1개, 주 버튼 1개, 막대 중 강조 1개 | 일반 타일 배경 (2개 이상 금지) |
| `--tile-2` | 보조 타일 1~2개, 진행 트랙, 날짜 박스, 칩 | 페이지 바탕 |
| 히트맵 | `--tile-2` → 액센트 4단계 순차 램프 | 무지개 팔레트 |

히트맵 램프 (light): `oklch(0.88 0.06 52)` → `oklch(0.80 0.11 48)` → `oklch(0.71 0.16 44)` → `oklch(0.58 0.17 40)`.

---

## 3. 타이포그래피

| 역할 | 서체 |
|---|---|
| UI·본문·라벨 | **Pretendard Variable** (`cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css`) |
| 숫자·시간·단위·축 눈금 | **Geist Mono** (Google Fonts), 대체 JetBrains Mono |

```css
--font-sans: "Pretendard Variable", Pretendard, -apple-system, sans-serif;
--font-num:  "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
.num { font-family: var(--font-num); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
```

### 스케일 (6단계 고정)

| 토큰 | 크기 / 굵기 / 자간 | 용도 |
|---|---|---|
| `display` | `clamp(52px, 5.4vw, 68px)` / 500 / -0.07em, mono | 히어로 수치 1개 |
| `metric` | 38px / 500 / -0.045em, mono | 메트릭 타일 수치 |
| `metric-sm` | 30px / 500 / -0.04em, mono | 링 옆 퍼센트 |
| `title` | 17px / 700 / -0.03em | 페이지 제목 |
| `body` | 14px / 600 (목록 제목) · 13px / 400 (설명) | 목록, 설명 |
| `label` | 13px / 600 / `--muted` | 타일 헤더 라벨 |
| `micro` | 11~12px / 500 / `--faint` | 축 눈금, 메타, 칩 |

원칙: 라벨은 **작고 회색**, 수치는 **크고 모노**. 타일 제목을 크게 키우지 않는다. 위계는 수치 크기가 만든다. 단위(`km`, `/km`, `원`)는 수치의 30~40% 크기로 낮춰 `--muted`로.

---

## 4. 컴포넌트

### 공통 타일

```css
.tile {
  background: var(--tile);
  border-radius: 20px;          /* --r-tile, 모든 타일 동일 */
  padding: 20px;                /* --pad */
  display: flex; flex-direction: column;
  min-width: 0; overflow: hidden; position: relative;
}
.tile--ink  { background: var(--ink); color: var(--on-ink); }   /* 히어로 전용 */
.tile--sunk { background: var(--tile-2); }                      /* 보조 */
.t-head  { display: flex; justify-content: space-between; align-items: center; }
.t-label { font-size: 13px; font-weight: 600; color: var(--muted); }
```

**동심 반경**: 타일 안에 박스를 넣을 때 `내부 반경 = 외부 반경 - 안쪽 여백`. 타일 20px에 12px 안쪽으로 붙는 카드는 `8~12px`. 내부 박스 반경을 타일과 같은 20px로 두면 모서리가 어긋나 보인다.

### Hero 타일 (2x2 이상, 1개)

가장 중요한 지표 하나 + 그 지표의 추세 차트 + 하단 보조 수치 3개.

```css
.hero { grid-area: hero; }               /* .tile.tile--ink */
.hero .big { font: 500 68px/0.95 var(--font-num); letter-spacing: -0.07em; }
.hero .unit { font-size: 20px; color: var(--on-ink-muted); }
.hero-chart { flex: 1; min-height: 0; }  /* SVG가 남은 높이를 채움 */
.hero-foot { display: grid; grid-template-columns: repeat(3,1fr);
             border-top: 1px solid var(--ink-2); padding-top: 14px; }
.hero-foot div + div { border-left: 1px solid var(--ink-2); padding-left: 16px; }
```

차트: 누적 영역(액센트 16% 채움 + 2.25px 선) vs 전년 점선(`#77756f`), 목표선 점선, 마지막 지점에 점 하나. 범례는 우상단 텍스트로.

### Metric 타일 (1x1)

```css
.metric { display: flex; align-items: baseline; gap: 6px; margin-top: 12px; }
.metric .v { font: 500 38px/1 var(--font-num); letter-spacing: -0.045em; }
.metric .u { font-size: 14px; color: var(--muted); }
.metric .delta { margin-left: auto; font-size: 12px; font-weight: 600; color: var(--positive); }
.spark { flex: 1; margin-top: 10px; }    /* 스파크라인: 1.75px 선, 채움 없음, 평균 점선 1개 */
```

### Chart 타일 — 진행 링 / 막대 / 히트맵

```html
<!-- 이중 진행 링: 바깥=주 지표(액센트), 안=보조(ink) -->
<svg viewBox="0 0 120 120" width="116" height="116">
  <circle cx="60" cy="60" r="50" fill="none" stroke="var(--tile-2)" stroke-width="12"/>
  <circle cx="60" cy="60" r="50" fill="none" stroke="var(--accent)" stroke-width="12"
          stroke-linecap="round" stroke-dasharray="276.5 314.16" transform="rotate(-90 60 60)"/>
</svg>
```

```css
/* 히트맵 달력: 열=주, 행=요일 */
.heat-grid { display: grid; grid-auto-flow: column; grid-template-rows: repeat(7,1fr);
             gap: 3px; height: 112px; }
.heat-grid span { border-radius: 3px; background: var(--tile-2); }
.heat-grid span[data-l="4"] { background: oklch(0.58 0.17 40); }
.heat-grid span[data-l="x"] { background: transparent; box-shadow: inset 0 0 0 1px var(--line); } /* 미래 날짜 */
```

막대: 기본 막대는 `#CFCDC7`, 강조 1개는 `--ink`, 현재 진행 중인 막대만 `--accent`. 값 라벨은 강조 막대 위에만 붙인다. `rx: 5`.

### List 타일 (세로 2칸 이상)

날짜 박스 + 제목/부제 + 우측 정렬 수치. 첫 항목(다음 일정)만 날짜 박스를 액센트로.

```css
.list li { display: grid; grid-template-columns: 52px 1fr auto; gap: 14px;
           align-items: center; padding: 12px 0; border-top: 1px solid var(--line); }
.list li:first-child { border-top: 0; }
.dbox { border-radius: 12px; background: var(--tile-2); text-align: center; padding: 7px 0; }
.list li.next .dbox { background: var(--accent); color: #fff; }
```

목록 아래에는 "다음 행동" 한 줄(시계 아이콘 + 문장)을 붙여 타일 바닥을 채운다.

### Media 타일 (지도·이미지, 패딩 0)

```css
.map { grid-area: map; padding: 0; }
.map-canvas { position: absolute; inset: 0; background: var(--tile-2); }
.map-card { position: absolute; left: 12px; right: 12px; bottom: 12px;
            background: var(--tile); border-radius: 12px;   /* 20 - 8 ≈ 12 동심 */
            padding: 14px 16px; display: grid;
            grid-template-columns: 1.5fr 1fr 1fr 0.9fr; gap: 12px; align-items: end; }
```

미디어는 끝까지 꽉 채우고(full-bleed), 정보는 떠 있는 내부 카드에 담는다. 지도는 외부 이미지 없이 SVG로: 사선 격자(도로) + 하천 띠 + 경로선(흰 9px 외곽선 위 액센트 4px) + km 표시 원.

### 보조 요소

- 칩: `font-size 12px / 600`, `padding 3px 9px`, `border-radius 999px`, `--tile-2` 배경
- 세그먼트 컨트롤: `--tile-2` 트랙, 선택 항목만 `--tile` + `0 0 0 1px var(--line)` 링
- 주 버튼: `--ink` 배경 알약형 34px, 화면에 1개

---

## 5. 레이아웃 원칙

### 간격·반경 토큰

| 토큰 | 값 |
|---|---|
| `--gap` | 12px (타일 사이, 모든 브레이크포인트 동일. 16px까지 허용, 섞지 않음) |
| `--pad` | 20px (타일 안쪽), 모바일 18px |
| `--r-tile` | 20px |
| `--r-inner` | 12px |
| 페이지 여백 | 데스크톱 20px, 모바일 16px |

### 데스크톱 12열 (≥1025px)

행은 `minmax(196px, 1fr)` 4줄로 뷰포트 높이를 꽉 채운다.

```css
.bento {
  display: grid; gap: 12px;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-template-rows: repeat(4, minmax(196px, 1fr));
  grid-template-areas:
    "hero hero hero hero hero ring ring ring map  map  map  map"
    "hero hero hero hero hero pace pace pace map  map  map  map"
    "heat heat heat heat heat heat heat race race race race race"
    "week week week week shoe shoe shoe race race race race race";
}
```

크기 구성: 히어로 5x2, 미디어 4x2, 목록 5x2, 와이드 7x1, 작은 타일 3~4x1. **같은 크기의 타일이 3개 연속 나란히 오지 않게** 한다.

### 태블릿 6열 (641~1024px)

```css
grid-template-columns: repeat(6, minmax(0, 1fr));
grid-template-rows: none;
grid-auto-rows: minmax(190px, auto);
grid-template-areas:
  "hero hero hero hero ring ring"
  "hero hero hero hero pace pace"
  "map  map  map  race race race"
  "map  map  map  race race race"
  "heat heat heat heat heat heat"
  "week week week shoe shoe shoe";
```

### 모바일 1열 (≤640px)

```css
grid-template-columns: minmax(0, 1fr);
grid-auto-rows: auto;
grid-template-areas: "hero" "ring" "pace" "race" "map" "heat" "week" "shoe";
```

순서는 **중요도 순**으로 다시 짠다(HTML 순서와 달라도 됨). 차트 타일은 높이가 0이 되지 않도록 `min-height`(히어로 380px, 지도 340px, 차트 200px)를 준다.

---

## 6. 깊이 & 엘리베이션

벤토의 깊이는 **표면 톤**이다. 그림자는 기본적으로 쓰지 않는다.

| 레벨 | 처리 | 용도 |
|---|---|---|
| 0 바탕 | `--bg` | 페이지 |
| 1 타일 | `--tile` (바탕보다 밝게) | 대부분의 타일 |
| 1' 가라앉음 | `--tile-2` | 보조 타일, 지도 바탕 |
| 2 반전 | `--ink` | 히어로 1개 |
| 3 떠 있는 카드 | 타일 안의 `--tile` 박스 (미디어 위) | 지도 정보 카드 |
| 링 | `0 0 0 1px var(--line)` | 세그먼트 선택 항목 |

호버 시 그림자를 키우지 않는다. 클릭 가능한 타일이면 `background`를 `--tile-2` 쪽으로 2% 이동하거나 `outline: 2px solid var(--accent)`(포커스)만 쓴다. 등장 모션은 타일당 6px 상승 + 페이드, 320ms, 30ms 순차 지연, `prefers-reduced-motion` 시 끔.

---

## 7. Do / Don't

### Do
- 타일 크기를 3종 이상 섞어 위계를 만든다 (5x2, 4x2, 3x1 등)
- 히어로 1개만 반전색으로, 나머지는 톤 차이만
- 모든 타일에 실제 데이터 시각화 또는 실제 목록을 넣는다
- 수치는 모노 + `tabular-nums`, 단위는 작게 회색으로
- 액센트는 "지금/다음"을 가리키는 곳에만 (최신 지점, 다음 일정, 경고 수준)
- 타일 하단을 비우지 않는다. 차트를 `flex: 1`로 늘리거나 다음 행동 한 줄을 둔다
- 미래·빈 데이터도 표시한다 (히트맵의 미래 칸 외곽선, "기록 없음")
- 한국어 현실 데이터: `1,493.3 km`, `5'08" /km`, `D-18`, `10.04 (일) 06:12`, `₩ 1,284,000`

### Don't
- 같은 크기 카드 3개(아이콘 + 제목 + 설명) 나열. 벤토가 아니라 기능 카드다
- 타일마다 다른 반경, 다른 간격
- `box-shadow: 0 10px 30px rgba(0,0,0,.1)`로 타일 띄우기
- 인디고·보라 그라디언트, 타일 배경 글로우, 블롭, 유리 효과
- 반전(먹색) 타일 2개 이상, 액센트 배경 타일
- 타일 하나에 지표 4~5개를 욱여넣기 (그건 표다)
- 장식용 아이콘을 타일 좌상단마다 배치
- 이모지
- 모바일에서 12열 영역을 그대로 축소해 글자를 9px로 만들기

---

## 8. 반응형

| 구간 | 열 | 행 | 비고 |
|---|---|---|---|
| ≥1280px | 12 | 4행 `1fr` (뷰포트 높이 채움) | 최대 폭 1280px 중앙 |
| 1025~1279px | 12 | 4행 `minmax(196px,1fr)` | 히어로 수치 `clamp`로 축소 |
| 641~1024px | 6 | `auto`, 최소 190px | 지도·목록 3x2 짝 |
| ≤640px | 1 | `auto` | 중요도 순 재배치, 세그먼트·날짜 숨김 |

- 그리드 영역만 바꾸고 타일 내부 마크업은 그대로 둔다. 타일 내부는 `flex` + `min-width: 0`으로 스스로 적응.
- SVG 차트는 `viewBox` + `preserveAspectRatio="none"` + `vector-effect: non-scaling-stroke`로 선 굵기를 유지하며 늘린다. 지도는 `xMidYMid slice`.
- 모바일 터치 타깃 40px 이상, 세그먼트 버튼 높이 36px 이상.
- 컨테이너 쿼리를 쓸 수 있다면 `.tile { container-type: inline-size; }` 후 `@container (max-width: 280px)`로 링 옆 텍스트를 아래로 내린다.

---

## 9. 에이전트 프롬프트 가이드

### 빠른 토큰

```
bg #EEEDE9 · tile #FEFDFB · tile-2 #E5E3DE · ink(hero) #1C1B19
text #1C1B19 · muted #6F6D67 · line #DCDAD4 · accent #EE6A2E
gap 12px · radius 20px (inner 12px) · pad 20px
Pretendard Variable + Geist Mono(tabular-nums)
```

### 복사용 프롬프트 1 — 개인 대시보드

```
벤토 그리드 스타일로 개인 러닝 기록 대시보드를 만들어줘. designs/bento.md를 따른다.
- 12열 CSS Grid, grid-template-areas 사용. 타일 크기를 섞을 것: 히어로 5x2, 지도 4x2, 목록 5x2, 와이드 7x1, 작은 타일 3x1
- 히어로(먹색 #1C1B19 1개만): 올해 누적 거리 1,493.3 km + 월별 누적 영역 차트(올해 실선 #EE6A2E, 작년 점선) + 하단 보조 수치 3개
- 나머지 타일: 이중 진행 링, 페이스 스파크라인, 경로 지도(SVG), 26주 히트맵 달력, 다가오는 대회 목록, 12주 막대, 신발 마일리지 진행바
- 간격 12px, 반경 20px 단일. 그림자 없음, 표면 톤(#FEFDFB / #E5E3DE)으로만 구분
- 숫자는 Geist Mono + tabular-nums, 나머지는 Pretendard
- 모바일 1열로 접고 중요도 순으로 재배치
```

### 복사용 프롬프트 2 — 제품 기능 소개 섹션

```
랜딩 페이지 기능 소개를 "카드 3개" 대신 벤토 그리드로 만들어줘. designs/bento.md 기준.
- 타일마다 아이디어 하나: 실제 수치(예: "배포 평균 38초"), 실제 UI 조각(작은 목록·차트), 고객 인용 1개
- 가장 중요한 기능 하나를 2x2 히어로로, 나머지는 2x1, 1x1로 섞기
- 아이콘+제목+설명만 있는 타일 금지. 각 타일에 시각 요소(SVG 차트, 코드 조각, 진행 링)를 넣을 것
- 액센트 #EE6A2E는 화면 전체에서 "지금"을 가리키는 2~3곳에만
```

### 복사용 프롬프트 3 — 팀 지표 / 가계부

```
팀 주간 지표(또는 월 가계부)를 벤토 레이아웃으로 구성해줘. designs/bento.md 참고.
- 히어로: 이번 달 지출 ₩ 2,184,300 + 일별 누적 vs 예산선
- 메트릭 타일: 전월 대비 증감, 카테고리 상위 3개 가로 막대, 고정비 진행 링
- 목록 타일: 다가오는 결제 일정 (날짜 박스 + 금액 우측 정렬, 첫 항목만 액센트)
- 히트맵: 최근 26주 일별 지출 강도
- 빈 상태(데이터 없는 타일)와 로딩 스켈레톤(타일 형태 유지, #E5E3DE)도 포함
```

### 반복 개선 체크

1. 같은 크기 타일이 3개 연속 나란히 있나? → 하나를 2배로 키우거나 합친다
2. 타일 안이 아이콘+텍스트뿐인가? → 수치나 차트로 바꾼다
3. 액센트가 4곳 이상인가? → "지금/다음"만 남긴다
4. 그림자나 반경 값이 2종 이상인가? → 토큰으로 통일
5. 데스크톱 첫 화면에 빈 공간(구멍)이 있나? → `grid-template-areas`를 다시 짜서 빈칸 `.` 제거
6. 모바일에서 차트 높이가 무너졌나? → `min-height` 지정

```
[디자인 제약 — AI 기본값 금지]
- 보라/인디고 그라디언트, 글로우, 블롭 배경 금지
- Inter 단독 금지. Pretendard + Geist Mono만
- 같은 크기 카드 3개 나열 금지. 크기가 다른 타일로 비대칭 구성
- 그림자 금지, 반경 20px / 내부 12px만
- 이모지 금지, 아이콘은 SVG 또는 lucide-react
- 한국어 현실 데이터(km, 원, 날짜, D-day) 사용, 빈 상태·로딩 포함
```
