# Design System — Neo Brutalism (네오 브루탈리즘)

> 크림 바탕 위에 굵은 검정 테두리와 흐림 없는 오프셋 그림자를 얹은 스타일. Gumroad·Figma 커뮤니티 페이지·인디 SaaS 랜딩에서 퍼졌다. 손으로 오려 붙인 종이 판처럼 보이는 것이 핵심이다.
> 공통 규칙은 `anti-slop.md`를 함께 적용한다. 미리보기: `preview/neo-brutalism.html` (사내 동호회 모집 게시판 예시)

> **장식 제한 완화 안내** — 이 문서는 "트렌디 · 감성" 묶음이다. `anti-slop.md`의 "효과는 1종만, 액센트는 화면당 1곳" 규칙을 **이 문서 5절이 정한 범위 안에서만** 풀어 준다. 허용되는 것은 ①오프셋 그림자 ②원색 블록 3색 ③눌림 모션 ④배지 기울임(±2도) 네 가지뿐이다. 그 밖의 anti-slop 원칙(이모지 금지, 현실적인 한국어 더미 데이터, 아이콘 카드 3개 나열 금지, 빈 상태·로딩·에러 디자인)은 그대로 지킨다.

---

## 1. 분위기

문구점에서 산 색지를 가위로 잘라 검정 사인펜으로 테두리를 두르고, 살짝 어긋나게 겹쳐 붙인 게시판. **매끈하지 않음이 곧 친근함**이다. 둥근 모서리·흐린 그림자·그라디언트로 만든 "프리미엄" 느낌을 일부러 버리고, 모든 요소가 무엇인지(버튼인지, 카드인지, 입력칸인지) 테두리로 정직하게 드러낸다.

굵은 선과 단단한 그림자가 화면을 "무겁게" 만드는 대신, 색은 밝고 원색이다. 노랑·분홍·하늘 세 가지 색 블록이 크림 바탕 위에서 리듬을 만든다. 제목은 두껍고 크며, 행간이 좁다. 그래서 무겁지만 경쾌하다.

**어울리는 곳**
- 사내 동호회 모집, 플리마켓·바자회, 해커톤·아이디어 공모, 사내 라디오·뉴스레터 아카이브
- 참여를 부르는 이벤트 페이지(신청 버튼이 화면의 주인공인 곳)
- 짧은 기간 열리는 캠페인 마이크로사이트

**쓰면 안 되는 곳**
- GMP 문서, 품질 일탈 보고, 결재 화면, 인사 평가 — 진지함과 신뢰가 먼저인 업무 화면
- 표가 빽빽한 대시보드(테두리 2.5px이 셀마다 붙으면 숫자가 안 읽힌다)
- 외부 고객·의료진에게 보이는 회사 공식 페이지(브랜드 가이드 우선)
- 슬픈 공지(부고·사고 안내). 이 스타일은 명랑함을 감출 수 없다

**핵심 특징**
- 크림 바탕(`#FFF6E5`) + 먹 검정(`#111111`) 테두리 2.5px(작은 요소 2px)
- 흐림 0의 오프셋 그림자 `4px 4px 0 #111` — 깊이를 표현하는 유일한 수단
- 원색 블록 3색(노랑·분홍·하늘) + 성공 초록 1색. 블록 위 글자는 **항상 검정**
- 버튼은 누르면 그림자가 사라지고 그만큼 내려앉는다
- 반경은 0 또는 6px 두 가지만. 알약형(999px)은 칩에만
- 제목은 Pretendard 900, 숫자·영문 라벨은 Space Grotesk 700

---

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

### Light (기본)

```css
:root {
  /* 표면 */
  --bg:        #FFF6E5;  /* 크림 페이지 바탕 */
  --surface:   #FFFFFF;  /* 카드·입력칸 기본 */
  --surface-2: #F3E9D2;  /* 가라앉은 칸, 진행바 트랙, 비활성 */

  /* 선·글자 */
  --ink:       #111111;  /* 테두리, 그림자, 본문, 주 버튼 배경 */
  --muted:     #4A4740;  /* 보조 글자 (크림 위 8.6:1) */
  --faint:     #6B6558;  /* 메타·축 눈금 (크림 위 5.4:1) */

  /* 원색 블록 — 위에는 반드시 --ink 글자 */
  --yellow:    #FFD23F;  /* 주 강조: 주 버튼 호버, 모집 마감 임박, 선택된 필터 */
  --pink:      #FF7AA8;  /* 카테고리 블록, 하이라이트 */
  --sky:       #7CC6FE;  /* 카테고리 블록, 정보 */
  --green:     #5FD38D;  /* 성공·가입 완료 전용 */
  --red:       #C62D32;  /* 에러 테두리·글자 전용 (블록 배경으로 쓰지 않음) */

  /* 효과 */
  --shadow:    4px 4px 0 var(--ink);
  --shadow-sm: 2px 2px 0 var(--ink);
  --shadow-lg: 6px 6px 0 var(--ink);
  --border:    2.5px solid var(--ink);
  --border-sm: 2px solid var(--ink);
}
```

### Dark

다크 모드에서는 **테두리와 그림자가 크림색**으로 뒤집힌다. 검정 바탕 위 검정 그림자는 안 보이기 때문이다. 원색 블록은 그대로 두고 블록 위 글자는 여전히 `#111`.

```css
:root[data-theme="dark"] {
  --bg:        #17150F;
  --surface:   #24211A;
  --surface-2: #322E25;
  --ink:       #F5EEDC;  /* 테두리·그림자·본문이 크림으로 반전 */
  --muted:     #CFC6B2;
  --faint:     #A39A86;
  --yellow:    #FFD23F;
  --pink:      #FF7AA8;
  --sky:       #7CC6FE;
  --green:     #5FD38D;
  --red:       #FF6B6F;
  --on-block:  #111111;  /* 다크에서도 블록 위 글자는 검정 고정 */
}
```

> 다크에서는 `color: var(--ink)` 를 블록 위에 그대로 쓰면 크림 글자가 노랑 위에 얹혀 대비가 1.3:1로 무너진다. 블록 안 글자는 `--on-block`(라이트에서도 `#111`)을 따로 쓴다.

### 역할 규칙

| 토큰 | 쓰는 곳 | 쓰지 않는 곳 |
|---|---|---|
| `--ink` | 모든 테두리·그림자, 본문, 주 버튼 배경 1개 | 넓은 영역 배경(검정 섹션 2개 이상 금지) |
| `--yellow` | 주 버튼 호버, 선택된 필터 칩, "마감 임박" 배지, 히어로 블록 1개, 세 번째 카테고리 머리띠 | 경고 아이콘 색(블록으로만 쓴다) |
| `--pink` `--sky` | 카테고리 구분 블록, 카드 머리띠, 진행바 채움 | 글자색(크림 위 대비 부족) |
| `--green` | 가입 완료·성공 토스트 | 장식용 블록 |
| `--red` | 입력 에러 테두리·문구 | 블록 배경, 그림자 |

**한 화면 색 블록 상한**: 원색 블록 면적이 화면의 **35%**를 넘지 않는다. 카드 20장에 모두 색을 칠하지 말고, 카드 몸통은 흰색, 머리띠(상단 40px)에만 카테고리 색을 둔다.

---

## 3. 타이포그래피

| 역할 | 서체 |
|---|---|
| 한글 제목·본문 | **Pretendard Variable** (`cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css`) |
| 숫자·영문 라벨·배지 | **Space Grotesk** 500/700 (Google Fonts) |

```css
--font-sans: "Pretendard Variable", Pretendard, -apple-system, sans-serif;
--font-grot: "Space Grotesk", "Pretendard Variable", sans-serif;
.num { font-family: var(--font-grot); font-variant-numeric: tabular-nums; }
```

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

### 스케일 (6단계)

| 토큰 | 크기 / 굵기 / 행간 / 자간 | 용도 |
|---|---|---|
| `display` | `clamp(44px, 5vw, 64px)` / 900 / 1.02 / -0.04em | 페이지 히어로 제목 1개 |
| `h2` | 28px / 900 / 1.15 / -0.03em | 섹션 제목 |
| `h3` | 20px / 800 / 1.25 / -0.02em | 카드 제목 |
| `body` | 15px / 500 / 1.6 | 설명 |
| `label` | 13px / 700 / 1.3 | 버튼, 필터, 입력 라벨 |
| `micro` | 12px / 700 / 1.3 / 0.02em, Space Grotesk | 배지, 메타, 인원 수 |

원칙: 제목은 **두껍고 촘촘하게**. 900 굵기는 `display`·`h2`에만. 본문은 500으로 두어 굵은 제목과 대비를 만든다. 밑줄 강조는 `text-decoration`이 아니라 노란 형광펜 블록(`background: linear-gradient(transparent 58%, var(--yellow) 58%)`)으로 — 이것도 화면당 1곳.

---

## 4. 컴포넌트

### 버튼

```css
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  height: 44px; padding: 0 20px;
  font: 700 14px/1 var(--font-sans);
  color: var(--on-block, #111); background: var(--surface);
  border: var(--border); border-radius: 6px;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: transform .12s ease-out, box-shadow .12s ease-out, background .12s;
}
.btn:hover  { background: var(--yellow); }
.btn:active { transform: translate(4px, 4px); box-shadow: 0 0 0 var(--ink); }  /* 눌림 */
.btn:focus-visible { outline: 3px solid var(--sky); outline-offset: 3px; }

.btn--primary { background: var(--ink); color: var(--bg); }
.btn--primary:hover { background: var(--ink); box-shadow: var(--shadow-lg); transform: translate(-2px,-2px); }
.btn--primary:active { transform: translate(4px, 4px); box-shadow: 0 0 0 var(--ink); }

.btn[disabled] { background: var(--surface-2); color: var(--faint); box-shadow: none;
                 border-style: dashed; cursor: not-allowed; }
```

눌림 원칙: `translate` 거리 = 그림자 오프셋. 4px 그림자면 4px 내려앉는다. 거리가 다르면 버튼이 "튄다".

### 카드

```css
.card {
  background: var(--surface);
  border: var(--border); border-radius: 6px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.card-band {               /* 상단 카테고리 머리띠 */
  height: 40px; padding: 0 14px;
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: var(--border);
  background: var(--sky); color: #111;
}
.card-body { padding: 16px 16px 18px; }
a.card:hover { transform: translate(-2px,-2px); box-shadow: var(--shadow-lg); }
```

카드 안 진행바:

```css
.meter { height: 16px; border: var(--border-sm); border-radius: 0;
         background: var(--surface-2); overflow: hidden; }
.meter i { display: block; height: 100%; background: var(--pink);
           border-right: var(--border-sm); }
.meter i[data-full] { background: var(--yellow); }  /* 마감 임박 80% 이상 */
```

### 배지 · 칩

```css
.badge {
  display: inline-block; padding: 4px 10px;
  font: 700 12px/1.2 var(--font-grot);
  background: var(--yellow); color: #111;
  border: var(--border-sm); border-radius: 0;
  transform: rotate(-2deg);        /* 기울임은 ±2도, 배지에만 */
}
.chip {
  height: 36px; padding: 0 14px; border-radius: 999px;
  border: var(--border-sm); background: var(--surface);
  font: 700 13px/1 var(--font-sans);
}
.chip[aria-pressed="true"] { background: var(--yellow); box-shadow: var(--shadow-sm); }
```

### 입력

```css
.field { display: grid; gap: 6px; }
.field label { font: 700 13px/1.3 var(--font-sans); }
.input {
  height: 44px; padding: 0 12px;
  border: var(--border); border-radius: 0;
  background: var(--surface); color: var(--ink);
  font: 500 15px/1 var(--font-sans);
}
.input:focus { outline: none; box-shadow: var(--shadow); background: #FFFDF5; }
.input[aria-invalid="true"] { border-color: var(--red); box-shadow: 4px 4px 0 var(--red); }
.field .err { font-size: 13px; font-weight: 700; color: var(--red); }
```

### 내비게이션

```css
.nav {
  height: 64px; padding: 0 20px;
  display: flex; align-items: center; justify-content: space-between;
  background: var(--surface); border-bottom: var(--border);
}
.nav a { font-weight: 700; padding: 8px 12px; border: 2px solid transparent; }
.nav a[aria-current="page"] { border-color: var(--ink); background: var(--yellow); box-shadow: var(--shadow-sm); }
```

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

- **빈 상태**: 점선 테두리(`2.5px dashed`) 카드 + 굵은 한 줄("아직 모집 중인 동호회가 없어요") + 주 버튼 1개("동호회 만들기")
- **로딩**: 카드 모양 그대로 `--surface-2` 채움, 그림자 유지. 반짝이 애니메이션 대신 1.2s 주기 명도 펄스(0.6~1)
- **에러**: 빨간 테두리 박스 + 원인 한 줄 + "다시 시도" 버튼. 블록 배경을 빨강으로 칠하지 않는다

---

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

| 효과 | 정확한 CSS | 상한 |
|---|---|---|
| 오프셋 그림자 | `box-shadow: 4px 4px 0 var(--ink)` (작은 요소 2px, 강조 6px) | 3단계만. blur·spread 값은 항상 0 |
| 테두리 | `2.5px solid var(--ink)` (작은 요소 2px) | 두 굵기만. 1px 회색 선 금지 |
| 원색 블록 | `background: var(--yellow \| --pink \| --sky)` + `color:#111` | 화면 면적 35% 이하, 한 카드에 1색 |
| 기울임 | `transform: rotate(-2deg)` 또는 `rotate(1.5deg)` | 배지·스티커만, 화면에 4개 이하 |
| 눌림 | `:active { transform: translate(4px,4px); box-shadow: 0 0 0 var(--ink) }` | 버튼·클릭 카드만 |
| 형광펜 | `linear-gradient(transparent 58%, var(--yellow) 58%)` | 화면당 1곳 |

**금지**: `blur()`, 그라디언트 배경(형광펜 제외), 글로우, 반투명 유리, 노이즈 텍스처, 3D 회전.

---

## 6. 레이아웃

### 간격·반경 토큰

| 토큰 | 값 |
|---|---|
| `--gap` | 20px (카드 사이). 그림자 4px이 다음 카드에 닿지 않도록 최소 16px |
| `--pad` | 16~20px (카드 안쪽) |
| `--r` | 6px (버튼·카드) / 0 (입력·배지·진행바) / 999px (칩만) |
| 페이지 폭 | 최대 1200px, 좌우 여백 24px |

### 구성

- 12열 그리드. 히어로는 **좌 7열 텍스트 + 우 5열 색 블록**의 비대칭. 가운데 정렬 히어로 금지
- 목록은 3열 카드 그리드지만, 첫 카드(추천·마감 임박)는 2열을 차지해 크기 위계를 만든다
- 사이드바(필터)를 쓰면 왼쪽 3열, 굵은 테두리 박스 하나로 감싼다
- 섹션 사이 간격 48px. 섹션 구분은 선이 아니라 **색 띠**(`--surface-2`, 상하 테두리)

```css
.wrap { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
.grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 20px; }
.grid .feature { grid-column: span 2; }
@media (max-width: 1024px) { .grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 640px)  { .grid { grid-template-columns: minmax(0,1fr); } .grid .feature { grid-column: auto; }
                             :root { --shadow: 3px 3px 0 var(--ink); } }
```

모바일에서 그림자는 3px로 줄인다(작은 화면에선 4px도 무겁다). 테두리 굵기는 유지.

---

## 7. 모션

| 상황 | 값 |
|---|---|
| 버튼 눌림 | 120ms `ease-out` (transform + box-shadow) |
| 카드 호버 들림 | 180ms `ease-out`, `translate(-2px,-2px)` + 그림자 6px |
| 필터 전환 | 카드 페이드 200ms, 이동 없음 |
| 진행바 채움 | 처음 보일 때 400ms `cubic-bezier(.2,.8,.2,1)` 0 → 값 |
| 토스트 | 아래에서 12px 올라오며 250ms, 3초 뒤 사라짐 |

```css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
  .btn:active { transform: none; }   /* 눌림은 색 변화로만 */
}
```

튕기는(bounce) 이징·흔들기(shake)는 쓰지 않는다. 브루탈리즘의 움직임은 **딱딱하고 짧다**.

---

## 8. 접근성

원색 위 글자는 **검정 고정**. 흰 글자는 원색 위에서 전부 기준 미달이다.

| 배경 | 글자 | 대비 | 판정 |
|---|---|---|---|
| `#FFF6E5` 크림 | `#111111` | 17.6:1 | 통과 |
| `#FFF6E5` 크림 | `#4A4740` | 8.6:1 | 통과 |
| `#FFF6E5` 크림 | `#6B6558` | 5.4:1 | 통과 (트랙 `#F3E9D2` 위 4.8:1) |
| `#FFD23F` 노랑 | `#111111` | 13.1:1 | 통과 |
| `#FF7AA8` 분홍 | `#111111` | 7.7:1 | 통과 |
| `#7CC6FE` 하늘 | `#111111` | 10.2:1 | 통과 |
| `#5FD38D` 초록 | `#111111` | 10.1:1 | 통과 |
| `#111111` 먹 | `#FFF6E5` | 17.6:1 | 통과 (주 버튼) |
| `#FF7AA8` 분홍 | `#FFFFFF` | 2.4:1 | **금지** |
| `#7CC6FE` 하늘 | `#FFFFFF` | 1.9:1 | **금지** |
| `#FFFFFF` 흰 | `#C62D32` 에러 | 5.5:1 | 통과 (에러 문구) |
| `#FFF6E5` 크림 | `#FF7AA8` | 2.3:1 | **금지** (분홍 글자) |

- 카테고리는 색만으로 구분하지 않는다. 머리띠에 **카테고리 이름 텍스트**를 반드시 쓴다
- 포커스 링은 `3px solid var(--sky)` + 3px 오프셋. 검정 테두리와 겹쳐도 보인다
- 터치 타깃 44px(버튼·입력), 칩 36px 이상
- 기울인 배지 안 글자는 12px 이상. 기울임이 작은 글자를 더 흐리게 만든다

---

## 9. 하지 말 것

- 테두리 없는 오프셋 그림자 — 그림자만 있으면 그냥 "어긋난 카드"다. 테두리와 그림자는 한 쌍
- `box-shadow: 4px 4px 8px` 처럼 blur 섞기. 0이 아니면 이 스타일이 아니다
- 원색 위에 흰 글자, 크림 위에 분홍·하늘 글자
- 모든 카드를 원색으로 칠하기(면적 35% 초과). 흰 몸통 + 색 머리띠가 기본
- 반경 12·16·24px 섞기. 6px / 0 / 999px(칩) 세 가지뿐
- 기울어진 카드·기울어진 제목. 기울임은 작은 배지 한정
- 인디고·보라 그라디언트, 글로우, 유리 효과
- 아이콘+제목+설명 카드 3개 나열. 실제 동호회 카드(인원·일정·진행바)로 채운다
- 이모지. 아이콘은 lucide 인라인 SVG, 선 굵기 2.5로 테두리와 맞춘다
- "OO 동호회 1", "회원 999명" 같은 가짜 데이터

---

## 10. 셀프 체크리스트

- [ ] 모든 그림자의 blur 값이 0인가?
- [ ] 그림자가 있는 요소에 검정 테두리가 함께 있나?
- [ ] 버튼 눌림 거리가 그림자 오프셋과 같은가?
- [ ] 원색 블록 위 글자가 전부 `#111`인가? (다크 모드 포함)
- [ ] 원색 면적이 화면의 35% 이하인가?
- [ ] 반경 값이 6px / 0 / 999px 외에 섞이지 않았나?
- [ ] 기울어진 요소가 4개 이하, 전부 배지인가?
- [ ] 빈 상태·로딩·에러 화면이 있는가?
- [ ] 이름·팀·날짜·인원이 현실적인 한국어 데이터인가?
- [ ] `prefers-reduced-motion`에서 눌림·들림이 멈추는가?

### 복사용 프롬프트

```
네오 브루탈리즘 스타일로 사내 동호회 모집 게시판을 만들어줘. designs/neo-brutalism.md 를 따른다.
- 크림 바탕 #FFF6E5, 테두리 2.5px solid #111, 그림자 4px 4px 0 #111 (blur 0)
- 원색 블록 노랑 #FFD23F · 분홍 #FF7AA8 · 하늘 #7CC6FE, 블록 위 글자는 항상 #111
- 버튼은 :active 에서 translate(4px,4px) + 그림자 0 으로 내려앉게
- 반경은 6px / 0 / 칩만 999px. 기울임은 배지에만 ±2도, 4개 이하
- 제목 Pretendard 900, 숫자·배지 Space Grotesk 700
- 동호회 카드: 카테고리 색 머리띠 + 모집 인원 진행바 + 정모 일정 + 가입 신청 버튼
- 카테고리 필터 칩, 빈 상태·로딩·에러 포함, 이모지 금지, prefers-reduced-motion 처리
```
