- route table: extension route for products with an existing visual language, feedback row, derived state tokens stay local - completion: blocking 0 plus reported unverified; hard-gate surface is the touched scope; delivery report starts with what and how to open - design.md created at step 0 with a skeleton, fingerprint line and change log; product-surface rubric reading; direction delegation states the chosen card and reason - layout: hero families, nav forms, footer forms; art-direction page convergence 6 axes; genre sections; image treatments; placeholder conventions; multi-screen order; Tailwind theme mapping - platform: scroll-driven examples, anchor positioning, canvas OT, three/R3F pins, shadcn style prefixes, Safari 26 notes, subgrid - design-gate: four result states, exit codes, cwd module resolution, --url/--out-dir/--strict, oklch contrast, dead selector word match - Korean font budget split from the Latin baseline
759 lines
56 KiB
Markdown
759 lines
56 KiB
Markdown
# typography — 폰트를 고르고 싣는 법
|
|
|
|
3단계에서 폰트를 정할 때, 4-1에서 적용할 때 읽는다.
|
|
|
|
**타입 스케일과 간격은 `tokens.md` 가 다룬다.** 이 문서는 **어떤 글자를 쓰고 그것을 어떻게 화면에 올리는가**만 다룬다.
|
|
|
|
## 이 문서를 읽는 법
|
|
|
|
| 상황 | 읽을 곳 |
|
|
|---|---|
|
|
| 폰트를 아직 안 골랐다 | §1 선택 절차. 대부분 여기서 끝난다 |
|
|
| 웨이트를 두 개 이상 쓴다 | §2 가변 폰트 |
|
|
| 로딩이 느리거나 글자가 늦게 뜬다 | §3 로딩 |
|
|
| 폰트 로드 전후로 레이아웃이 흔들린다 | §4 폴백 메트릭. **가장 자주 빠뜨리는 곳이다** |
|
|
| 숫자가 흔들리거나 자간이 이상하다 | §5 OpenType |
|
|
| 한글이 들어간다 | §6. **조판 정본은 §6-5**(줄바꿈·행간·자간·강조·measure·혼식). 서체 후보는 §6-6 |
|
|
| 배포 전 | §7 라이선스, §8 자기 점검(납품 판정은 preflight.md P7·P8) |
|
|
| 헤딩 크기·자간·행간을 어떻게 조정할지 원칙이 필요하다 | §9 |
|
|
| 폰트 기능(`font-synthesis`, OpenType, 동적 숫자)을 더 쓰고 싶다 | §10 |
|
|
| 줄바꿈·위도우·justify 문제가 있다 | §11 |
|
|
| 밑줄이나 `text-box` 트림이 필요하다 | §12 |
|
|
| 텍스트가 잘려서 전체 값을 볼 수 없다 | §13. **하드 게이트** |
|
|
| 폰트 스무딩·선택 가능성·장식 텍스트 디테일이 필요하다 | §14 |
|
|
| 배포 직전 타이포 전용 빠른 점검이 필요하다 | §15 |
|
|
|
|
---
|
|
|
|
## 1. 고르는 절차
|
|
|
|
### 1-1. 먼저 개수를 정한다
|
|
|
|
디스플레이 하나와 본문 하나, 필요할 때 모노 하나는 관리하기 쉬운 시작점이다. 추가 패밀리는 언어 범위·역할·전송량·라이선스와 실제 위계 효과를 근거로 제한한다. 가변 폰트가 정적 파일보다 항상 작거나 모든 필요한 축을 제공하는 것은 아니므로 실제 subset과 로딩 결과를 비교한다.
|
|
|
|
### 1-2. 성격을 브리프에서 끌어낸다
|
|
|
|
폰트를 고르기 전에 **한 줄로 답한다**: 이 글자는 무엇처럼 보여야 하는가.
|
|
|
|
| 브리프가 요구하는 것 | 글자에서 찾을 성질 |
|
|
|---|---|
|
|
| 정확함, 계기, 데이터 | 좁은 폭, 낮은 대비, 열린 카운터, 모노와 잘 붙는 것 |
|
|
| 편집, 읽는 시간, 권위 | 세리프 또는 고대비 산세, 넉넉한 x-height |
|
|
| 도구, 중립, 신뢰 | 그로테스크. 성격은 웨이트와 여백이 만든다 |
|
|
| 인상, 기억 | 디스플레이 전용 서체를 우선 검토한다. 본문 사용은 긴 읽기·언어 범위·확대 상태에서 검증한 경우에만 채택한다 |
|
|
|
|
### 1-3. 후보를 실제 값으로 검증한다
|
|
|
|
이름과 인상으로 고르지 마라. 다음 넷을 본다.
|
|
|
|
실제 proof에는 패밀리, 계산된 크기, 행간, 본문 폭을 따로 기록한다. 같은 CSS px도 글꼴의 x-height·획·글자틀에 따라 같은 광학 크기로 보이지 않는다. 선호와 읽기 성능도 같은 값이 아니므로 [Wallace et al.](./visual-perception-research.md#wallace-et-al-2022)의 조건처럼 실제 독자·문자 체계·과업에서 분리해 확인한다.
|
|
|
|
- **x-height 비율** — 소문자 높이와 본문 크기의 관계. 단일 임계값으로 가독성을 판정하지 말고 실제 크기·언어·렌더링에서 확인한다
|
|
- **대비** — 획 굵기 차이. 큰 크기에서만 살아나는 폰트가 있다
|
|
- **웨이트 범위** — 필요한 웨이트가 실제로 있는가. 없는 웨이트를 브라우저가 합성하면 뭉개진다
|
|
- **숫자 형태** — 라이닝인가 올드스타일인가, tabular 가 있는가
|
|
|
|
브라우저에서 재는 법:
|
|
|
|
```js
|
|
// 미로딩 매칭 폰트를 사용해야 하는지 점검한다. "Body KR"은 §6-6에서 고른 서체 이름으로 바꾼다
|
|
document.fonts.check('16px "Body KR"', '가나다ABC0123');
|
|
[...document.fonts].map(f => `${f.family} ${f.weight} ${f.status}`);
|
|
```
|
|
|
|
`true`는 특정 폰트의 존재·실제 사용·개별 글리프 지원을 보증하지 않는다. [MDN `FontFaceSet.check()`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/check)을 2026-09-20에 확인했다. 화면에 필요한 문자·언어·숫자·기호와 웨이트를 실제 카피로 확인하고, 가능한 브라우저의 rendered-font 확인 또는 필요한 글리프 검증과 실렌더 관찰을 결합한다. heading·line box 밖 rect만으로 글리프 잘림을 확정하지 않는 절차와 artifact 조건은 [audit-gate.md](audit-gate.md)의 렌더 측정 계약을 따른다.
|
|
|
|
### 1-4. 기본값을 피한다
|
|
|
|
`Inter` 는 금지가 아니라 **기본값 금지**다. 다국어 UI나 초고밀도 대시보드처럼 이유가 있으면 쓴다. 이유 없이 쓰면 그건 고른 게 아니다.
|
|
|
|
같은 이유로 `Fraunces` 와 `Instrument Serif` 도 기본 선택에서 뺀다. 생성 도구가 가장 자주 뱉는 두 서체다.
|
|
|
|
> **못 대면 기본값이다.** "왜 이 폰트인가"에 한 문장으로 답하지 못하면 아직 고른 것이 아니다.
|
|
|
|
---
|
|
|
|
## 2. 가변 폰트
|
|
|
|
여러 웨이트가 필요하면 가변 폰트를 후보로 둔다. 필요한 glyph·축·subset·캐시 조건에 따라 정적 파일 묶음이 더 작거나 호환성이 나을 수 있으므로 전송량과 실제 렌더를 비교한다.
|
|
|
|
### 축
|
|
|
|
| 축 | 뜻 | 쓸 곳 |
|
|
|---|---|---|
|
|
| `wght` | 굵기 | 위계. 가장 많이 쓴다 |
|
|
| `opsz` | 광학 크기 | 큰 글자와 작은 글자의 획 대비를 자동 조정 |
|
|
| `wdth` | 폭 | 좁은 컬럼, 긴 제목 |
|
|
| `slnt` / `ital` | 기울기 | 강조 |
|
|
|
|
### 쓰는 법
|
|
|
|
```css
|
|
/* 표준 속성을 쓴다. font-variation-settings 는 마지막 수단이다 */
|
|
h1 { font-weight: var(--weight-display); } /* 가변 폰트면 640 같은 비정수 값도 되지만, 토큰으로만 쓴다(tokens.md "웨이트") */
|
|
.lead { font-weight: var(--weight-medium); }
|
|
|
|
/* opsz 가 있으면 자동 적용된다. 끄지 마라 */
|
|
h1 { font-optical-sizing: auto; }
|
|
|
|
/* 표준 속성으로 못 여는 축만 이걸로 */
|
|
.wide { font-variation-settings: "wdth" 112; }
|
|
```
|
|
|
|
**`font-variation-settings` 로 `wght` 를 지정하지 마라.** 상속이 끊기고 `font-weight` 와 충돌한다.
|
|
|
|
### 애니메이션
|
|
|
|
가변 축 애니메이션은 레이아웃 계산 비용을 만들 수 있다. 자동 금지가 아니라, 3단계의 성능 예산 안에서 비용·입력 간섭·감소 모션 경로·상태 피드백을 실제 기기와 브라우저에서 측정해 채택한다. 글자 굵기 변화가 필요하지만 그 조건을 충족하지 못하면 `opacity`로 두 겹을 교차시키는 대안을 검토한다.
|
|
|
|
---
|
|
|
|
## 3. 로딩
|
|
|
|
### 3-1. `font-display` 를 고른다
|
|
|
|
| 값 | 동작 | 언제 |
|
|
|---|---|---|
|
|
| `swap` | 즉시 폴백, 로드되면 교체 | **본문 기본값.** 글자가 먼저 읽힌다 |
|
|
| `optional` | 아주 짧게 기다리고, 늦으면 이번 방문은 폴백으로 끝 | CLS 를 0으로 만들고 싶을 때. 첫 방문에 브랜드 서체가 안 보일 수 있다 |
|
|
| `fallback` | 짧은 블록 + 짧은 교체 창 | 절충안 |
|
|
| `block` | 최대 3초 블록 | **아이콘 폰트 외에는 쓰지 마라** |
|
|
|
|
### 3-2. `preload` 는 조건부다
|
|
|
|
**첫 화면에 실제로 그려지는 폰트 파일만** preload 한다. 그 외에는 다른 자원의 대역을 뺏을 뿐이다.
|
|
|
|
```html
|
|
<!-- 본문 한 벌만. crossorigin 을 빠뜨리면 두 번 받는다 -->
|
|
<link rel="preload" as="font" type="font/woff2" crossorigin
|
|
href="/fonts/body-var.woff2" />
|
|
```
|
|
|
|
모노는 보통 코드 블록에만 쓰이므로 preload 하지 않는다.
|
|
|
|
### 3-3. 서브셋
|
|
|
|
라틴은 `unicode-range` 로 쪼갠다. 브라우저가 **실제로 쓰인 범위만** 받는다.
|
|
|
|
```css
|
|
@font-face {
|
|
font-family: "Body";
|
|
src: url("/fonts/body-latin.woff2") format("woff2");
|
|
unicode-range: U+0000-00FF, U+2000-206F, U+2190-21BB;
|
|
font-display: swap;
|
|
}
|
|
```
|
|
|
|
**한글은 §6 을 보라.** 범위가 아니라 글자 단위로 쪼개야 한다.
|
|
|
|
### 3-4. 셀프 호스팅 vs CDN
|
|
|
|
| | 장점 | 대가 |
|
|
|---|---|---|
|
|
| 셀프 호스팅 | 요청 도메인 하나, 캐시 통제, 서드파티 장애 무관 | 업데이트를 직접 한다 |
|
|
| CDN | 설치가 한 줄 | 도메인이 늘고(각각 DNS+TLS), 그 서비스가 죽으면 글자가 죽는다 |
|
|
|
|
CDN을 쓸 때 `preconnect`는 실제 첫 화면 요청·연결 비용을 측정한 뒤 선택한다. 도메인 수만으로 결함을 판정하지 말고, 권한·캐시·장애 경로·전송 waterfall을 함께 검토한다.
|
|
|
|
---
|
|
|
|
## 4. 폴백 메트릭 — 가장 자주 빠뜨리는 곳
|
|
|
|
폰트가 늦게 오면 표시 전략에 따라 폴백과 웹폰트가 교체될 수 있다. 두 폰트의 행 높이·폭이 다르면 레이아웃 이동의 원인이 될 수 있지만, `swap`이 반드시 CLS를 만든다고 단정하지 않는다. 실제 네트워크 조건에서 측정한다.
|
|
|
|
해결은 **폴백 폰트를 실제 폰트의 치수에 맞추는 것**이다.
|
|
|
|
```css
|
|
/* 실제 폰트 */
|
|
@font-face {
|
|
font-family: "Body";
|
|
src: url("/fonts/body-var.woff2") format("woff2"); /* "woff2-variations"는 구식 문법이다. 필요하면 format(woff2) tech(variations) */
|
|
font-weight: 300 800;
|
|
font-display: swap;
|
|
}
|
|
|
|
/* 같은 치수로 보정한 폴백. 이름을 따로 준다 */
|
|
@font-face {
|
|
font-family: "Body Fallback";
|
|
src: local("Arial");
|
|
size-adjust: 107%; /* 글자 크기를 맞춘다 */
|
|
ascent-override: 92%; /* 행 위 여백 */
|
|
descent-override: 24%; /* 행 아래 여백 */
|
|
line-gap-override: 0%;
|
|
}
|
|
|
|
body {
|
|
font-family: "Body", "Body Fallback", sans-serif;
|
|
}
|
|
```
|
|
|
|
**숫자는 폰트마다 다르다.** 재는 방법:
|
|
|
|
```js
|
|
// 두 폰트로 같은 문자열을 그려 폭을 비교한다. 비율이 size-adjust 값이다
|
|
const measure = (family) => {
|
|
const el = document.createElement("span");
|
|
el.style.cssText = `position:absolute;visibility:hidden;font:100px ${family};white-space:nowrap`;
|
|
el.textContent = "가나다ABCabc0123";
|
|
document.body.appendChild(el);
|
|
const w = el.getBoundingClientRect().width;
|
|
el.remove();
|
|
return w;
|
|
};
|
|
(measure("Arial") / measure('"Body KR"') * 100).toFixed(1) + "%"; // "Body KR" = 고른 한글 서체
|
|
```
|
|
|
|
**Safari는 `size-adjust`만 적용한다.** `ascent-override`·`descent-override`·`line-gap-override`는 Chrome·Firefox에서만 동작하고 Safari 안정판(iOS 포함)은 무시한다(확인 2026-10-06)[WEB-BASELINE]. iPhone에서 폰트 교체 때 행 높이가 튀면 `line-height`를 단위 없는 비율로 명시했는지 먼저 보고, 남는 이동은 `font-display: optional`이나 preload로 줄인다. 교체 시점 레이아웃 측정(P8a)은 WebKit에서도 따로 한다.
|
|
|
|
`ascent-override` / `descent-override` 는 실제 폰트의 `hhea` 또는 `OS/2` 값을 `unitsPerEm` 으로 나눈 백분율이다. 도구가 없으면 **행 높이가 눈에 띄게 튀지 않을 때까지 조정**하고 그 값을 `design.md` 에 적어라.
|
|
|
|
> `font-display: optional`은 교체 가능성을 줄이는 한 선택지다. 폴백 메트릭 보정, 표시 전략, preload 여부는 실제 첫 방문·재방문·느린 망에서 조합해 검증한다.
|
|
|
|
---
|
|
|
|
## 5. OpenType 기능
|
|
|
|
### 숫자
|
|
|
|
```css
|
|
/* 표에 들어가는 수치. 자리가 흔들리지 않는다 */
|
|
.nums { font-variant-numeric: tabular-nums; }
|
|
|
|
/* 본문 속 숫자. 소문자 높이에 맞는다 */
|
|
.prose { font-variant-numeric: oldstyle-nums; }
|
|
|
|
/* 분수, 서수 */
|
|
.frac { font-variant-numeric: diagonal-fractions ordinal; }
|
|
```
|
|
|
|
**수치를 나열하는 곳에 `tabular-nums` 가 없으면 숫자가 춤춘다.** 대시보드·가격표·측정값 전부 해당한다.
|
|
|
|
### 그 외
|
|
|
|
```css
|
|
/* 커닝과 리가처는 기본으로 켜져 있다. 끄지 마라 */
|
|
body { font-kerning: normal; }
|
|
|
|
/* 대문자 사이 여백 보정. 전부 대문자인 짧은 라벨에만 */
|
|
.caps { font-variant-caps: all-small-caps; letter-spacing: 0.06em; }
|
|
|
|
/* 폰트가 제공하는 대체 글자. 무엇이 있는지 확인하고 쓴다 */
|
|
.alt { font-feature-settings: "ss01" 1; }
|
|
```
|
|
|
|
`font-variant-*` 표준 속성이 있으면 그걸 쓴다. `font-feature-settings` 는 표준 속성이 없는 기능에만 쓴다 — 이 속성은 기능별로 합쳐지지 않고 **선언 전체가 덮어써지므로**, 하위 선택자에서 `"ss01" 1`만 다시 쓰면 상위에서 켠 다른 기능(`"tnum"` 등)이 조용히 사라진다. 커닝·기본 리가처 같은 폰트 기본 기능은 끄지 않는 한 유지된다.
|
|
|
|
---
|
|
|
|
## 6. 한글
|
|
|
|
§6-1~§6-4는 **폰트 자체**(서브셋·웨이트·혼식·지정), §6-5는 **조판 정본**, §6-6은 **서체 후보**다. 한글 조판 규칙은 이 문서 §6-5 하나만 정본이다 — `layout.md` §3, `tokens.md` §1, `antipatterns.md` §8은 요약과 링크만 둔다. 값이 서로 달라 보이면 §6-5를 따르고 다른 쪽을 고친다.
|
|
|
|
### 6-1. 서브셋과 로딩
|
|
|
|
한글 완성형은 11,172자다. 전체 폰트가 클 수 있으므로 subset 전략을 검토한다. 모든 프로젝트가 글자 빈도 기반의 동적 subset을 반드시 써야 하는 것은 아니며, 콘텐츠 범위·cache·운영 복잡도와 실제 전송량으로 결정한다.
|
|
|
|
**쪼개는 단위가 라틴과 다르다.** 라틴은 `unicode-range` 로 블록을 나누면 되지만, 한글은 자주 쓰는 글자가 블록에 흩어져 있다. 그래서 **글자 빈도로 나눈 수십 개 조각**을 만들고 각각에 `unicode-range` 를 건다. 브라우저가 페이지에 실제로 쓰인 조각만 받는다.
|
|
|
|
이 방식을 **동적 서브셋**이라 부른다. Pretendard 의 `dynamic-subset` 빌드가 그것이고, 파일이 수십 개로 보이는 이유다. Google Fonts의 한글 패밀리도 같은 방식으로 잘려 나온다.
|
|
|
|
> **파일 개수로 폰트 예산을 세지 마라.** 한글 웹폰트가 파일 40개인 것은 정상이고 올바른 최적화다. 세야 할 것은 **패밀리 수와 실제 전송량**이다(`tokens.md` §5).
|
|
|
|
용도별 선택:
|
|
|
|
| 상황 | 방식 | 주의 |
|
|
|---|---|---|
|
|
| 본문·UI(문구가 바뀌거나 사용자 입력이 섞임) | 동적 서브셋 | 조각을 하나하나 `preload` 하지 않는다. 어느 조각이 필요할지 미리 모른다 |
|
|
| 디스플레이 서체를 고정 문구(로고타입·히어로 한 줄)에만 씀 | 그 문구의 글자만 담은 정적 서브셋(`pyftsubset --text-file=...` 등) | 문구가 바뀌면 빠진 글자가 폴백 서체로 섞여 그려진다. 문구 변경 때 서브셋을 다시 만드는 절차를 `design.md`에 적는다. CMS 문구면 동적 서브셋으로 간다 |
|
|
| 고정 범위 서브셋(KS X 1001 완성형 2,350자 + 라틴) | 파일 하나로 단순하게 | 일상 문장 대부분을 덮지만 이름·신조어에서 빠지는 글자가 생긴다. 고유명사·사용자 입력이 많은 화면에는 쓰지 않는다 |
|
|
|
|
본문 한글 웹폰트는 `font-display: swap` + 폴백 메트릭 보정(§4)에서 시작한다. 한글 폴백(Apple SD Gothic Neo·맑은 고딕)은 웹폰트와 글자 폭·행 높이가 달라 교체 때 줄바꿈이 바뀔 수 있으므로, 느린 망에서 히어로·내비가 다시 흐르는지 본다.
|
|
|
|
### 6-2. 웨이트가 라틴과 다르게 보인다
|
|
|
|
같은 700 이라도 한글은 획이 많아 더 무겁게 보인다. **라틴 기준으로 고른 웨이트를 한글에 그대로 쓰면 본문이 답답해진다.** 한글 본문은 400~500, 제목은 600~700 정도에서 시작해 눈으로 맞춘다. 이 값들은 `tokens.md` "웨이트"의 역할 토큰(`--weight-body`·`--weight-medium`·`--weight-strong`, 필요 시 `--weight-display`)에 담아 쓴다.
|
|
|
|
한글 폰트에는 x-height 개념이 없다. 대신 **글자틀 대비 속공간**을 본다. 속공간이 좁은 서체는 작은 크기에서 막힌다.
|
|
|
|
없는 굵기를 브라우저가 합성(가짜 볼드)하지 않게 실제로 쓰는 웨이트 파일을 싣는다. 확인은 `font-synthesis-weight: none`을 잠시 걸어 굵기 차이가 사라지는 자리가 있는지 보는 것이다(§10-1).
|
|
|
|
### 6-3. 라틴과 섞을 때(혼식)
|
|
|
|
**조건부 규칙이다.** 순서는 "라틴 디자인을 어느 서체가 맡는가"로 정한다.
|
|
|
|
| 조건 | 스택 | 이유 |
|
|
|---|---|---|
|
|
| 한글 패밀리 하나가 라틴·숫자까지 디자인했고 그 품질을 렌더로 확인했다(예: Pretendard·SUIT·IBM Plex Sans KR) | 그 패밀리 단독 + 폴백 | 한 패밀리 안에서 크기감·굵기·기준선이 맞춰져 있다. 단일 스택은 정당한 선택이다 |
|
|
| 라틴·숫자를 별도 서체가 맡는다 | 라틴 서체를 **앞에**, 한글 서체를 뒤에 | 한글 서체를 앞에 두면 숫자·영문까지 한글 서체의 라틴 글리프로 그려진다 |
|
|
|
|
```css
|
|
/* 라틴 서체가 따로 있을 때. 크기감이 다르면 라틴 쪽 @font-face 에 size-adjust 를 준다.
|
|
"Display Latin"·"Body KR"은 고른 서체 이름 자리다 — 후보는 §6-6 */
|
|
font-family: "Display Latin", "Body KR", sans-serif;
|
|
```
|
|
|
|
두 서체의 크기감이 다르면 `size-adjust` 를 쓴 별도 `@font-face` 로 맞춘다. §4 와 같은 기법이다. `size-adjust`는 그 face 전체에 걸리므로 라틴 face에만 준다. 같은 줄에서 라틴 대문자·숫자 높이가 한글 글자 높이보다 눈에 띄게 작거나 크면, 그리고 기준선이 떠 보이면 실제 문장(예: "2026년 iPhone 17 Pro 출시")을 크게 렌더해 비교한다.
|
|
|
|
### 6-4. 한글 서체를 지정할지
|
|
|
|
웹폰트를 쓰지 않고 한글 서체 이름도 적지 않으면 OS마다 다른 기본 한글 서체(맑은 고딕, Apple SD 산돌고딕 Neo, Noto Sans CJK KR 등)로 떨어진다. 서로 크기감·폭이 달라 **화면마다 다른 디자인이 된다.** 그래서 기본은 한글 서체를 명시하는 것이다.
|
|
|
|
시스템 서체를 **의도적으로** 고르는 것(브리프가 "네이티브 느낌"을 요구하거나 전송량 제약이 클 때)은 정당하다. 그때는 OS별로 어느 서체가 그려지는지 스택에 명시하고, 그 결정과 이유를 `design.md`에 적는다. 미결정과 결정의 차이는 기록에 있다.
|
|
|
|
```css
|
|
/* 웹폰트를 쓸 때 마지막 안전망까지 적는다. "Body KR"은 §6-6에서 고른 서체 이름 자리다 */
|
|
font-family: "Body KR", -apple-system, BlinkMacSystemFont,
|
|
"Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", sans-serif;
|
|
```
|
|
|
|
### 6-5. 한글 조판 — 정본
|
|
|
|
**전제: `<html lang="ko">`.** 외국어 구간에는 `lang`을 따로 단다. 한자 자형 선택(같은 코드포인트가 한·중·일에서 다르게 그려진다), 줄바꿈 규칙, 아래 `:lang(ko)` 선택자가 모두 이 값을 따른다.
|
|
|
|
출발 CSS:
|
|
|
|
```css
|
|
:lang(ko) {
|
|
word-break: keep-all; /* 어절(띄어쓰기) 단위로만 줄을 바꾼다 */
|
|
overflow-wrap: anywhere; /* 공백 없는 긴 토큰(URL·영문 ID·긴 복합어)만 예외로 끊는다. keep-all과 반드시 짝 */
|
|
line-break: strict; /* 넣어도 해는 없다. 한국어 본문에서 효과는 작다(아래 표) */
|
|
}
|
|
:lang(ko) :is(p, li, dd, blockquote, figcaption) { line-height: 1.7; letter-spacing: 0; }
|
|
:lang(ko) :is(h1, h2, h3) { line-height: 1.3; text-wrap: balance; }
|
|
|
|
/* 한글 이탤릭 금지: UA 기본 이탤릭을 풀고 다른 강조로 바꾼다 */
|
|
:root { font-synthesis-style: none; }
|
|
:lang(ko) :is(em, i, cite, dfn, var, address) { font-style: normal; }
|
|
:lang(ko) em { font-weight: var(--weight-strong); }
|
|
|
|
/* 밑줄이 받침을 긋지 않게 */
|
|
:lang(ko) a { text-underline-position: under; }
|
|
```
|
|
|
|
| 항목 | 규칙·시작값 | 왜 / 예외 |
|
|
|---|---|---|
|
|
| **어절 줄바꿈** | `word-break: keep-all` + `overflow-wrap: anywhere`. **keep-all 단독 금지** | 기본값(`normal`)은 한글 음절 사이 어디서나 줄을 바꿔 "디자/인"처럼 어절이 끊긴다. keep-all만 두면 공백 없는 긴 토큰이 컨테이너를 뚫는다. `anywhere`는 min-content 계산에도 반영돼 flex·grid 자식이 줄어들 수 있게 한다. 예외: 자동 폭 표 셀·`width: fit-content` 상자에서 `anywhere` 때문에 열이 좁아져 글자 단위로 쪼개지면, 그 범위만 `overflow-wrap: break-word`로 바꾼다(min-content에 영향을 주지 않는다) |
|
|
| **`line-break: strict`** | 넣되 효과를 기대하지 않는다 | `strict`와 `normal`의 차이는 일본어 작은 가나·반복 부호·일부 붙임표 앞 줄바꿈 같은 것이라 한국어 본문에서는 거의 같다. 닫는 괄호·마침표·쉼표가 줄 첫머리로 가는 것은 기본 줄바꿈 규칙(UAX #14)이 이미 막는다 |
|
|
| **`word-break: auto-phrase`** | 한국어에 쓰지 않는다 | Chrome에서 `lang="ja"`(일본어 문절)에만 동작한다 |
|
|
| **하이픈** | `hyphens`를 한글에 쓰지 않는다 | 한글에는 효과가 없다. 라틴 구간에서만 `lang`과 함께 쓴다 |
|
|
| **본문 크기** | 16px 이상. 캡션·보조 라벨은 13px 이상에서 시작 | 획이 많아 같은 px에서 라틴보다 막힌다. KRDS(정부 디자인 시스템)도 본문 최소 16px을 둔다. 12px 한글은 실제 서체로 확대 없이 읽히는지 확인한 뒤에만 쓴다 |
|
|
| **본문 행간** | 긴 읽기 1.7에서 시작, 범위 1.6~1.8. **하한 1.5** | 한글 글리프는 글자틀을 거의 채워 라틴처럼 어센더·디센더가 만든 빈 공간이 없다. 줄 사이 숨은 행간이 만든다. KRDS는 줄 간격 최소 150%를 두고, WCAG 1.4.12도 사용자가 1.5로 바꿔도 깨지지 않기를 요구한다. 1.8을 넘기면 줄이 서로 떨어진 목록처럼 읽히기 시작하므로 실제 문단으로 비교한다 |
|
|
| **제목·UI 행간** | 제목 1.2~1.35, 한 줄 UI 라벨 1.3~1.5 | 라틴 표제의 1.1을 그대로 쓰면 두 줄 제목에서 윗줄 받침과 아랫줄 초성이 닿는다. 확정 전 두 줄 이상 나오는 가장 긴 제목으로 렌더한다. 행 높이는 4px 격자로 반올림한다(`tokens.md` §3 수직 리듬) |
|
|
| **본문 자간** | `0` ~ `-0.01em` | 한국 웹에서 흔한 범위다. 자간이 넉넉하게 설계된 고딕은 -0.01em 근처, 명조·부리 본문은 0에서 시작한다. 단위는 반드시 `em`(px 자간은 크기가 바뀌면 비율이 깨진다) |
|
|
| **큰 표제 자간** | 대략 32px 이상 표제에서 `-0.02em` 시작. 더 좁히면 아래 충돌 검사를 통과해야 한다 | 큰 크기에서는 글자 사이가 벌어져 보여 음수 자간이 흔하다. 그러나 한글은 받침·쉼표·마침표·"ㄹ/ㅌ" 같은 넓은 자소가 옆 글자에 닿는 지점이 라틴보다 빨리 온다. 검사: 실제 문구로 1440·390 폭과 200% 확대 캡처를 2배로 잘라 보고 글리프 외곽이 닿거나 겹치는 쌍이 하나라도 있으면 되돌린다 |
|
|
| **양수 자간(라벨)** | 한글 라벨에 라틴 대문자식 넓은 자간(`0.1em` 이상)을 그대로 쓰지 않는다 | 한글은 자소가 모여 한 글자를 이뤄 자간을 벌리면 낱글자로 흩어져 "시 험 구 간"처럼 읽힌다. eyebrow를 한글로 쓰면 자간 대신 크기·굵기·색으로 구분한다 |
|
|
| **강조** | **한글 이탤릭 금지.** 굵기·색·밑줄·드러냄표(`text-emphasis: dot`, 지원·렌더 확인 후)로 강조 | 한글 서체는 이탤릭 면이 거의 없다. `font-style: italic`을 걸면 브라우저가 글자를 기울인 가짜 사체를 만든다. `font-synthesis-style: none`이면 기울임 대신 그냥 정체로 그려져 강조가 조용히 사라지므로, 위 CSS처럼 `em`·`i`·`cite` 등의 UA 기본 이탤릭을 풀고 다른 강조를 준다. 라틴 구간은 그 라틴 서체에 실제 이탤릭 face가 있을 때만 `:lang(en)` 등으로 기울인다 |
|
|
| **모노 서체** | 한글 글리프가 없는 모노(JetBrains Mono·IBM Plex Mono 등)를 한글이 섞이는 텍스트의 첫 서체로 두지 않는다 | 한글이 폴백 서체로 그려지면서 모노 라벨에 준 자간·폭 설정과 섞여 글자가 흩어져 보인다. 모노는 숫자·코드·라틴 약어에만 쓴다. 숫자 정렬이 목적이면 본문 서체 + `font-variant-numeric: tabular-nums`. 코드에 한글 주석이 필요하면 한글을 담은 모노(D2Coding·나눔고딕코딩, 둘 다 SIL OFL)를 쓴다. 섞어야 하면 아래 `unicode-range` 합성 서체로 라틴만 모노에 맡긴다 |
|
|
| **밑줄** | `:lang(ko) a { text-underline-position: under; }`. 그래도 붙으면 `text-underline-offset`을 em 단위로 실측 조정 | 한글은 기준선 아래로 내려오는 글리프가 많아 기본 밑줄이 받침을 긋는다. `under`는 글자 아래로 밑줄을 내린다. `text-decoration-skip-ink`는 한글 받침 사이를 지저분하게 끊을 수 있으니 실제 렌더로 본다 |
|
|
| **measure** | 긴 본문 한 줄 32~40자에서 시작, 단위는 **`em`**(`--measure: 36em` 근처에서 출발해 실제 글자 수를 센다). 카드·사이드바 같은 좁은 컬럼은 20자대까지 허용. **`ch`로 한글 measure를 적지 않는다** | 한글 음절의 폭은 대부분 서체에서 1em 안팎이라 `Nem`이 한글 N자의 시작 근사가 된다(비례 폭 서체는 조금 좁고, 띄어쓰기는 훨씬 좁다). `ch`는 "0"의 폭(대개 1em의 절반 남짓)이라 `40ch`는 한글 20자 남짓이 된다. em은 본문 글자 크기 기준이므로 본문 요소나 그 컨테이너에 선언한다. keep-all에서 measure가 너무 좁으면 오른쪽 들쭉날쭉(rag)이 커진다 |
|
|
| **제목 마지막 줄** | `text-wrap: balance` + keep-all. 표제 **마지막 줄에 한 어절만 남기지 않는다**(특히 2~3글자) | balance는 keep-all이 허락한 어절 경계 안에서 줄 길이를 고른다. 미지원 브라우저·예외 길이에서는 마지막 두 어절 사이 공백을 ` `로 묶거나 문구를 고친다. 브랜드 워드마크는 `white-space: nowrap`. 본문 문단의 위도우는 `text-wrap: pretty`(지원 범위는 §11-1)로 줄인다. 320·390·1440 폭에서 아래 스크립트로 잰다 |
|
|
| **한글·라틴 사이 간격** | 수동 공백이나 `margin`으로 벌리지 않는다. `text-autospace: normal`은 기본으로 켜지 않고, 켜려면 엔진별 렌더를 나란히 비교한다 | 세 엔진이 속성을 구현했지만(Chrome 140·Firefox 145·Safari 18.4) 초기값은 모두 `no-autospace`이고 적용 범위가 다르다. 2026-10-06 렌더에서 Chromium은 한글·라틴 경계에 간격을 넣었고 WebKit은 넣지 않았다 — 켜면 엔진 간 줄바꿈이 달라질 수 있다 [WEB-BASELINE] |
|
|
| **숫자·단위** | 표·가격·수치 나열에는 `tabular-nums`(§5). 숫자와 단위 표기 관례는 [product-copy.md](product-copy.md) §11-1 | 한글 서체의 숫자가 라틴 서체 숫자와 크기감이 다를 수 있다. 혼식(§6-3)에서 함께 확인한다 |
|
|
| **사용자 글자 간격 재정의** | WCAG 1.4.12(AA): 행간 1.5, 자간 0.12em, 단어 간격 0.16em, 문단 뒤 2em으로 바꿔도 내용이 잘리거나 겹치지 않는다 | keep-all + 넓어진 자간은 어절을 더 길게 만들어 고정 높이 카드·버튼에서 넘치기 쉽다. 고정 높이 대신 `min-height`, 줄바꿈 허용으로 둔다 |
|
|
|
|
**모노와 한글을 한 스택에 섞어야 할 때 — `unicode-range` 합성 서체.**
|
|
|
|
```css
|
|
/* "Label Mono"라는 이름 아래 라틴·숫자는 모노, 한글은 본문 고딕이 맡는다 */
|
|
@font-face { font-family: "Label Mono"; src: url("/fonts/mono-latin.woff2") format("woff2");
|
|
unicode-range: U+0000-00FF, U+2000-206F; }
|
|
@font-face { font-family: "Label Mono"; src: url("/fonts/body-hangul.woff2") format("woff2");
|
|
unicode-range: U+AC00-D7A3, U+1100-11FF, U+3130-318F; }
|
|
.label { font-family: "Label Mono", ui-monospace, monospace; letter-spacing: 0; }
|
|
```
|
|
|
|
**표제 마지막 줄의 어절 수를 잰다.**
|
|
|
|
```js
|
|
// 1 이면 마지막 줄에 한 어절만 남은 것이다. 320·390·1440 폭에서 h1·h2에 돌린다
|
|
const lastLineWords = (el) => {
|
|
const tops = [];
|
|
const w = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
for (let n; (n = w.nextNode());) {
|
|
for (const m of n.data.matchAll(/\S+/g)) {
|
|
const r = document.createRange();
|
|
r.setStart(n, m.index); r.setEnd(n, m.index + m[0].length);
|
|
const rects = r.getClientRects();
|
|
if (rects.length) tops.push(rects[rects.length - 1].top);
|
|
}
|
|
}
|
|
const last = Math.max(...tops);
|
|
return tops.filter((t) => t > last - 2).length;
|
|
};
|
|
[...document.querySelectorAll("h1, h2")].map((h) => [h.textContent.trim().slice(0, 20), lastLineWords(h)]);
|
|
```
|
|
|
|
**배포 전에 한글 조판을 렌더로 확인하는 항목** — 납품 판정은 [preflight.md](preflight.md) P8h가 이 목록을 가리킨다.
|
|
|
|
- 가장 긴 제목과 대표 본문 문단을 320·390·1440 폭, 200% 확대에서 렌더했다
|
|
- 어절이 중간에서 끊긴 곳, 긴 URL·영문 토큰이 넘친 곳이 없다
|
|
- 기울어진 한글(가짜 사체), 모노 라벨에서 흩어진 한글이 없다
|
|
- 큰 표제에서 글리프가 서로 닿는 쌍이 없다
|
|
- 밑줄이 받침을 긋지 않는다
|
|
|
|
### 6-6. 한글 서체 후보 — 기본값을 선택지로 바꾼다
|
|
|
|
**Pretendard + 마루 부리 + JetBrains Mono 조합을 기본값처럼 쓰지 않는다.** 이 조합은 지금 이 스킬의 결과물에서 가장 자주 반복된 하우스 룩이다. 이 조합(또는 그중 둘)을 쓰려면 아래 표에서 성격이 다른 후보 두 개 이상과 같은 실제 문구로 proof를 만들어 비교하고, 고른 이유를 `design.md`에 한 줄로 적는다. 직전 프로젝트와 같은 서체를 고르면 그 이유도 적는다. designpaca로 만든 쇼케이스 21종 실측(2026-10-01)에서 본문이 21종 모두 Pretendard였다. 한글이 섞이는 라벨은 라틴 모노(JetBrains Mono 등)로 조판하지 않는다(§6-5).
|
|
|
|
**후보 풀의 정본은 [presets/README.md](presets/README.md) "한글 서체 후보 풀"이다** — 서체별 배포처와 라이선스 확인일(2026-10-02)이 거기 있고, 프리셋 파일도 그 풀을 인용한다. 아래 표는 역할별 이름 요약이다. 둘이 다르면 presets/README.md를 따른다. **배포 전에는 배포처 원문에서 웹 임베딩·수정(서브셋) 허용을 다시 확인한다(§7).** 풀에서 "자체 라이선스"로 적힌 것(G마켓 산스·여기어때 잘난체)은 무료라도 조항이 서체마다 달라 반드시 원문을 본다.
|
|
|
|
각 칸은 라틴 이름 알파벳순, 그다음 한글 이름 가나다순이다. **순서는 우선순위가 아니다** — 첫 이름을 고르지 않는다. 성격·라이선스·배포처는 정본 풀의 표에서 본다.
|
|
|
|
| 역할 | 후보 |
|
|
|---|---|
|
|
| 본문 고딕 | Gothic A1 · Gowun Dodum · IBM Plex Sans KR · LINE Seed Sans KR · Noto Sans KR · Pretendard · Spoqa Han Sans Neo · SUIT · Wanted Sans · 나눔바른고딕 |
|
|
| 본문·표제 명조(부리) | Gowun Batang · Hahmlet · Noto Serif KR · 나눔명조 · 리디바탕 · 마루 부리 · 이롭게 바탕 |
|
|
| 디스플레이 | Bagel Fat One · Black Han Sans · Do Hyeon · Gasoek One · Jua · Orbit · Paperlogy · Song Myung · G마켓 산스 · 여기어때 잘난체 · 제주명조 |
|
|
| 모노(한글 포함) | D2Coding · 나눔고딕코딩 |
|
|
|
|
KoPub World 바탕·돋움은 웹폰트 임베딩에 한국출판인회의의 별도 승인이 필요하므로 승인 기록이 없으면 웹 후보에서 뺀다.
|
|
|
|
후보를 고르는 질문은 §1-2와 같다 — 이 글자는 무엇처럼 보여야 하는가. 본문 명조(부리)는 금지가 아니다. 16px 이상 긴 읽기에서 실제 서체로 proof를 만들어 획이 막히지 않는지 보고 채택한다.
|
|
|
|
### 6-7. 세로쓰기 — 가로쓰기 자간을 그대로 쓰지 마라
|
|
|
|
`writing-mode: vertical-rl` 로 세운 글자에 라벨용 자간(`letter-spacing: 0.14em`)을
|
|
그대로 물리면 **글자 사이 세로 간격**이 되어 낱글자가 흩어진다.
|
|
|
|
한글은 자소가 모여 한 글자를 이루기 때문에 라틴보다 심하다 —
|
|
"플로럴 스튜디오" 가 낱글자 일곱 개로 읽히기 시작한다.
|
|
|
|
```css
|
|
.vertical {
|
|
writing-mode: vertical-rl;
|
|
letter-spacing: 0.02em; /* 가로쓰기 라벨의 0.14em 을 그대로 쓰면 안 된다 */
|
|
}
|
|
```
|
|
|
|
세로쓰기는 **자간이 아니라 `line-height` 로 숨을 만든다.** 세로쓰기에서 `line-height` 는
|
|
글자 사이가 아니라 **줄(세로 기둥) 사이**를 벌린다.
|
|
|
|
> 좁은 화면에서는 세로 글자가 자리를 못 얻는다. `writing-mode: horizontal-tb` 로 눕히되,
|
|
> **그 자리의 그리드 칼럼도 같이 손봐라** — 가로로 누운 긴 문장이 `auto` 칸을 통째로 먹는다.
|
|
|
|
---
|
|
|
|
## 7. 라이선스
|
|
|
|
**배포 전에 확인한다.** 폰트 라이선스 위반은 조용히 있다가 청구서로 온다.
|
|
|
|
| 확인할 것 | 왜 |
|
|
|---|---|
|
|
| **웹폰트 임베딩이 허용되는가** | 데스크톱 설치는 되는데 웹 임베딩은 금지인 폰트가 많다 |
|
|
| 페이지뷰 상한이 있는가 | 상업 폰트에 흔하다 |
|
|
| 도메인이 묶여 있는가 | 스테이징·프리뷰 도메인이 빠질 수 있다 |
|
|
| 수정·서브셋이 허용되는가 | 서브셋도 파생물이다 |
|
|
| 크레딧이 필요한가 | CC-BY 계열 |
|
|
|
|
**한국 무료 폰트의 함정**: "상업적 이용 무료"가 곧 "웹폰트 임베딩 무료"는 아니다. 눈누(noonnu.cc)는 허용 범위를 표로 보여주니 **웹폰트 항목을 직접 확인**해라. SIL OFL 은 임베딩·수정·재배포가 모두 허용되지만 **폰트 자체를 판매할 수 없고**, 개명 조항이 있는 경우가 있다.
|
|
|
|
확인 결과를 `design.md` 에 한 줄로 남긴다: `본문 ___(라이선스, 웹 임베딩 가능 여부, 확인일) / 디스플레이 ___(같은 항목)`.
|
|
|
|
---
|
|
|
|
## 8. 구현 중 자기 점검 (납품 판정은 preflight.md P8)
|
|
|
|
서체를 고르고 조판하는 동안 확인한다. 납품 판정은 [preflight.md](preflight.md)가 한다 — 폰트 패밀리 수·전송량은 P7, `font-display`·폴백 메트릭은 P8a, preload·`crossorigin`은 P8b, 합성 웨이트·이탤릭은 P8c, `tabular-nums`는 P8d, 서브셋·전송량은 P8e, 라이선스는 P8f, 폰트 없이 열기는 P8g, §6-5 한글 조판 렌더 확인은 P8h.
|
|
|
|
- **"왜 이 폰트인가"에 한 문장으로 답할 수 있다**
|
|
- 필요한 웨이트의 가변/정적 후보를 실제 subset 전송량·지원 범위로 비교했다
|
|
- 혼식 순서를 §6-3 조건으로 정했다(라틴을 별도 서체가 맡으면 앞에, 한글 패밀리 단독이면 그 이유를 기록)
|
|
- 한글 서체를 명시했다. 시스템 서체를 쓰면 의도적 선택이라는 기록이 `design.md`에 있다(§6-4)
|
|
- Pretendard·마루 부리·JetBrains Mono 조합을 썼다면 다른 후보와 비교한 proof 기록이 있다(§6-6)
|
|
|
|
> 근거: 저장소 조사 문서 03-trends-2026 §7, 04-ai-slop-signatures §8 (저장소 전용, 설치본 미포함, 조사일 2026-08-20). 한글 조판 §6-5: [KRDS 타이포그래피](https://www.krds.go.kr/html/site/style/style_03.html)(본문 최소 16px, 줄 간격 최소 150%), [CSS Text 3 line-break](https://www.w3.org/TR/css-text-3/#line-break-property), [Chrome word-break: auto-phrase](https://developer.chrome.com/blog/css-i18n-features)(확인 2026-10-02)
|
|
|
|
---
|
|
|
|
## 9. 위계와 자간·행간 보강
|
|
|
|
### 9-1. 자간·행간은 크기의 함수다 — 통합 원칙
|
|
|
|
이 문서와 `tokens.md`에 이미 흩어져 있는 개별 규칙은 하나의 원리를 공유한다. **글자가 커질수록 자간은 좁아지고 행간은 타이트해지며, 글자가 작아질수록 자간은 넓어지고 행간은 느슨해진다.** 큰 디스플레이 텍스트에서 음수 자간을 후보로 두는 이유([tokens.md](tokens.md) §1 "함께 정해야 하는 것")도, 작은 대문자 라벨에 양의 자간을 주는 이유(§5의 `all-small-caps` 예시)도, 헤딩 행간을 좁히고(1.1~1.2대) 본문 행간을 넓히는(1.5~1.6, 한글 1.6~1.8 — 한글 정본은 §6-5) 이유도 같은 함수의 다른 지점일 뿐이다.
|
|
|
|
이 절은 **새 강제 수치를 만들지 않는다.** `tokens.md`·`antipatterns.md` §2가 이미 정한 값과 "보편 하한을 선언하지 않는다"는 방향은 그대로 유지한다. 여기서 더하는 것은 흩어진 개별 사례를 하나의 원리로 묶어 읽는 프레임뿐이다 [SKILL-BETTER-TYPE].
|
|
|
|
### 9-2. 타입을 능동적 디자인 요소로 쓰기 — 관찰 후보
|
|
|
|
**언제**: 헤드라인이나 숫자 자체가 화면의 시각적 주인공일 때(히어로 숫자, 잡지형 표지, 데이터 하나를 강조하는 카드).
|
|
|
|
이런 자리에서는 타입 처리 자체가 콘텐츠를 담기만 하는 중립적 그릇이 아니라 디자인의 능동적인 부분이 될 수 있다[SKILL-FRONTEND-DESIGN]. 예: 큰 사이즈, 의도적 절단(뷰포트 경계 밖으로 흘러나가는 글자), 겹침, 회전, 색 분리 같은 처리는 타이포그래피가 "읽히는" 것을 넘어 그 자체로 이미지가 되게 한다 — 이 나열 자체는 출처 없음(designpaca 확장)이다. **관찰 후보**다 — 본문·UI 라벨·폼처럼 기능이 우선인 텍스트에는 적용하지 않는다. 가독성이 과업에 실제로 필요한 자리(§13의 하드 게이트가 도는 자리)에서는 장식이 접근을 이기지 않는다.
|
|
|
|
### 9-3. Display와 Text는 다른 폰트다
|
|
|
|
"Display"라는 이름이 큰 사이즈용 최적화를 보장하지 않는다. 반대도 마찬가지다. SF Pro, Heldane 같은 일부 패밀리는 **큰 사이즈용 Display 변형과 작은 사이즈용 Text 변형을 별도 파일로 낸다** — Text 변형은 작은 크기에서도 읽히도록 획이 굵고 속공간이 넓게 그려지고, Display 변형은 큰 크기에서만 의도가 살도록 더 섬세하게 그려진다 [SKILL-BETTER-TYPE]. 가변 폰트라면 `font-optical-sizing: auto`(§2)가 이 전환을 자동으로 처리하지만, Display/Text가 별도 정적 파일로만 나오는 패밀리는 **설정하는 크기에 맞는 변형을 직접 골라야 한다.** 헤딩에 Display 파일을, 본문에 Text 파일을 매핑하지 않으면 한쪽에서 항상 맞지 않는 파일을 쓰게 된다.
|
|
|
|
### 9-4. 헤딩 위계 — 내림차순과, 본문보다 작아지지 않는 하한 (프로젝트 계약)
|
|
|
|
**프로젝트 계약**이다. 타입 스케일을 정했다면(`tokens.md` §1) 그 스케일에 헤딩 레벨을 매핑하는 기본 규칙이지, 위계가 "실제로 읽힌다"는 것을 증명하는 검사 자체는 아니다. 위계가 실제로 구별되는지는 `tokens.md` "제목 역할이 실제로 구분되는지 확인한다" 절의 실측 방법(여러 폭에서 계산된 크기를 재는 것)을 그대로 따른다 — 단일 비율 하나로 위계를 증명할 수 있다는 태도는 [design-foundations.md](design-foundations.md) §1이 이미 잘못된 일반화로 지목했다.
|
|
|
|
규칙은 두 가지다 [SKILL-BETTER-TYPE].
|
|
|
|
1. **헤딩 레벨은 타입 스케일의 내림차순 단계에 매핑한다.** h1이 h2보다, h2가 h3보다 시각적으로 작아서는 안 된다. 스케일 단계가 부족해 인접 레벨이 같은 크기를 공유해야 하면, 굵기나 자간으로 구별을 유지한다.
|
|
2. **헤딩은 본문보다 작아지지 않는다.** 예외는 의도적으로 라벨형 오버라인(예: "CASE STUDY"처럼 본문 위에 놓이는 작은 대문자 태그)으로 쓸 때뿐이다. 어떤 시맨틱 요소를 쓸지는 [accessibility.md](accessibility.md) 소관이고, 이 규칙은 고른 요소의 시각적 크기만 다룬다 — `<h3>`를 골랐다고 브라우저 기본 크기를 그대로 두지 않는다.
|
|
|
|
---
|
|
|
|
## 10. 폰트 렌더링 제어 보강
|
|
|
|
### 10-1. font-synthesis — shorthand 대신 longhand
|
|
|
|
`font-synthesis: none`은 굵기·기울임·small-caps·상하첨자 **합성을 한 번에 다 끈다.** 폴백 스택 전체에서 필수 강조(굵게, 기울임)가 실제 글리프로 구별되는지 검증하기 전에 이 shorthand를 쓰면, 폴백으로 떨어졌을 때 강조가 조용히 사라진다 — 오류도 보고도 없이.
|
|
|
|
**한 모드만 끄고 싶으면 shorthand 대신 개별 속성을 쓴다** [SKILL-BETTER-TYPE].
|
|
|
|
```css
|
|
/* 브랜드 워드마크처럼 합성된 굵기가 눈에 띄게 다를 때만, 검증 후 좁게 적용 */
|
|
.brand-wordmark {
|
|
font-synthesis-weight: none;
|
|
}
|
|
|
|
/* shorthand는 폴백 전체의 강조 표현을 확인한 뒤에만 */
|
|
.isolated-treatment {
|
|
font-synthesis: none;
|
|
}
|
|
```
|
|
|
|
### 10-2. OpenType 확장 기능
|
|
|
|
§5가 이미 다루는 `tabular-nums`·`oldstyle-nums`·`diagonal-fractions`·`ordinal`·`all-small-caps`·`ss01` 외에, 자주 쓰는 나머지 기능이다 [SKILL-BETTER-TYPE].
|
|
|
|
```css
|
|
/* 0과 O를 구별해야 하는 곳 — 일련번호, 인증코드, 주문번호 */
|
|
.serial { font-variant-numeric: slashed-zero; }
|
|
|
|
/* 리가처(fi, fl 합자 등)를 명시적으로 켠다. 대부분 기본 켜짐이지만 폰트가 다르면 확인한다 */
|
|
.prose { font-variant-ligatures: common-ligatures; }
|
|
|
|
/* 위·아래 첨자. 폰트가 실제 첨자 글리프를 담고 있어야 한다 — 없으면 인위적 축소로 대체된다 */
|
|
.formula-sup { font-variant-position: super; } /* x² 의 2 */
|
|
.formula-sub { font-variant-position: sub; } /* H₂O 의 2 */
|
|
|
|
/* 문자 변형(character variant). 슬롯 번호의 의미는 폰트마다 다르다 */
|
|
.logo { font-feature-settings: "cv11" 1; }
|
|
```
|
|
|
|
`ss01`~`ss20`(스타일 세트), `cv01`~`cv99`(문자 변형)는 번호가 폰트마다 다른 의미를 가지므로 쓰기 전에 그 폰트의 OpenType 기능 문서를 확인한다. 표준 속성(`font-variant-*`)이 있는 기능은 그것을 먼저 쓴다 — `font-feature-settings`는 표준 속성이 없는 기능에만 쓰는 마지막 수단이라는 §5의 원칙은 여기서도 그대로다.
|
|
|
|
### 10-3. 숫자가 흔들리면 안 되는 곳 — tabular-nums 확장
|
|
|
|
§5는 이미 "수치를 나열하는 곳"(표·대시보드·가격표)에 `tabular-nums`를 요구한다. 같은 이유가 **값이 실시간으로 바뀌는 자리**에도 그대로 적용된다 — 타이머, 카운트다운, 실시간 가격, 진행률 퍼센트처럼 숫자가 갱신될 때마다 폭이 바뀌면 주변 레이아웃이 갱신마다 흔들린다 [SKILL-BETTER-TYPE].
|
|
|
|
```css
|
|
/* 정적인 표뿐 아니라, 매초 값이 바뀌는 타이머에도 같은 이유로 필요하다 */
|
|
.countdown-digit { font-variant-numeric: tabular-nums; }
|
|
```
|
|
|
|
---
|
|
|
|
## 11. 줄바꿈과 정렬
|
|
|
|
### 11-1. 자동 줄바꿈 선언 — text-wrap:balance / pretty
|
|
|
|
**지원 상태 (확인 2026-09-24)** [WEB-BASELINE].
|
|
|
|
| 선언 | 상태 | 지원 브라우저 |
|
|
|---|---|---|
|
|
| `text-wrap: balance` | Baseline **Newly available**(2024-05-13. Widely는 30개월 뒤) | Chrome/Chrome Android 114, Edge 114, Firefox/Firefox Android 121, Safari/Safari iOS 17.5 |
|
|
| `text-wrap: pretty` | Baseline **Limited**(Baseline 아님) — **Firefox 미구현** | Chrome/Chrome Android 117, Edge 117, Safari/Safari iOS 26 |
|
|
|
|
두 값 모두 미지원 브라우저에서는 기본 줄바꿈으로 조용히 저하된다(레이아웃이 깨지지 않는다) — **점진적 향상으로 쓴다.** Firefox 비중이 큰 프로젝트라면 `pretty`가 주요 엔진 셋 중 Firefox만 미지원이라는 점을 감안한다.
|
|
|
|
```css
|
|
/* 두 줄 헤딩이 한쪽으로 쏠리지 않게 폭을 맞춘다 */
|
|
h1, h2, h3 { text-wrap: balance; }
|
|
|
|
/* 설명 문단의 위도우를 줄인다. 장문에는 쓰지 않는다(아래 §11-2) */
|
|
.lead, .description { text-wrap: pretty; }
|
|
```
|
|
|
|
한글 제목은 `word-break: keep-all`과 함께 쓴다. `balance`는 keep-all이 허락한 어절 경계 안에서만 줄 길이를 고르므로, keep-all 없이 쓰면 균형을 맞추려고 어절 중간에서 끊을 수 있다(§6-5).
|
|
|
|
`balance`는 브라우저가 몇 줄을 넘으면 스스로 적용을 포기한다 — 헤딩·소제목처럼 짧은 텍스트에 쓴다. 장문 본문에는 `balance`도 `pretty`도 쓰지 않는다. 문단 전체를 고르게 만들려는 시도는 공간을 낭비하고 가독성을 해칠 수 있다 [SKILL-BETTER-TYPE].
|
|
|
|
### 11-2. widow · orphan · river
|
|
|
|
- **widow(과부행)**: 문단이나 헤드라인의 마지막 줄에 단어 하나만 남는 것.
|
|
- **orphan(고아행)**: 문단의 첫 줄이 혼자 떨어져 남는 것.
|
|
- **river(리버)**: justify 정렬에서 여러 줄에 걸쳐 흰 공백이 세로로 이어져 보이는 것.
|
|
|
|
세 가지 모두 **관찰 후보**다. 헤드라인·리드 문단에서는 `text-wrap: balance`/`pretty`(§11-1)로 대부분 해소되고, 미지원 브라우저나 예외적인 문구 길이에서는 수동 개행(` `로 마지막 두 단어를 묶기)으로 보정한다. river는 justify 정렬(§11-4)에서만 나타나므로 인터페이스 본문에 justify를 쓰지 않으면 대부분 발생하지 않는다 [SKILL-DESIGN-REVIEW]. 실제 콘텐츠 길이와 뷰포트 폭으로 렌더해 확인한다 — 코드만 보고는 판정할 수 없다.
|
|
|
|
### 11-3. overflow-wrap과 white-space
|
|
|
|
- `overflow-wrap: anywhere` — 긴 단어·URL·ID가 컨테이너를 밀어내지 않게 강제로 끊는다. [preflight.md](preflight.md) W14가 긴 단어·URL 넘침을 판정한다. `break-word`와 달리 min-content 크기 계산에도 끊을 자리를 반영해 flex·grid 자식이 줄어들 수 있다. 반대로 자동 폭 표 셀이나 `fit-content` 상자는 그 때문에 지나치게 좁아질 수 있으므로, 그런 자리만 `break-word`로 둔다.
|
|
- `white-space: nowrap` — 배지·라벨처럼 줄바꿈되면 의미가 깨지는 짧은 텍스트에 쓴다. 컨테이너보다 라벨이 길어질 가능성이 있으면 `text-overflow: ellipsis`(§13)를 함께 둔다.
|
|
|
|
한글 문서에서 `word-break`가 어절 중간을 끊는 문제와 그 대응(`keep-all`과 `overflow-wrap`의 짝)은 §6-5가 정본이다 — 여기서는 라틴 문자열·URL·ID에 쓰는 `overflow-wrap`만 다룬다. 둘은 다른 문제를 푼다. `overflow-wrap`은 끊을 지점이 없는 긴 토큰의 예외 처리이고, 한글 `word-break` 규칙은 정상적인 어절 단위 줄바꿈 자체를 다룬다.
|
|
|
|
### 11-4. justify 사용 제한
|
|
|
|
**관찰 후보**다 — [antipatterns.md](antipatterns.md) §3의 판정과 같다. `text-align: start`를 인터페이스 기본으로 하고, `justify`는 자동 하이픈네이션이 걸린 좁은 에디토리얼 컬럼처럼 특정 레이아웃에서만 검토한다 [SKILL-BETTER-TYPE]. 인터페이스의 나머지 자리(카드 설명, 폼 도움말, 버튼 라벨)에 justify를 쓰면 단어 사이 공백이 불규칙해지고 §11-2의 river가 나타나기 쉽다. 한글은 어절 단위로 줄바꿈되므로 river가 나타나는 양상이 라틴과 다르다 — 실제 렌더로 확인한다.
|
|
|
|
### 11-5. 문장부호
|
|
|
|
한국어 문장부호(따옴표, 줄임표, 붙임표, 값과 단위 사이 공백 처리)의 관례는 [product-copy.md](product-copy.md) §11(숫자·단위·금액·날짜는 §11-1)이 정본이다. 이 문서는 줄바꿈·렌더 규칙만 다루고 문장부호 자체는 다루지 않는다.
|
|
|
|
---
|
|
|
|
## 12. 밑줄과 트리밍
|
|
|
|
### 12-1. 밑줄 메트릭
|
|
|
|
폰트가 내장한 밑줄 위치·두께를 기본값으로 쓴다.
|
|
|
|
```css
|
|
a {
|
|
text-underline-position: from-font;
|
|
text-decoration-thickness: from-font;
|
|
text-decoration-skip-ink: auto; /* 하강부(g, y, p)를 밑줄이 가로지르지 않게 */
|
|
}
|
|
```
|
|
|
|
`from-font`가 원하는 두께를 안 주거나 폰트가 메트릭을 제공하지 않으면 수동으로 조정한다 [SKILL-BETTER-TYPE]. 한글 링크는 `text-underline-position: under`가 출발점이다 — 기본 위치는 받침을 긋는다(§6-5).
|
|
|
|
```css
|
|
a {
|
|
text-decoration-thickness: 1px; /* 실측 조정 — 시작값 */
|
|
text-underline-offset: 3px; /* 실측 조정 — 시작값 */
|
|
text-decoration-skip-ink: auto;
|
|
}
|
|
```
|
|
|
|
**관찰 후보 — 실측으로 확인한다.** 밑줄에서 애니메이션이 안정적으로 동작하는 속성은 `color`뿐이라는 관찰이 있다. `text-decoration-thickness`나 `text-underline-offset`을 트랜지션에 넣으면 브라우저마다 다르게(또는 전혀) 움직일 수 있다. 두께·오프셋이 변하는 밑줄 애니메이션(왼쪽에서 자라나는 효과 등)이 필요하면 `text-decoration`을 쓰지 말고 별도 요소(가상 요소 `::after`의 `transform: scaleX()`)로 구축하는 대안을 검토한다. 점선 밑줄(`text-decoration-style: dotted`)은 약어·정의어 같은 부가 정보 힌트의 관례로 쓴다.
|
|
|
|
### 12-2. text-box trim — 점진적 향상
|
|
|
|
**지원 상태 (확인 2026-10-06)** — 세 엔진이 모두 출하했다(Chrome 133, Safari 18.2, 마지막인 Firefox 154 — MDN 호환성 데이터) [WEB-BASELINE]. webstatus.dev는 이 날짜에도 Limited로 표시하지만 Firefox 칸이 빈 데이터 지연이다. 아직 Widely가 아니므로 점진적 향상으로 쓴다.
|
|
|
|
`text-box`는 글자 위·아래의 행간 여백(폰트가 내장한 ascender/descender 공간) 중 시각적으로 불필요한 부분을 잘라낸다 — 배지·필처럼 텍스트를 컨테이너에 광학적으로 정확히 맞춰야 하는 좁은 자리에 쓴다.
|
|
|
|
```css
|
|
/* 위아래 모두 트림 */
|
|
.badge { text-box: trim-both cap alphabetic; }
|
|
/* 위쪽만 (큰 헤딩이 컨테이너 상단에 붙어야 할 때) */
|
|
.heading { text-box: trim-start cap; }
|
|
/* 아래쪽만 */
|
|
.label { text-box: trim-end alphabetic; }
|
|
```
|
|
|
|
미지원 브라우저에서는 이 선언이 무시되고 기존 여백이 그대로 남는다 — 레이아웃이 깨지지 않으므로 **점진적 향상으로 쓴다.** 광학 정렬이 하드 게이트인 자리는 없다. 지원 브라우저에서 더 정확해지는 정도로 다룬다.
|
|
|
|
---
|
|
|
|
## 13. 잘린 텍스트 — 전체 값에 도달하는 수단 (하드 게이트)
|
|
|
|
**하드 게이트**다. 정보 접근 자체가 걸린 문제이기 때문이다. 이 규칙은 [SKILL.md](../SKILL.md)의 규범·기능 하드 게이트, [accessibility.md](accessibility.md), [critique.md](critique.md)의 에스컬레이션 표와 같은 판정을 공유한다.
|
|
|
|
절단 자체는 금지가 아니다.
|
|
|
|
```css
|
|
/* 한 줄 절단 */
|
|
.truncate {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* 여러 줄 절단 */
|
|
.clamp {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 3;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
```
|
|
|
|
**금지되는 것은 잘린 정보가 어디서도 완전한 형태로 확인될 수 없는 상태다.** 잘려나간 값이 의미를 가지면(전체 파일명, 긴 이메일 제목, 잘린 가격 설명), 다음 중 하나로 전체 값에 도달할 수단을 남긴다.
|
|
|
|
1. **클릭·탭으로 여는 확장 뷰** — 펼치기 버튼, 상세 페이지 링크, 모달, 아코디언. 마우스·키보드·터치에서 모두 동작하므로 첫 선택이다.
|
|
2. **포커스 가능한 트리거에 붙인 커스텀 툴팁** — hover와 키보드 focus 양쪽에서 열리고 [accessibility.md](accessibility.md) §9의 1.4.13 세 조건(해제 가능·호버 가능·지속)을 지킨다. 터치에서는 탭으로 열리는지 따로 확인한다.
|
|
3. **네이티브 `title` 속성은 마우스 보조 수단일 뿐 단독으로는 불충분하다.** 키보드 포커스와 터치에서 나타나지 않는다. 1·2 중 하나와 함께 둘 때만 쓴다.
|
|
|
|
`aria-label`로 스크린리더에만 전체 값을 주는 것은 화면을 보는 키보드·터치 사용자에게 아무것도 보여주지 않으므로 도달 수단으로 세지 않는다.
|
|
|
|
전체 값에 도달할 수단이 하나도 없는 절단은 통과시키지 않는다. 검증은 실제 콘텐츠 중 가장 긴 값으로 렌더한 뒤, 마우스 없이 키보드만으로, 그리고 터치 에뮬레이션에서 탭만으로 전체 값에 닿을 수 있는지 확인하는 것이다 [SKILL-BETTER-TYPE].
|
|
|
|
---
|
|
|
|
## 14. 렌더링과 선택 디테일
|
|
|
|
### 14-1. 폰트 스무딩은 루트에서 한 번
|
|
|
|
```css
|
|
:root {
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
```
|
|
|
|
**관찰 후보.** macOS에서 글자를 얇게 렌더하는 이 두 속성은 쓸지 말지부터 실제 렌더로 정한다(가는 서체·작은 크기에서는 획이 약해질 수 있다). 쓰기로 했다면 **루트에 한 번만** 선언한다. 컴포넌트마다 반복해서 적용하면 스무딩 방식이 요소 경계에서 갈라져 보이는 자리가 생긴다 [SKILL-BETTER-TYPE].
|
|
|
|
### 14-2. 시스템 폰트 폴백 스택 예시
|
|
|
|
브랜드 서체를 아직 못 구했거나 로딩 실패에 대비할 때, 또는 브리프가 "네이티브 느낌"을 요구할 때 쓸 수 있는 시작 스택이다 [SKILL-APPLE-DESIGN]. **기본값으로 강요하지 않는다** — 브랜드 서체가 정해졌으면 그 폴백 체인(§4)을 우선한다.
|
|
|
|
```css
|
|
font-family:
|
|
system-ui, -apple-system, "Segoe UI",
|
|
Roboto, "Helvetica Neue", Arial,
|
|
"Apple SD Gothic Neo", "Malgun Gothic",
|
|
sans-serif;
|
|
```
|
|
|
|
한글이 섞이는 프로젝트는 §6-3의 조건부 순서 규칙과 §6-4(시스템 서체를 의도적으로 고르면 기록)를 따른다.
|
|
|
|
### 14-3. 텍스트 선택 가능성 유지
|
|
|
|
텍스트는 기본적으로 선택 가능해야 한다. `::selection`으로 선택 영역의 배경·글자색을 브랜드에 맞게 바꾸는 것은 괜찮다 — 단, 그 조합으로도 선택된 글자가 읽혀야 한다(대비를 확인한다) [SKILL-BETTER-TYPE].
|
|
|
|
```css
|
|
::selection {
|
|
background: var(--accent);
|
|
color: var(--accent-ink);
|
|
}
|
|
```
|
|
|
|
**`user-select: none`은 드래그·제스처 표면에서만 쓴다** — 정렬 핸들, 캔버스 위 라벨, 스와이프 카드처럼 선택이 제스처와 충돌하는 자리다. 인터페이스 전역에 걸거나, 버튼 라벨이 드래그 중 하이라이트될 가능성만으로 걸지 않는다. 본문·카드 설명·에러 메시지처럼 사용자가 복사하고 싶어할 수 있는 텍스트에 `user-select: none`이 걸려 있으면 그 자체가 완성도 미달 신호다.
|
|
|
|
### 14-4. 장식적 텍스트 기법 — 관찰 후보
|
|
|
|
**언제**: 에디토리얼 인트로, 인용구, 잡지형 섹션 오프닝처럼 타이포그래피가 §9-2의 능동적 요소로 쓰이는 자리.
|
|
|
|
| 기법 | 용도 |
|
|
|---|---|
|
|
| `::first-letter` | 드롭캡 |
|
|
| `::first-line` | 첫 줄만 다른 스타일(작은 대문자 등) |
|
|
| `-webkit-text-stroke` | 텍스트 외곽선. 가변 폰트에서는 겹치는 글자 모양이 병합되지 않은 채 보일 수 있다 — 정적 폰트로 확인한다 |
|
|
| `text-shadow` | 텍스트 그림자. [elevation.md](elevation.md)의 그림자 원칙(광원 하나로 통일)과 같은 기준을 따른다 |
|
|
|
|
본문·UI 텍스트에는 쓰지 않는다. `background-clip: text`(그라디언트 텍스트)는 이미 `antipatterns.md`가 다룬다 [SKILL-BETTER-TYPE].
|
|
|
|
---
|
|
|
|
## 15. 타이포 마감 전 빠른 점검
|
|
|
|
§8 자기 점검과 preflight P8이 로딩·라이선스를 다룬다면, 이 표는 **렌더된 실수**를 잡는다. 실제 콘텐츠로 채운 화면에서 확인한다.
|
|
|
|
| 발견 | 조치 |
|
|
|---|---|
|
|
| 합성된 굵기·기울임이 디자인과 다르게 보인다 | 실제 페이스를 로드한다. 검증된 모드만 `font-synthesis-*: none`(§10-1) |
|
|
| Display 전용 파일을 작은 본문 크기에 그대로 쓴다 | 크기에 맞는 Text/Display 변형을 고른다(§9-3) |
|
|
| 자식 헤딩이 부모보다 시각적으로 강하다 | 해당 섹션의 위계를 스케일 내림차순에 다시 매핑한다(§9-4) |
|
|
| 헤딩 요소를 시각 크기만 보고 골랐다 | 시맨틱을 먼저 고르고([accessibility.md](accessibility.md)) 시각 크기는 CSS로 정한다(§9-4) |
|
|
| 문단 마지막 줄에 단어 하나(위도우) | `text-wrap: pretty`(§11-1, 지원 상태 확인) 또는 수동 개행 |
|
|
| 두 줄 헤딩이 한쪽으로 쏠린다 | `text-wrap: balance`(§11-1) |
|
|
| 인터페이스에 justify 정렬이 쓰인다 | `text-align: start`로. justify는 에디토리얼 컬럼에만(§11-4) |
|
|
| 밑줄이 하강부를 가로지른다 | `text-decoration-skip-ink: auto`, `from-font` 메트릭(§12-1) |
|
|
| 밑줄 두께·오프셋을 트랜지션에 걸어 애니메이션이 브라우저마다 다르게 움직인다 | `color`만 트랜지션하거나 별도 요소로 구축한다(§12-1) |
|
|
| 절단된 텍스트에 전체 값 도달 수단이 없다 | §13 — **하드 게이트**, 배포 차단 |
|
|
| 인터페이스 전역에서 텍스트 선택이 막혀 있다 | 복원하고, 드래그·제스처와 충돌하는 자리에만 남긴다(§14-3) |
|
|
| 갱신되는 숫자(타이머·카운터·실시간 가격)의 폭이 매번 바뀐다 | `tabular-nums`(§10-3) |
|
|
| 한글 어절이 중간에서 끊기거나, 긴 URL이 넘친다 | `word-break: keep-all` + `overflow-wrap: anywhere` 짝(§6-5) |
|
|
| 한글이 기울어져 보인다(가짜 사체) | `font-synthesis-style: none` + `em`·`i` 기울임 해제, 굵기·색으로 강조(§6-5) |
|
|
| 모노 라벨의 한글이 낱글자로 흩어진다 | 본문 서체 + `tabular-nums`, 또는 `unicode-range` 합성 서체(§6-5) |
|
|
| 큰 한글 표제에서 글자·쉼표·받침이 닿는다 | 음수 자간을 되돌린다(§6-5 큰 표제 자간) |
|
|
| 한글 표제 마지막 줄에 한 어절만 남는다 | `text-wrap: balance` + keep-all, ` ` 묶기, 문구 수정(§6-5) |
|
|
| 한글 본문 한 줄이 20자 남짓이다 | `ch`로 적은 measure를 `em`으로 바꾼다(§6-5) |
|
|
|
|
> 근거: [SKILL-BETTER-TYPE], [SKILL-FRONTEND-DESIGN], [SKILL-DESIGN-REVIEW], [SKILL-APPLE-DESIGN], [WEB-BASELINE](확인 2026-09-24)
|