디자인 SSOT 를 세우고 3D 를 배경 재질로 되돌린다

지적받은 것: 모바일 마진·패딩, 폰트 웨이트, 버튼 스타일 불일치, SSOT,
스크롤바, 모바일 메뉴, 그리고 3D 를 왜 상자에 넣었는지.

3D 의 역할을 잘못 잡았다
  lusion.co 의 "3D 는 액자 안"은 3D 자체가 상품일 때의 규칙이다.
  우리는 절차를 파는데 액자에 넣을 상품이 없어 "저 상자는 뭐냐"가 됐고,
  의미를 게이트 판독기가 자막으로 설명하고 있었다.
  이 씬의 역할은 재질이다. 배경으로 되돌리고 값을 전부 낮췄다 —
  테두리 광 0.85->0.42, 알파 0.72->0.34, UV 경계 0.05->0.17, 이동 60%
  ResizeObserver 로 캔버스를 따라가게 했다. window.resize 만 듣는 동안
  히어로 높이 변화를 놓쳐 배경이 화면의 3분의 2만 덮었다

SSOT
  웨이트 6종(400/500/560/600/620/660) -> 3종. 셋은 토큰에 없던 값이었다
  .btn + btn-solid/btn-quiet. 두 버튼이 높이 36 vs 44, 패딩 16 vs 8,
    테두리 1px vs 0, 웨이트 400 vs 500 이었다
  --header-h. 간격을 clamp 로 바꾸자 관계가 끊겨 h1 이 헤더 뒤로 들어갔다
  --space-5~8 과 --gutter 를 clamp 로. 고정이면 모바일 여백이 화면의 26%
  scrollbar-color 지정

모바일
  좌우 여백 24 -> 32px, 섹션 상하 112 -> 56px
  헤더에 섹션 앵커 4개. 900px 미만은 details 기반 메뉴(JS 0바이트)

패럴랙스는 섹션마다 다른 말을 한다
  히어로   글자 상승 / 배경 하강 — 반대 방향이라야 시차가 보인다
  파이프라인 단계명 고정, 산출물만 지연 — 인과
  재료     타일이 서로 다른 깊이 — 표면은 한 겹이 아니다
  프리플라이트 뒤 항목일수록 늦게 — 로그는 흐른다
  수치     없음. 의도적 정지 — 계기판의 바늘은 떨지 않는다

스킬
  three.md  §0-A 를 역할별로 갈랐다. "액자 안"만 적어둔 것이 잘못된
            일반화였고 내가 거기 그대로 걸렸다. 재질일 때의 값 교정을 실측으로 남김
  tokens.md 웨이트·컨트롤·헤더 높이·반응형 간격 — 토큰이 아예 없던 네 축
  motion.md 섹션별 패럴랙스 배정표. 움직이지 않는 것도 결정이다

390px 실렌더: 가로 스크롤 0, 대비 실패 0, 웨이트 3종, radius 2종. 테스트 22개 통과
This commit is contained in:
Yun Chan 2026-08-20 14:15:30 +09:00
parent 4b853355e3
commit 9c9ab2de7e
18 changed files with 606 additions and 231 deletions

View file

@ -12,7 +12,14 @@ const rest = c.items.filter((m) => !m.feature);
카드에 담지 않는다. 숫자가 크기와 정렬선으로 스스로 위계를 만든다.
카드는 elevation 이 진짜 위계를 나를 때만 쓴다는 규칙을 여기서 지킨다.
*/}
<section class="section metrics" aria-labelledby="metrics-h">
{/*
이 섹션에는 패럴랙스를 넣지 않는다. 빠뜨린 것이 아니라 결정이다.
여기가 이 페이지의 수치를 공개하는 자리다. 숫자가 스크롤에 따라
흔들리면 그 값도 흔들리는 것처럼 읽힌다. 계기판의 바늘은 떨지 않는다.
움직임이 의미를 만드는 자리가 있으면, 움직이지 않는 것이 의미인 자리도 있다.
*/}
<section id="metrics" class="section metrics" aria-labelledby="metrics-h">
<div class="head">
<p class="eyebrow reveal">{c.eyebrow}</p>
<h2 id="metrics-h" class="reveal">{c.h2}</h2>
@ -52,7 +59,7 @@ const rest = c.items.filter((m) => !m.feature);
.figures {
max-width: 1180px;
margin-inline: auto;
padding-inline: var(--space-4);
padding-inline: var(--gutter);
display: grid;
/* 비대칭. 큰 수치 둘이 왼쪽을 차지하고 나머지는 오른쪽 여백에 흐른다 */
grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
@ -69,11 +76,11 @@ const rest = c.items.filter((m) => !m.feature);
.fig-value {
font-size: clamp(3.4rem, 2rem + 5vw, 6rem);
line-height: 0.92;
font-weight: 620;
font-weight: var(--weight-strong);
letter-spacing: -0.04em;
color: var(--accent);
}
.fig-label { font-size: var(--step-1); font-weight: 500; margin-top: var(--space-2); }
.fig-label { font-size: var(--step-1); font-weight: var(--weight-medium); margin-top: var(--space-2); }
.fig-note { font-size: var(--step-0); max-width: 22em; }
/* 나머지 수치는 오른쪽 열에서 두 칸을 세로로 이어 쓴다 */
@ -93,7 +100,7 @@ const rest = c.items.filter((m) => !m.feature);
border-top: 1px solid var(--line);
}
.row:last-child { border-bottom: 1px solid var(--line); }
dt { font-weight: 500; }
dt { font-weight: var(--weight-medium); }
dd { margin: 0; }
dd.nums { font-size: var(--step-1); color: var(--accent); justify-self: end; }
.row-note {