# Design System — Dopamine Pop (도파민 팝)

> 채도 높은 핫핑크·라임·일렉트릭 블루를 크게 칠하고, 둥글고 굵은 글자와 기울어진 스티커 배지로 "보기만 해도 기분 좋아지는" 화면을 만드는 스타일. 도파민 드레싱 패션, Spotify Wrapped, Duolingo 캠페인 페이지 계열이다.
> 공통 규칙은 `anti-slop.md`를 함께 적용한다. 미리보기: `preview/dopamine-pop.html` (사내 걷기 챌린지 예시)

> **장식 제한 완화 안내** — 이 문서는 "트렌디 · 감성" 묶음이다. `anti-slop.md`의 "효과는 1종만, 액센트는 화면당 1곳" 규칙을 **이 문서 5절이 정한 범위 안에서만** 풀어 준다. 허용되는 것은 ①원색 대형 블록 3색 ②스티커 배지(흰 다이컷 테두리 + 기울임) ③큰 반경 ④통통 튀는 등장·숫자 모션 ⑤기하 도형 장식 3개까지, 다섯 가지뿐이다. 그 밖의 anti-slop 원칙(이모지 금지, 현실적인 한국어 더미 데이터, 아이콘 카드 3개 나열 금지, 빈 상태·로딩·에러 디자인)은 그대로 지킨다. 특히 **보라 그라디언트·글로우·블롭은 여기서도 금지**다. 이 스타일의 화려함은 평평한 원색에서 나온다.

---

## 1. 분위기

형광 운동화, 사탕 포장지, 놀이공원 입장권. 색끼리 부딪히는 소리가 들릴 만큼 **채도를 끝까지 올린** 평면 색 블록이 화면을 큰 덩어리로 나눈다. 블록은 모서리가 아주 둥글고(28px), 그 위에 숫자가 커다랗게 앉는다. 오늘 걸음 수, 남은 일수, 우리 팀 순위처럼 **자랑하고 싶은 숫자 하나**가 화면의 주인공이다.

스티커 배지는 노트북에 붙인 굿즈처럼 살짝 비뚤게 붙는다. 흰 다이컷 테두리가 스티커를 블록에서 떼어 낸다. 기울기, 색 충돌, 커다란 둥근 글자가 합쳐져 "해 보고 싶다"는 마음을 만든다. 대신 정보 구조는 단순해야 한다. 블록 하나에 메시지 하나.

**어울리는 곳**
- 걷기·금연·물 마시기 같은 건강 챌린지, 사내 캠페인(헌혈·플로깅·텀블러 쓰기)
- 연말 결산("올해 우리 팀이 함께 걸은 거리"), 입사 1주년 축하, 송년회 경품 이벤트
- 신입사원 온보딩 미션, 퀴즈 이벤트, 사내 투표 결과 발표

**쓰면 안 되는 곳**
- 업무 시스템 전반, 데이터 표·보고서, 결재·인사 평가
- 안전·보건 경고(채도 높은 핑크·블루가 경고 의미를 흐린다)
- 의약품 정보·의료진 대상 화면(신뢰·정확성이 먼저)
- 오래 읽어야 하는 글 페이지 — 채도가 높아 10분 넘게 보면 눈이 피곤하다

**핵심 특징**
- 연분홍 바탕(`#FFF3FA`) 위 핫핑크(`#FF2E93`)·라임(`#C6FF3D`)·일렉트릭 블루(`#2B4BFF`) 대형 블록
- 큰 반경: 블록 28px, 카드 20px, 버튼·칩 알약형 999px
- 한글 제목 **Jua**(둥근 굵은 글자), 숫자·영문 **Fredoka** 700, 본문 Pretendard
- 스티커 배지: 흰 3px 테두리 + `rotate(-6deg ~ 8deg)` + 흐림 없는 그림자
- 그림자는 "바닥에 붙은 두께" 하나뿐 — `0 6px 0 rgba(26,18,48,.16)`
- 블록 글자: 핑크·라임 위는 먹색, 블루 위만 흰색

---

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

### Light (기본)

```css
:root {
  /* 표면 */
  --bg:        #FFF3FA;  /* 연분홍 페이지 바탕 */
  --surface:   #FFFFFF;  /* 카드 */
  --surface-2: #FFE3F1;  /* 트랙, 잠긴 배지, 칩 기본 */

  /* 글자 */
  --ink:       #1A1230;  /* 본문, 핑크·라임 위 글자 */
  --muted:     #5B4F70;  /* 보조 (바탕 위 7.0:1) */
  --faint:     #716585;  /* 메타·축 (바탕 위 5.0:1) */

  /* 팝 3색 — 블록용 */
  --pink:      #FF2E93;  /* 히어로 블록, 주 버튼 */
  --lime:      #C6FF3D;  /* 순위·성공·"오늘 달성" */
  --blue:      #2B4BFF;  /* 차트, 정보 블록 (위 글자 흰색) */
  --sun:       #FFE14D;  /* 스티커 전용 4번째 색 (블록 금지) */

  /* 고정 글자색 */
  --on-pink:   #1A1230;
  --on-lime:   #1A1230;
  --on-blue:   #FFFFFF;

  /* 의미 */
  --danger:    #D1004F;  /* 에러 문구 (흰 위 5.5:1) */

  /* 효과 */
  --lift:      0 6px 0 rgba(26, 18, 48, .16);   /* 블록·카드 두께 */
  --lift-sm:   0 3px 0 rgba(26, 18, 48, .18);   /* 스티커 */
  --r-block:   28px;
  --r-card:    20px;
  --r-field:   16px;
  --r-pill:    999px;
}
```

### Dark

밤 놀이공원. 바탕은 아주 어두운 남보라 먹색, 블록 색은 거의 그대로 두되 핑크만 한 단계 밝게 올린다. 블루 블록은 다크에서도 흰 글자.

```css
:root[data-theme="dark"] {
  --bg:        #130F24;
  --surface:   #1F1938;
  --surface-2: #2A2248;
  --ink:       #F4EEFF;  /* 본문 (바탕 위 16.5:1) */
  --muted:     #B9AECF;  /* 카드 위 8.0:1 */
  --faint:     #8F84A8;  /* 카드 위 4.8:1 */
  --pink:      #FF4FA6;
  --lime:      #C6FF3D;
  --blue:      #2B4BFF;
  --sun:       #FFE14D;
  --on-pink:   #130F24;
  --on-lime:   #130F24;
  --on-blue:   #FFFFFF;
  --danger:    #FF6B9A;
  --lift:      0 6px 0 rgba(0, 0, 0, .45);
  --lift-sm:   0 3px 0 rgba(0, 0, 0, .5);
}
```

### 역할 규칙

| 토큰 | 쓰는 곳 | 쓰지 않는 곳 |
|---|---|---|
| `--pink` | 히어로 블록 1개, 주 버튼, 오늘 수치 강조 | 본문 글자색(바탕 위 3.2:1), 에러 |
| `--lime` | 순위·랭킹 블록, 목표 달성 표시, 성공 토스트 | 흰 바탕 위 얇은 선(거의 안 보인다) |
| `--blue` | 차트 막대, 정보 블록, 링크 | 넓은 배경 2곳 이상 |
| `--sun` | 스티커 배지 한정 | 블록·버튼 배경 |
| `--ink` | 본문, 보조 버튼, 테두리가 필요할 때(2px) | — |

**한 화면 블록 상한**: 대형 색 블록은 **3개까지**, 각 색 하나씩. 같은 색 블록이 두 번 나오면 둘 중 하나를 흰 카드로 바꾼다. 색 블록 면적은 화면의 **50% 이하**(나머지는 흰 카드와 바탕).

---

## 3. 타이포그래피

| 역할 | 서체 |
|---|---|
| 한글 제목·큰 문구 | **Jua** (Google Fonts, 둥글고 굵은 한 굵기) |
| 숫자·영문 | **Fredoka** 600/700 (Google Fonts, 둥근 기하 산세리프) |
| 본문·UI | **Pretendard Variable** (`cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css`) |

```html
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@500;600;700&family=Jua&display=swap" rel="stylesheet">
```

```css
--font-display: "Jua", "Pretendard Variable", sans-serif;
--font-num:     "Fredoka", "Pretendard Variable", sans-serif;
--font-sans:    "Pretendard Variable", Pretendard, -apple-system, sans-serif;
.num { font-family: var(--font-num); font-weight: 700; font-variant-numeric: tabular-nums; }
```

### 스케일 (6단계)

| 토큰 | 크기 / 굵기 / 행간 / 자간 | 용도 |
|---|---|---|
| `mega` | `clamp(88px, 10vw, 136px)` / Fredoka 700 / 0.9 / -0.04em | 히어로 숫자 1개 |
| `display` | `clamp(40px, 4.4vw, 56px)` / Jua / 1.1 / -0.02em | 페이지 제목 |
| `h2` | 30px / Jua / 1.15 | 블록 제목 |
| `h3` | 20px / Pretendard 800 / 1.3 | 카드 제목, 목록 이름 |
| `body` | 15px / Pretendard 500 / 1.6 | 설명 |
| `label` | 13px / Pretendard 700 / 1.3 | 버튼, 칩, 메타 |

원칙: **숫자가 가장 크다.** 제목(Jua)은 숫자보다 작게, 본문은 조용하게. Jua는 굵기가 하나뿐이라 본문에 쓰면 안 읽힌다 — 18px 미만에는 쓰지 않는다. 단위(`보`, `km`, `위`)는 숫자의 30% 크기, Pretendard 700으로.

---

## 4. 컴포넌트

### 버튼

```css
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 52px; padding: 0 26px;
  font: 800 16px/1 var(--font-sans);
  border: 0; border-radius: var(--r-pill);
  background: var(--pink); color: var(--on-pink);
  box-shadow: var(--lift);
  cursor: pointer;
  transition: transform .18s cubic-bezier(.34,1.56,.64,1), box-shadow .18s;
}
.btn:hover  { transform: translateY(-2px) scale(1.03); }
.btn:active { transform: translateY(4px) scale(.98); box-shadow: 0 2px 0 rgba(26,18,48,.16); }
.btn:focus-visible { outline: 3px solid var(--blue); outline-offset: 3px; }

.btn--blue  { background: var(--blue); color: var(--on-blue); }
.btn--ghost { background: var(--surface); color: var(--ink); box-shadow: inset 0 0 0 2px var(--ink); }
.btn[disabled] { background: var(--surface-2); color: var(--faint); box-shadow: none; transform: none; }
```

### 블록 · 카드

```css
.block {                         /* 대형 색 블록 — 화면에 최대 3개 */
  border-radius: var(--r-block);
  padding: 28px;
  box-shadow: var(--lift);
  position: relative; overflow: hidden;
}
.block--pink { background: var(--pink); color: var(--on-pink); }
.block--lime { background: var(--lime); color: var(--on-lime); }
.block--blue { background: var(--blue); color: var(--on-blue); }

.card {                          /* 흰 카드 — 블록 사이 쉼표 */
  background: var(--surface); border-radius: var(--r-card);
  padding: 22px; box-shadow: var(--lift);
}
```

블록 안 내부 박스는 동심 반경: 블록 28px − 안쪽 여백 12px ≈ **16px**. 내부 박스는 흰색 또는 `rgba(255,255,255,.55)` 하나.

### 스티커 배지

```css
.sticker {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px;
  font: 700 14px/1 var(--font-num);
  background: var(--sun); color: var(--ink);
  border: 3px solid #FFFFFF;           /* 흰 다이컷 테두리 */
  border-radius: var(--r-pill);
  box-shadow: var(--lift-sm);
  transform: rotate(-6deg);
}
.sticker--tilt-r { transform: rotate(7deg); }
.sticker--lime   { background: var(--lime); }
.sticker--pink   { background: var(--pink); color: var(--on-pink); }
```

원형 배지(배지 모음용):

```css
.medal { width: 88px; height: 88px; border-radius: 50%;
         display: grid; place-items: center;
         border: 4px solid #FFFFFF; box-shadow: var(--lift-sm); }
.medal[data-locked] { background: var(--surface-2); color: var(--muted);
                      border-style: dashed; border-color: var(--faint); box-shadow: none; }
```

### 칩 · 입력

```css
.chip { height: 40px; padding: 0 18px; border-radius: var(--r-pill);
        background: var(--surface-2); color: var(--ink);
        font: 700 14px/1 var(--font-sans); border: 0; }
.chip[aria-pressed="true"] { background: var(--ink); color: var(--bg); }

.input { height: 52px; padding: 0 18px; border-radius: var(--r-field);
         background: var(--surface); color: var(--ink);
         border: 2px solid var(--surface-2); font: 500 16px/1 var(--font-sans); }
.input:focus { outline: none; border-color: var(--blue); box-shadow: 0 0 0 4px rgba(43,75,255,.18); }
.input[aria-invalid="true"] { border-color: var(--danger); }
```

### 내비게이션

```css
.nav { height: 72px; display: flex; align-items: center; justify-content: space-between; }
.nav-pill { display: flex; gap: 4px; padding: 6px; border-radius: var(--r-pill);
            background: var(--surface); box-shadow: var(--lift-sm); }
.nav-pill a { padding: 10px 18px; border-radius: var(--r-pill); font-weight: 700; }
.nav-pill a[aria-current="page"] { background: var(--pink); color: var(--on-pink); }
```

### 빈 상태 · 로딩 · 에러

- **빈 상태**: `--surface-2` 큰 둥근 박스 + 잠긴 원형 배지 하나 + Jua 한 줄("아직 오늘 기록이 없어요") + 주 버튼("걸음 수 연동하기")
- **로딩**: 블록 모양 그대로 `--surface-2`, 숫자 자리에 둥근 막대. 1.4s 명도 펄스
- **에러**: 흰 카드 + `--danger` 2px 테두리 + 원인 한 줄 + "다시 불러오기". 핑크 블록을 에러로 쓰지 않는다(핑크는 이 화면에서 "좋은 것"이다)

---

## 5. 효과 레시피 (허용 범위의 전부)

| 효과 | 정확한 CSS | 상한 |
|---|---|---|
| 대형 색 블록 | `background: var(--pink \| --lime \| --blue); border-radius: 28px` | 3개, 색마다 1개, 면적 50% 이하 |
| 두께 그림자 | `box-shadow: 0 6px 0 rgba(26,18,48,.16)` (스티커 `0 3px 0 …18`) | 2단계만. blur 0 |
| 스티커 | `border: 3px solid #fff; transform: rotate(-6deg)` | 화면에 5개 이하, 각도 −8~8도, 이웃끼리 반대 방향 |
| 기하 장식 | 블록 모서리에 인라인 SVG 원·별·물결선(블록 색과 다른 팝 색, 불투명) | 화면에 3개, 글자 뒤에 겹치지 않게 |
| 통통 모션 | `cubic-bezier(.34,1.56,.64,1)` (오버슈트) | 버튼 호버, 숫자 등장, 배지 획득만 |
| 내부 반투명 박스 | `background: rgba(255,255,255,.55)` | 블록 안에서만 |

**금지**: 그라디언트(특히 핑크→보라), `blur()`·글로우, 반투명 유리, 흐린 블롭, 무지개 텍스트, 네온 외곽선.

---

## 6. 레이아웃

### 간격·반경 토큰

| 토큰 | 값 |
|---|---|
| `--gap` | 20px (블록·카드 사이) |
| `--pad` | 28px (블록 안쪽), 22px (카드 안쪽), 모바일 20px |
| 반경 | 28 블록 / 20 카드 / 16 입력·내부 / 999 버튼·칩·스티커 |
| 페이지 폭 | 최대 1200px, 좌우 여백 24px |

### 구성

- 12열 그리드, **첫 줄은 히어로 블록(7열, 핑크) + 랭킹 블록(5열, 라임)**의 비대칭. 히어로 블록 안에 오늘 수치를 거대하게
- 둘째 줄은 흰 카드 위주로 쉬어 간다(차트 7열 + 배지 모음 5열). 블루 블록은 이 줄에 최대 1개
- 블록끼리 붙이지 말고 사이에 흰 카드를 끼워 색 충돌을 "의도한 곳"에만 만든다
- 가운데 정렬 히어로·같은 크기 카드 3개 나열 금지

```css
.wrap { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
.board { display: grid; gap: 20px; grid-template-columns: repeat(12, minmax(0,1fr)); }
.hero  { grid-column: span 7; }
.rank  { grid-column: span 5; }
.chart { grid-column: span 7; }
.medals{ grid-column: span 5; }
@media (max-width: 1024px) { .hero, .rank, .chart, .medals { grid-column: span 12; } }
@media (max-width: 640px)  { :root { --r-block: 24px; } .block { padding: 20px; } }
```

모바일에서는 블록 반경을 24px로 줄이고, 히어로 숫자는 `clamp` 최소 88px까지만 내려간다.

---

## 7. 모션

| 상황 | 값 |
|---|---|
| 버튼 호버 | 180ms 오버슈트 이징, `translateY(-2px) scale(1.03)` |
| 버튼 눌림 | 120ms, `translateY(4px) scale(.98)` + 그림자 2px로 |
| 히어로 숫자 등장 | 400ms, `scale(.85) → 1` 오버슈트 (숫자 카운트업은 하지 않아도 됨) |
| 블록 등장 | 320ms, 12px 상승 + 페이드, 60ms 순차 지연 |
| 배지 획득 | 360ms, `rotate(-20deg) scale(.6) → rotate(-6deg) scale(1)` |
| 진행바 채움 | 400ms `cubic-bezier(.2,.8,.2,1)` |

```css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
  .btn:hover, .btn:active { transform: none; }
}
```

스티커의 정적 기울임은 모션이 아니므로 `reduce`에서도 유지한다. 계속 흔들리는(무한 반복) 애니메이션은 쓰지 않는다.

---

## 8. 접근성

| 배경 | 글자 | 대비 | 판정 |
|---|---|---|---|
| `#FFF3FA` 바탕 | `#1A1230` | 16.6:1 | 통과 |
| `#FFF3FA` 바탕 | `#5B4F70` | 7.0:1 | 통과 |
| `#FFF3FA` 바탕 | `#716585` | 5.0:1 | 통과 (최소선) |
| `#FF2E93` 핑크 | `#1A1230` | 5.2:1 | 통과 |
| `#FF2E93` 핑크 | `#FFFFFF` | 3.5:1 | **24px 이상 굵은 글자만** (큰 숫자) |
| `#C6FF3D` 라임 | `#1A1230` | 15.2:1 | 통과 |
| `#2B4BFF` 블루 | `#FFFFFF` | 5.9:1 | 통과 |
| `#2B4BFF` 블루 | `#1A1230` | 3.0:1 | **금지** |
| `#FFE14D` 선 | `#1A1230` | 13.8:1 | 통과 (스티커) |
| `#FFFFFF` 흰 | `#D1004F` | 5.5:1 | 통과 (에러 문구) |
| `#FFF3FA` 바탕 | `#FF2E93` | 3.2:1 | **금지** (핑크 글자) |
| `#1F1938` 다크 카드 | `#B9AECF` | 8.0:1 | 통과 |
| `#FF4FA6` 다크 핑크 | `#130F24` | 6.2:1 | 통과 |

- 색만으로 순위·상태를 구분하지 않는다. 1·2·3위에는 숫자와 "위"를 쓰고, 달성 여부는 아이콘(체크)+문구로
- 스티커 안 글자 14px 이상. 기울어진 글자는 작으면 더 안 읽힌다
- 숫자 카운트업을 쓰면 `aria-live`에 최종 숫자만 한 번 알린다
- 터치 타깃: 버튼 52px, 칩 40px
- 채도 높은 화면을 오래 보는 사람(시각 과민)을 위해 다크 모드 토글을 제공한다

---

## 9. 하지 말 것

- 핑크→보라, 핑크→주황 그라디언트 배경. 팝은 평평한 색이다
- 글로우·네온·blur·유리 효과, 둥둥 떠다니는 블롭
- 블루 블록 위 먹색 글자, 바탕 위 핑크 글자
- 대형 블록 4개 이상, 같은 색 블록 2개
- 스티커를 같은 방향으로만 기울이기(전부 −6도면 화면이 쏠려 보인다)
- Jua로 본문·표·18px 미만 텍스트 쓰기
- 반경 섞기 — 28 / 20 / 16 / 999 외 값 금지
- 무한 반복 흔들림, 3초 넘는 애니메이션
- 아이콘+제목+설명 카드 3개 나열. 실제 숫자·순위·막대로 채운다
- 이모지(불꽃·트로피 이모지 금지 — lucide `flame`·`trophy` 인라인 SVG)
- "12,345보", "100% 달성", "홍길동" 같은 가짜 데이터

---

## 10. 셀프 체크리스트

- [ ] 대형 색 블록이 3개 이하, 색마다 하나인가?
- [ ] 블루 위 글자는 흰색, 핑크·라임 위 글자는 먹색인가?
- [ ] 화면에서 가장 큰 글자가 "자랑할 숫자"인가?
- [ ] 스티커가 5개 이하이고 이웃끼리 반대로 기울었나?
- [ ] 그라디언트·blur·글로우가 하나도 없나?
- [ ] 그림자 blur 값이 0인가?
- [ ] Jua가 18px 이상 제목에만 쓰였나?
- [ ] 빈 상태(기록 없음)·로딩·에러(연동 실패)가 있는가?
- [ ] 이름·팀·걸음 수·날짜가 현실적인가?
- [ ] `prefers-reduced-motion`에서 통통 모션이 멈추는가?

### 복사용 프롬프트

```
도파민 팝 스타일로 사내 걷기 챌린지 페이지를 만들어줘. designs/dopamine-pop.md 를 따른다.
- 바탕 #FFF3FA, 팝 3색 핫핑크 #FF2E93 · 라임 #C6FF3D · 블루 #2B4BFF, 스티커 전용 #FFE14D
- 핑크·라임 위 글자는 #1A1230, 블루 위 글자는 흰색
- 대형 블록 반경 28px, 카드 20px, 버튼·칩 999px. 그림자는 0 6px 0 rgba(26,18,48,.16) 하나
- 제목 Jua, 숫자 Fredoka 700(가장 큰 요소), 본문 Pretendard
- 첫 줄: 오늘 걸음 수 핑크 블록(7열) + 팀 순위 라임 블록(5열). 둘째 줄: 주간 막대 카드 + 배지 모음
- 스티커 배지는 흰 3px 테두리 + −8~8도 기울임, 5개 이하
- 그라디언트·글로우·블롭·이모지 금지. 빈 상태·로딩·에러 포함, prefers-reduced-motion 처리
```
