fix(site): 제목-본문 간격을 전역 하나로 통일하고 스파인 리듬을 토큰화
All checks were successful
ci / build (push) Successful in 26s

제목과 본문이 붙어 있던 문제를 고친다.

- h2{margin:0} + p{margin-top:0} 이라 제목 다음 간격이 0px 이었다.
  섹션마다 각자 margin-top 을 붙여 어떤 곳은 0, 어떤 곳은 24px 로 갈려 있었다.
  base.css 에서 한 번만 정하고 각 섹션의 margin-top 오버라이드를 걷어냈다.
- :where() 로 감싼 첫 시도는 특이도 0 이라 p 규칙에 져서 무효였다.
  특이도를 갖추고 p 뒤로 옮겼다.
- 제목을 .head 로 감싼 수치 섹션은 인접 형제가 끊겨 혼자 0px 이었다.
  .head 도 규칙에 포함했다.

파이프라인 정거장 간격이 224px 로 벌어져 목록이 흩어져 보이던 문제도 고친다.

- 읽기 간격과 sticky 스크롤 구간을 같은 값으로 맞추려던 것이 원인이다.
  간격은 --stop-pad(space-5)로 되돌리고, 스크롤 구간은 .after 뒤 여백으로 옮겼다.
  게이트당 237px 확보 — 과민 반응 기준(200px) 위다.
- 마커 원의 top 이 space-4, 패딩이 space-6 로 따로 적혀 있어 원이 제목보다
  24px 위에 떠 있었다. 셋 다 --stop-pad 를 보게 했다.

3D 판 위 본문 대비는 실측 결과 최저 9.83:1 로 AAA 를 넘어 손대지 않았다.

스킬 내재화:
- tokens.md  제목 다음 간격 SSOT, :where() 특이도 함정, 리듬은 지역 토큰으로
- motion.md  읽기 간격과 스크롤 구간은 같은 손잡이가 아니다
- antipatterns.md  재는 도구가 거짓말하는 세 방식
  (reveal 중 측정 / sharp stats 가 extract 를 무시 / 눈보다 숫자)
This commit is contained in:
Yun Chan 2026-08-20 22:29:29 +09:00
parent e1e6002bee
commit f7f811ab42
8 changed files with 142 additions and 13 deletions

View file

@ -292,6 +292,43 @@ letter-spacing:\s*-0\.0[3-9] (한글 본문)
---
## 9-b. 재는 도구가 거짓말하는 세 가지 방식
측정으로 판정하라고 해놓고, 그 측정 자체가 틀리면 잘못된 확신만 남는다.
실측에서 걸린 세 가지다.
**① 등장 애니메이션이 끝나기 전에 재면 간격이 틀리게 나온다.**
`.reveal { transform: translateY(16px) }` 이 걸린 요소는 화면 밖에 있는 동안 계속
16px 아래에 있다. 화면 최상단에서 아래쪽 섹션의 간격을 재면 그 16px 이 여백으로 잡힌다.
한 섹션만 24px 이 아니라 38px 로 나와 한참 원인을 찾았는데, 원인은 CSS 가 아니라
**측정 시점**이었다. 제목과 본문이 나란히 애니메이션되면 같이 움직여 상쇄되지만,
한쪽만 `.reveal` 이면 상쇄되지 않는다.
```js
await page.evaluate(() => el.scrollIntoView({ block: 'center' }));
await page.waitForTimeout(1600); // 반드시 애니메이션이 끝난 뒤에 잰다
```
**② `sharp(...).extract(...).stats()` 는 잘라내기를 무시하고 원본 전체를 잰다.**
영역을 여섯 군데 잘라 밝기를 쟀는데 여섯 개가 **소수점까지 똑같이** 나왔다.
전부 원본 전체 평균이었기 때문이다. 값이 수상하게 균일하면 도구를 먼저 의심해라.
```js
const buf = await sharp(f).extract(box).png().toBuffer();
const s = await sharp(buf).stats(); // 잘라낸 버퍼를 다시 물려야 한다
```
**③ 눈으로 의심한 것이 실측에서 뒤집힐 수 있다.**
WebGL 판이 본문 뒤를 지나가는 화면을 보고 "대비가 죽었다" 고 판단해 캔버스를
어둡게 만들려 했다. 스크린샷에서 글자 사이 빈 띠의 배경 휘도를 재보니
최악 지점이 **9.83:1** — AAA(7:1)를 넘었다. 고칠 필요가 없었다.
밝은 것과 대비가 낮은 것은 다르다. **화려한 배경 위의 대비는 인상이 아니라 숫자로 판정해라.**
> 캔버스 위에서는 `readPixels` 가 안 통한다(three.md 참고). 스크린샷을 찍어
> **글자를 피한 빈 띠**의 평균 휘도를 재는 것이 유일하게 맞는 방법이다.
---
## 10. 자가 채점표
| 카테고리 | 걸린 항목 | 조치 |
@ -315,6 +352,7 @@ letter-spacing:\s*-0\.0[3-9] (한글 본문)
- [ ] 색을 회색조로 바꿔도 위계 순서가 비즈니스 우선순위와 일치한다
- [ ] 키보드만으로 전 인터랙션이 가능하고 포커스 링이 보인다
- [ ] (한국어) 한글 폰트가 명시되어 있고 `word-break: keep-all`이 있다
- [ ] 모든 섹션에서 제목→본문 간격이 **같은 값**이다 (0px 인 섹션이 하나도 없다)
**판정**

View file

@ -638,6 +638,30 @@ CSS 격자와 사진을 같이 깔았다가 걷어냈다. 두 배경이 서로
---
### 읽기 간격과 스크롤 구간은 같은 손잡이가 아니다
sticky 캔버스의 진행도는 `섹션 높이 뷰포트` 다. 그래서 "스크롤 구간이 짧다" 는
문제를 만나면 **목록 항목의 간격을 벌려 섹션을 늘리고 싶어진다.** 실측에서 그렇게 했다.
- `--space-4`(24px): 섹션 1493px → sticky 이동 593px. 조금만 굴려도 여섯 단계를 다 지나침
- `--space-7`(96px): 게이트당 300px 확보. 대신 항목 사이가 **224px** 로 벌어져
여섯 개가 한 목록으로 안 읽힘. 사용자 지적: "각 아이템별로 마진 공간이 너무 크다"
**하나의 값으로 두 가지를 맞추려 하면 둘 다 나빠진다.** 나눠라.
```css
.stop { padding-block: var(--stop-pad); } /* 읽기 리듬 — 눈으로 정한다 */
.after { padding-bottom: 62dvh; } /* 스크롤 구간 — 계산으로 정한다 */
```
여백을 **마지막 항목 뒤**에 몰면, 목록은 촘촘하게 읽히고 스크롤 구간은 확보된다.
목록이 끝난 뒤의 공백은 빈칸이 아니라 여운으로 읽힌다 — 스크롤텔링의 기본 문법이다.
판정 기준: **게이트 하나당 200px 이상**. 휠 한 번이 100~150px 이므로
그 아래면 "조금만 스크롤해도 다 지나가는" 과민한 반응이 된다.
---
## 4. JS 스택 판단 — 언제 끌어오고 언제 안 끌어오나
```

View file

@ -126,6 +126,52 @@
- 섹션 간격은 **본문 간격의 4배 이상**. 좁으면 페이지가 뭉개진다
- 요소를 정렬할 때 **간격이 아니라 정렬선**을 먼저 맞춰라
### 제목 다음 간격은 전역에서 한 번 정한다
`h2 { margin: 0 }` 리셋과 `p { margin: 0 0 X }` 를 같이 쓰면 **제목과 본문이 붙는다.**
위쪽 여백이 양쪽 다 0 이기 때문이다. 눈에 띄게 깨져 보이지만, 섹션을 하나씩 만들다 보면
어떤 섹션에는 `.mat-lead { margin-block: var(--space-4) ... }` 처럼 손으로 붙게 되고
어떤 섹션에는 안 붙는다. 실측에서 한 페이지 안에 0px 인 섹션 둘과 24px 인 섹션 셋이
공존했다. **간격이 컴포넌트마다 다르면 그건 SSOT 가 아니다.**
```css
p { margin: 0 0 var(--space-3); }
/* 제목 다음에 오는 것은 반드시 떨어진다 — 여기 한 곳에서만 정한다 */
h1 + *,
h2 + *,
h3 + *,
.head + * { margin-top: var(--space-4); }
```
세 가지 함정이 있다.
1. **`:where()` 로 감싸면 특이도가 0 이 되어 위의 `p` 규칙에 진다.**
`:where(h2) + :where(.lead)` 는 0-0-0, `p` 는 0-0-1 이다. 아무 효과 없이 조용히 무시된다.
특이도를 갖추고, 캐스케이드에서 `p` **뒤에** 둬라.
2. **제목을 `<div class="head">` 로 감싼 섹션에서는 인접 형제가 끊긴다.**
그 섹션만 혼자 0px 이 된다. 제목 묶음 클래스도 같은 규칙에 넣어라.
3. **섹션이 각자 `margin-top` 을 또 붙이면 전역이 무력해진다.**
위쪽은 전역이, 아래쪽만 섹션이 정한다 — `margin-block: A B` 가 아니라 `margin-bottom: B`.
### 반복되는 리듬은 값이 아니라 토큰 하나로
같은 리듬을 세 곳에서 쓰면 세 곳이 따로 논다. 실측 사례 — 타임라인에서
정거장 패딩은 `--space-5`, 마커 원의 `top``calc(var(--space-4) + 0.55em)`,
연결선의 `top/bottom``--space-4` 로 각자 적혀 있었다. 패딩만 한 단계 올리자
**원이 제목보다 24px 위에 떠 버렸다.**
```css
.spine {
--stop-pad: var(--space-5); /* 리듬의 출처는 한 곳 */
}
.spine::before { top: var(--stop-pad); bottom: var(--stop-pad); }
.stop { padding-block: var(--stop-pad); }
.stop::before { top: calc(var(--stop-pad) + 0.55em); }
```
규칙: **같은 값을 두 번 이상 적게 되면 그 순간 지역 토큰으로 올려라.**
---
## 3-b. 형태 (radius·선)