designpaca/packages/skill/references/elevation.md
Yun Chan 6805fb2be7 feat(skill): absorb external design skills, restore interview gate, add review route
- Restore the step-0 interview as a mechanical gate the skill explicitly
  depends on; add harness.md (per-harness question tools, limits,
  fallbacks) and brief-interview.md (slots, question cards, rounds).
- Add 10 reference docs absorbed from external design skills
  (accessibility, interaction-feel, elevation, color, icons, product-copy,
  component-systems, critique, change-review, print-email) and extend
  existing references.
- Add a review-only route and two hard-gate clauses (truncated content
  reachability, three-flashes limit).
- design-gate: split tap targets into WCAG 2.5.8 and 44px contract layers,
  run axe-core when available, and fix false positives found on a real
  site (decorative alt="", stacked wordmark line count, url-only pages).
- lint-skill: fail if the interview gate section or its links disappear.
- Ship agents/openai.yaml and THIRD_PARTY_NOTICES.md.
2026-09-24 13:26:03 +09:00

177 lines
13 KiB
Markdown

# elevation — 그림자·깊이·머티리얼
3단계에서 토큰을 정할 때, 4-2 재질에서 읽는다.
`tokens.md` §3-b(형태)는 radius·line-width만 정의하고 그림자 토큰 자리가 비어 있다. 이 문서가 그 자리를 채운다. `--dur-*`·`--ease-*`는 여기서 새로 만들지 않고 `tokens.md` §4에 정의된 값을 그대로 재사용한다.
## 이 문서를 읽는 법
| 상황 | 읽을 곳 |
|---|---|
| 그림자 토큰 3단만 빨리 정하면 된다 | §2 |
| radius와 그림자를 같이 정의해야 한다 | §3 |
| 테두리 대신 그림자를 쓸지 고민 중이다 | §4 |
| 다크 모드에서 그림자가 안 보인다 | §5 |
| 카드·리스트가 슬롭처럼 보인다 | §6 |
| 브리프가 유리·Apple풍 질감을 요구한다 | §7 |
| `prefers-reduced-transparency`·`forced-colors` 대응이 필요하다 | §8 |
| 커밋 전 확인 목록이 필요하다 | §9 |
---
## 0. 언제, tokens.md와의 관계
그림자·깊이 체계는 3단계에서 토큰과 함께 정하고, 4단계 구현에서 그대로 꺼내 쓴다. `tokens.md`는 타입·색·간격·형태·모션 토큰의 정본이고, 이 문서는 그중 형태(§3-b) 옆에 있어야 할 그림자·머티리얼 절만 담당한다. 토큰 이름은 `tokens.md`가 색에서 쓰는 역할 기반 명명 관례(`--surface`, `--surface-raised`)를 그대로 따라, "얼마나 떠 있는가"라는 역할로 짓는다. 팔레트식 이름(`--shadow-1`, `--shadow-2`)은 쓰지 않는다.
## 1. 원칙
아래 원칙은 **관찰 후보**다.
1. **광원은 하나로 통일한다.** 모든 그림자는 화면 위쪽에서 아래로 비추는 하나의 광원을 가정한다. `offset-y`가 음수인 그림자, 여러 방향에서 비추는 그림자를 같은 화면에 섞으면 표면이 물리적으로 말이 안 되게 읽힌다.
2. **깊이는 위계 정보다.** 그림자 강도는 장식이 아니라 "이 표면이 다른 표면보다 얼마나 위에 있는가"를 전달하는 신호다. 강도를 정보 없이 임의로 섞으면 신호가 사라진다.
3. **그림자 체계는 하나다.** 한 프로젝트 안에서 정의한 다층 그림자 토큰과 프레임워크 기본 그림자(`shadow-lg` 등)를 섞어 쓰지 않는다. 코너 반경·그림자 단계·아이콘 스트로크 굵기가 화면 전체에서 같은 체계로 파생됐는지는 감사 항목이다 [SKILL-DESIGN-REVIEW][SKILL-BEAUTIFUL-SHADOWS].
## 2. 다층 그림자 시작값 3단
아래 값은 **관찰 후보**다. 브리프·기존 토큰이 침묵할 때의 시작값이며, 렌더로 확인하고 조정한다. Tailwind 임의값 문법이 아니라 순수 CSS 커스텀 프로퍼티로 옮긴다 — designpaca는 프레임워크를 전제하지 않는다.
```css
:root {
/* 컴팩트 카드, 폼 컨트롤, 필(pill) — 조용한 표면 */
--shadow-resting:
0px 2px 3px -1px rgba(0, 0, 0, 0.1),
0px 1px 0px 0px rgba(25, 28, 33, 0.02),
0px 0px 0px 1px rgba(25, 28, 33, 0.08);
/* 카드, 패널, 팝오버 — 기본 elevated 표면 */
--shadow-raised:
0px 0px 0px 1px rgba(0, 0, 0, 0.06),
0px 1px 1px -0.5px rgba(0, 0, 0, 0.06),
0px 3px 3px -1.5px rgba(0, 0, 0, 0.06),
0px 6px 6px -3px rgba(0, 0, 0, 0.06),
0px 12px 12px -6px rgba(0, 0, 0, 0.06),
0px 24px 24px -12px rgba(0, 0, 0, 0.06);
/* 히어로 미디어, feature callout, 모달류 — 가장 강한 lift */
--shadow-overlay:
0 2.8px 2.2px rgba(0, 0, 0, 0.034),
0 6.7px 5.3px rgba(0, 0, 0, 0.048),
0 12.5px 10px rgba(0, 0, 0, 0.06),
0 22.3px 17.9px rgba(0, 0, 0, 0.072),
0 41.8px 33.4px rgba(0, 0, 0, 0.086),
0 100px 80px rgba(0, 0, 0, 0.12);
}
```
값과 레이어 구조의 출처: [SKILL-BEAUTIFUL-SHADOWS]. `--shadow-raised`는 링 1겹 뒤로 offset·blur·spread가 1→3px 다음부터 2배씩(3·6·12·24px) 커지는 레이어이고, `--shadow-overlay`는 레이어가 멀어질수록(더 흐려질수록) 알파가 0.034→0.12로 비선형으로 진해진다. **레이어가 늘수록 왜 값이 커지고 진해지는지에 대한 근거는 관찰 후보이며 원 출처가 확인되지 않았다** — 자연광 산란을 흉내 낸 설계로 보이지만 이 문장 자체를 근거로 인용하지 않는다.
### 밀도 → 단계 매핑
| 표면 | 토큰 | 밀도 |
|---|---|---|
| 폼 컨트롤, 배지, 필, 컴팩트 카드 | `--shadow-resting` | 조용함 |
| 카드, 패널, 팝오버, 드롭다운 | `--shadow-raised` | 기본 |
| 히어로 미디어, feature callout, 모달, 시트 | `--shadow-overlay` | 강함 |
인터랙션이 명확히 elevation을 바꾸는 경우(호버로 카드가 뜨는 등)가 아니면, 같은 컴포넌트의 한 상태에는 그림자 토큰을 하나만 쓴다 [SKILL-BEAUTIFUL-SHADOWS]. elevation이 상태에 따라 바뀔 때는 새 duration 토큰을 만들지 않고 `tokens.md`의 `--dur-instant`·`--ease-out`을 그대로 재사용해 전환한다.
## 3. radius·표면과 짝짓기
그림자 프리셋은 깨끗한 표면 채움(fill)과 일관된 radius를 항상 함께 정의한다 [SKILL-BEAUTIFUL-SHADOWS]. 같은 컴포넌트에서 그림자만 바꾸고 radius·표면색을 따로 관리하면 값이 어긋난다.
중첩된 표면의 radius는 동심원 관계로 계산하는 것이 시작 규칙이다.
```
외곽 radius = 내부 radius + 둘 사이 padding
```
패딩이 24px를 넘으면 동심 계산을 포기하고 두 표면을 독립된 radius로 다룬다 [SKILL-BETTER-UI]. radius 토큰 자체("서로 다른 radius 값은 3종 미만으로")는 [tokens.md](tokens.md) §3-b가 정본이다.
## 4. 테두리를 대신하는 그림자
**언제**: 카드·버튼·컨테이너의 깊이·elevation을 표현하는 테두리를 옅은 `box-shadow`로 대체할 때 쓴다. 구분선(`border-top`/`border-bottom`), 셀 경계, `selected`·`focus` 상태를 나타내는 테두리에는 적용하지 않는다 — 그 용도는 레이아웃 분리이지 깊이가 아니다 [SKILL-BETTER-UI].
그림자는 투명도를 쓰므로 색이 고정된 border와 달리 어떤 배경 위에서도 자연스럽게 섞인다. 이미지나 여러 배경색 위에 놓이는 표면일수록 이 대체가 유효하다.
```css
:root {
/* 라이트 모드 — 3겹: 1px 링 + 미세한 lift + ambient 깊이 */
--shadow-border:
0px 0px 0px 1px oklch(0 0 0 / 0.06),
0px 1px 2px -1px oklch(0 0 0 / 0.06),
0px 2px 4px 0px oklch(0 0 0 / 0.04);
--shadow-border-hover:
0px 0px 0px 1px oklch(0 0 0 / 0.08),
0px 1px 2px -1px oklch(0 0 0 / 0.08),
0px 2px 4px 0px oklch(0 0 0 / 0.06);
}
@media (prefers-color-scheme: dark) {
:root {
/* 다크 모드 — 레이어드 그림자는 어두운 배경에서 거의 안 보인다.
흰 링 1겹으로 단순화한다. */
--shadow-border: 0 0 0 1px oklch(1 0 0 / 0.08);
--shadow-border-hover: 0 0 0 1px oklch(1 0 0 / 0.13);
}
}
.card {
box-shadow: var(--shadow-border);
transition-property: box-shadow;
transition-duration: var(--dur-instant);
transition-timing-function: var(--ease-out);
}
.card:hover { box-shadow: var(--shadow-border-hover); }
```
값과 라이트 3겹·다크 1겹 구조의 출처: [SKILL-BETTER-UI]. `transition-duration`은 원문의 150ms를 그대로 새 값으로 두지 않고 `tokens.md`의 폐쇄형 duration 토큰(`--dur-instant` = 100ms)에 매핑했다 — 근거 없는 새 리터럴을 만들지 않는다는 원칙에 따른 **실측 조정**이다.
## 5. 다크모드의 깊이
다크 배경에서는 밝은 배경보다 그림자의 명도 대비가 약해 레이어드 그림자가 잘 읽히지 않는다(§4의 "다크 1겹 단순화"가 같은 이유다). 다크 테마의 elevation 위계는 그림자보다 **표면 밝기 차이**로 먼저 전달한다 — `--surface`, `--surface-raised` 같은 역할 토큰([tokens.md](tokens.md) §2)을 elevation 단계가 올라갈수록 조금씩 밝게 다시 정의하고, `--shadow-border`의 흰 링은 경계를 보조하는 역할로만 둔다. 순수 검정에 가까운 표면 위에 그림자만으로 3단 위계를 만들려 하지 않는다.
## 6. 오용 휴리스틱
아래는 모두 **관찰 후보**다. 발견 자체가 실패가 아니라, 브리프·실제 렌더로 유지·수정 여부를 판단하는 점검 대상이다.
| 지문 | 왜 문제 | 대신 |
|---|---|---|
| 정의한 다층 그림자 체계와 프레임워크 기본 그림자를 한 화면에 섞어 씀 | 위계 신호가 두 체계로 나뉘어 무엇이 무엇보다 위인지 읽히지 않는다 | §2의 3단 토큰 하나만 쓴다 [SKILL-BEAUTIFUL-SHADOWS] |
| 강한 색으로 그림자를 틴팅함(`rgba(139,92,246,.5)`류) | 깊이 신호가 브랜드 강조처럼 읽혀 의미가 겹친다 | 브랜드 토큰이 틴팅을 의도적으로 요구할 때만 예외로 둔다. 중립이 기본값이다 [SKILL-BEAUTIFUL-SHADOWS] |
| 밀집 리스트·그리드의 각 행에 `--shadow-overlay`급 강한 그림자를 반복 | 시각 노이즈가 쌓이고 리스트 스크롤에서 페인트 비용이 누적된다 | 밀집 목록에는 `--shadow-resting`이나 얇은 경계를 쓴다 [SKILL-BEAUTIFUL-SHADOWS] |
| 한 요소에 그림자 토큰 여러 개를 동시에 쌓음 | 어느 레이어가 진짜 위계 신호인지 불명확해진다 | 상태당 그림자 토큰 하나 원칙(§2)을 지킨다 [SKILL-BEAUTIFUL-SHADOWS] |
| 대비가 낮은 배경에서 명확한 경계선 대신 옅은 그림자만으로 표면을 구분 | 저대비 환경·확대·forced-colors에서 경계 자체가 사라질 수 있다 | 저대비 배경에는 `--line` 토큰의 실제 테두리를 함께 쓴다 [SKILL-BEAUTIFUL-SHADOWS] |
| 여러 카드가 위계와 무관하게 같은 radius·같은 흐린 회색 그림자를 반복(카드 키트 지문) | 콘텐츠가 다른데 표면 처리가 같으면 임의 템플릿으로 읽힌다 | radius·그림자 단계를 콘텐츠 밀도·중요도와 연결해 다시 정한다 [SKILL-FRONTEND-DESIGN] |
## 7. 머티리얼 위계
**언제**: 브리프나 프리셋이 유리·Apple풍 질감을 허용할 때만 연다. `swiss-minimal`처럼 재질을 배제하는 프리셋에는 적용하지 않는다([svg-filters.md](svg-filters.md) §1의 프리셋별 재질 표를 먼저 확인한다).
이 절은 반투명 표면의 깊이 위계를 다룬다. `backdrop-filter` 구현 자체(변위맵, Safari 폴백, 성능 최적화)와 위계·가독성(vibrancy)·스티키 헤더 마스크·진입 애니메이션의 상세 규칙·코드는 [svg-filters.md](svg-filters.md) "머티리얼 위계와 가독성" 절이 정본이다 — 여기서 중복 정의하지 않는다. 핵심만 요약하면, 무겁고 어두운(강한 블러+짙은 배경) 재질은 사이드바 같은 구조적 영역에, 가볍고 밝은 재질은 버튼 같은 인터랙티브 요소에 쓰고, 표면이 클수록 재질도 두껍게 읽혀야 한다 — **머티리얼 무게가 위계를 인코딩한다**는 것이 이 절의 한 줄 요약이다[SKILL-APPLE-DESIGN].
## 8. 선호 신호
| 미디어 쿼리 | 위계 | 대응 |
|---|---|---|
| `prefers-reduced-transparency: reduce` | **프로젝트 계약**(§7 적용 시) | 반투명 표면의 배경 불투명도를 높이고 블러를 낮춰 더 서리 낀 상태로 전환한다. 2026-09-24 기준 Chrome/Edge 119만 구현했고 Firefox·Safari는 미지원이라 점진적 향상으로 다룬다 — 미지원 브라우저에서는 아무 효과가 없을 뿐 오류가 나지 않는다 [WEB-BASELINE] |
| `prefers-contrast: more` | **프로젝트 계약** | 거의 불투명한 배경과 뚜렷한 테두리로 전환한다. 2022년 5월부터 주요 브라우저 전반에서 지원되는 안정 기능이다 [WEB-BASELINE] |
| `forced-colors: active` | **하드 게이트** | 강제 색상 모드에서는 `box-shadow`가 `none`으로 강제되어 사라진다(대체되지 않는다). `border` 등 색은 시스템 팔레트로 바뀐다. 그림자만으로 표시한 경계는 이 모드에서 사라지므로, §4의 `--shadow-border`로만 경계를 나타낸 표면에는 실제 `border`나 `outline`을 함께 정의해 forced-colors에서도 경계가 남게 한다. `forced-colors`는 2022년 9월부터 안정 지원된다 [WEB-BASELINE][WCAG-NONTEXT] |
```css
@media (prefers-reduced-transparency: reduce) {
.glass { background: var(--surface-raised); backdrop-filter: none; }
}
@media (prefers-contrast: more) {
.card { border: 1px solid var(--line); }
}
@media (forced-colors: active) {
.card { border: 1px solid CanvasText; box-shadow: none; }
}
```
## 9. 검증
- **두 테마**에서 그림자·표면 밝기가 elevation 단계를 실제로 구분해 보이는지 확인한다(§5).
- `backdrop-filter`를 쓴 표면에는 **최악의 배경 콘텐츠**(다색 이미지, 밝은 텍스트가 섞인 스크린샷)를 뒤에 두고 vibrancy 처리로 텍스트가 읽히는지 확인한다(§7).
- **밀집 목록·그리드**에서 그림자를 반복 적용했을 때 시각 노이즈나 스크롤 프레임 저하가 없는지 확인한다(§6).
- **200% 확대**와 `forced-colors`·필터를 전부 끈 상태([svg-filters.md](svg-filters.md) §7의 감사 주입 패턴)에서 표면 경계가 여전히 구분되는지 확인한다(§8).