타이포그래피 스킬 신설 + 폴백 메트릭 보정

'폰트 관련된 것도 스킬이 좀 잘 해줘야 하는데 그렇지 못한 것 같다'는 지적.
점검해보니 size-adjust, ascent-override, unicode-range, OpenType 기능,
FOUT/FOIT, 폰트 라이선스가 스킬 어디에도 없었다. font-display 와 서브셋은
tokens.md 에 한 줄씩뿐이었다.

references/typography.md 신설
- 고르는 절차: 개수를 먼저 정하고, 성격을 브리프에서 끌어내고,
  x-height·대비·웨이트 범위·숫자 형태를 실제로 재서 검증한다
- 가변 폰트: 축, 표준 속성 우선, 가변 축 애니메이션이 왜 게이트 8 위반인지
- 로딩: font-display 값별 선택 기준, preload 조건, unicode-range
- 폴백 메트릭: size-adjust 와 override 3종. 재는 코드 포함
- OpenType: tabular-nums 등. font-variant-* 를 font-feature-settings 보다 먼저
- 한글: 동적 서브셋 원리, 웨이트가 라틴과 다르게 보이는 이유, 스택 순서
- 라이선스: 웹 임베딩 허용 여부가 상업 이용 허용과 다르다는 것

그리고 이 페이지가 그 규칙을 어기고 있었다
- 실측: 맑은 고딕이 Pretendard 보다 13.0% 넓다. 교체 순간 줄바꿈이 다시 잡힌다
- fonts.css 신설. OS 별로 폴백을 따로 정의하고 size-adjust 로 맞췄다
- 실측 차이 13.0% -> 0.0%

preflight.md 에 폰트 검사 6개를 추가했다. 지금까지 하나도 없었다.
This commit is contained in:
Yun Chan 2026-08-20 12:42:59 +09:00
parent 5f8316f336
commit eb3ca69b5d
9 changed files with 389 additions and 7 deletions

View file

@ -258,6 +258,11 @@ export const ko: Content = {
h2: "이 페이지를 만들며 실제로 걸린 것",
lead: "남을 검사한 결과가 아니라 자신을 검사한 결과다.",
items: [
{
caught: "폰트가 교체되는 순간 글자 폭이 13% 튀고 있었다",
fixed:
"웹폰트가 오기 전에는 시스템 폰트로 그려진다. 맑은 고딕은 Pretendard 보다 13% 넓어서 교체되는 순간 줄바꿈이 다시 잡힌다. 폴백 폰트에 size-adjust 로 치수를 맞췄다. 실측 차이 0.0%. OS 마다 시스템 폰트가 다르므로 폴백도 따로 정의했다.",
},
{
caught: "유리 카드를 29개 뿌렸다. 재질만 바뀌었을 뿐 구조는 카드 나열 그대로였다",
fixed:
@ -297,7 +302,7 @@ export const ko: Content = {
fixed: "그리드의 본문 폭을 토큰에 묶었다. 스킬 문서의 예시 코드도 같이 고쳤다.",
},
],
note: "여덟 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 마지막 한 건은 기계가 아니라 사람이 잡았다. 카드가 전형적이라는 말 한 줄이었다.",
note: "아홉 건 모두 만들면서 걸린 것이고, 걸린 뒤에 고쳤다. 사람이 잡은 것이 둘이다. 카드가 전형적이라는 말과, 폰트를 제대로 다루지 못한다는 말.",
},
typography: {
eyebrow: "한글 조판",
@ -452,6 +457,11 @@ export const en: Content = {
h2: "What this page actually got caught on",
lead: "Not the result of auditing someone else. The result of auditing itself.",
items: [
{
caught: "Text width jumped 13% at the moment the font swapped",
fixed:
"Before the web font arrives the system font draws instead, and Malgun Gothic runs 13% wider than Pretendard, so line breaks are recalculated on swap. Matched the fallback with size-adjust. Measured difference is now 0.0%. System fonts differ per OS, so each gets its own fallback.",
},
{
caught: "29 glass cards. Only the material had changed. The structure was still a row of cards",
fixed:
@ -492,7 +502,7 @@ export const en: Content = {
"Bound the grid's main column to the token. The skill's own example code was wrong too, so that got fixed as well.",
},
],
note: "All eight were caught while building this page, and fixed after being caught. The last one came from a person, not a checker. One line: the cards look generic.",
note: "All nine were caught while building this page, and fixed after being caught. Two came from a person rather than a checker: the cards look generic, and the typography is not handled well.",
},
typography: {
eyebrow: "Your language is probably not Latin",

View file

@ -1,4 +1,5 @@
---
import "../styles/fonts.css";
import "../styles/tokens.css";
import "../styles/base.css";

View file

@ -0,0 +1,48 @@
/* ============================================================
폰트 폴백 메트릭 보정
웹폰트가 늦게 오면 브라우저는 시스템 폰트로 먼저 그리고 나중에 교체한다.
폰트의 폭과 높이가 다르면 순간 레이아웃이 튄다. CLS 흔한 원인이고
font-display: swap 쓰는 반드시 일어난다.
그래서 폴백 폰트를 실제 폰트의 치수에 맞춘다.
아래 숫자는 페이지에서 직접 값이다. 추정치가 아니다.
한글 Pretendard 1434px vs 맑은 고딕 1620px -> 88.5%
Apple SD Gothic Neo 1510px -> 94.9%
영문 Switzer 1580px vs Arial 1501px -> 105.3%
재는 법은 references/typography.md §4 있다.
OS 마다 시스템 폰트가 다르므로 폴백도 따로 정의한다. 하나로 뭉치면
한쪽 OS 에서는 보정이 틀린다.
============================================================ */
/* ---- 한글: Windows ---------------------------------------- */
@font-face {
font-family: "KO Fallback Win";
src: local("Malgun Gothic"), local("맑은 고딕");
size-adjust: 88.5%;
ascent-override: 108%;
descent-override: 27%;
line-gap-override: 0%;
}
/* ---- 한글: macOS ------------------------------------------ */
@font-face {
font-family: "KO Fallback Mac";
src: local("Apple SD Gothic Neo"), local("AppleSDGothicNeo-Regular");
size-adjust: 94.9%;
ascent-override: 100%;
descent-override: 25%;
line-gap-override: 0%;
}
/* ---- 라틴 -------------------------------------------------- */
@font-face {
font-family: "EN Fallback";
src: local("Arial"), local("Helvetica Neue"), local("Helvetica");
size-adjust: 105.3%;
ascent-override: 110%;
descent-override: 28%;
line-gap-override: 0%;
}

View file

@ -95,7 +95,8 @@
/* ---- 타입: 한국어판 --------------------------------------- */
[lang="ko"] {
--font-sans: "Pretendard Variable", Pretendard, system-ui, sans-serif;
--font-sans: "Pretendard Variable", Pretendard,
"KO Fallback Win", "KO Fallback Mac", system-ui, sans-serif;
--step-0: 1.0625rem;
--step-1: 1.328rem;
@ -119,7 +120,7 @@
/* ---- 타입: 영어판 ------------------------------------------ */
[lang="en"] {
--font-sans: "Switzer", system-ui, -apple-system, sans-serif;
--font-sans: "Switzer", "EN Fallback", system-ui, -apple-system, sans-serif;
--step-0: 1rem;
--step-1: 1.25rem;