디자인 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:
parent
4b853355e3
commit
9c9ab2de7e
18 changed files with 606 additions and 231 deletions
|
|
@ -428,6 +428,58 @@ JS 쪽 판정도 한 곳에 모은다. 설정이 도중에 바뀌면 반영한
|
|||
|
||||
---
|
||||
|
||||
## 패럴랙스는 섹션마다 다른 말을 해야 한다
|
||||
|
||||
같은 패럴랙스를 모든 섹션에 똑같이 걸면 그건 문법이 아니라 벽지다.
|
||||
**그 섹션이 주장하는 것이 무엇인지 먼저 말하고, 그 주장을 움직임으로 옮겨라.**
|
||||
|
||||
실측 사례 — designpaca 소개 페이지의 섹션별 배정:
|
||||
|
||||
| 섹션 | 주장 | 움직임 | 왜 |
|
||||
|---|---|---|---|
|
||||
| 히어로 | "결정하지 않으면 기본값이 나온다" | 글자 ↑ / 배경 ↓ (**반대 방향**) | 결정이 떠오르고 기본값이 가라앉는다. 같은 방향이면 그냥 느린 스크롤로 읽힌다 |
|
||||
| 파이프라인 | 단계를 밟으면 결과가 나온다 | 단계명 **고정**, 산출물만 지연 | 옆의 것이 고정이어야 "저것 때문에 이것"이 보인다. 둘 다 움직이면 인과가 사라진다 |
|
||||
| 사례 | 서로 다른 두 결과물 | 두 판이 다른 속도로 어긋남 | 같은 스킬에서 다른 것이 나온다 |
|
||||
| 재료 | 표면은 한 겹이 아니다 | 타일이 서로 다른 깊이 | 주장 자체가 레이어다 |
|
||||
| 프리플라이트 | 시간순 기록 | 뒤 항목일수록 더 늦게 따라옴 | 로그는 흐른다 |
|
||||
| 수치 | 공개한 값은 검증 가능하다 | **없음** | 숫자가 흔들리면 값도 흔들려 보인다. 계기판의 바늘은 떨지 않는다 |
|
||||
|
||||
**마지막 줄이 가장 중요하다.** 움직이지 않는 것도 결정이다.
|
||||
안 움직이는 섹션이 하나도 없으면 움직임에 의미가 없다는 뜻이고,
|
||||
`design.md` 에 "여기는 의도적으로 정지"라고 적어두지 않으면 다음 사람이 "빠뜨린 것"으로 보고 채운다.
|
||||
|
||||
### 구현은 scroll-driven animation 으로
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
@supports (animation-timeline: view()) {
|
||||
.par-slow, .par-fast, .par-lag {
|
||||
animation: par linear both;
|
||||
animation-timeline: view();
|
||||
animation-range: cover 0% cover 100%;
|
||||
}
|
||||
@keyframes par {
|
||||
from { transform: translate3d(0, var(--par-from), 0); }
|
||||
to { transform: translate3d(0, var(--par-to), 0); }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**두 가지를 지켜라.**
|
||||
|
||||
1. **`transform` 만 애니메이션한다.** `opacity` 를 걸면 범위 끝에 도달하지 못한 요소가
|
||||
영영 투명하게 남는다 — 지원 안 되는 브라우저에서는 제자리에 그대로 있을 뿐이지만,
|
||||
`opacity` 는 콘텐츠를 지운다. 실측에서 6단계 중 3개가 그렇게 사라졌다.
|
||||
2. **페이지 최상단 요소는 `view()` 가 아니라 `scroll()` 을 써라.** 히어로는 처음부터 보이므로
|
||||
`entry` 구간이 이미 지나 있어 시작점이 확정되지 않는다.
|
||||
`animation-timeline: scroll(root block); animation-range: 0 92vh;`
|
||||
|
||||
새 클래스를 만들 때 **애니메이션 셀렉터에 추가하는 것을 잊지 마라.** 변수만 정의하면
|
||||
아무 일도 일어나지 않고, 조용히 실패해서 검출도 안 된다.
|
||||
|
||||
---
|
||||
|
||||
## 4. JS 스택 판단 — 언제 끌어오고 언제 안 끌어오나
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -20,11 +20,23 @@
|
|||
|
||||
---
|
||||
|
||||
## 0-A. 배치가 먼저다 — 3D 는 액자 안, 텍스트는 액자 밖
|
||||
## 0-A. 배치가 먼저다 — 이 씬은 주인공인가 재질인가
|
||||
|
||||
채택을 결정했다면 **어디에 놓을지부터 정해라.** 이 결정이 씬의 품질보다 결과를 크게 바꾼다.
|
||||
그리고 배치는 하나가 아니다. **먼저 이 씬의 역할을 정해야 한다.**
|
||||
|
||||
> **기본값: 캔버스를 배경 전면에 깔고 그 위에 본문을 얹지 마라.**
|
||||
| 역할 | 배치 | 언제 |
|
||||
|---|---|---|
|
||||
| **주인공** | 액자 안. 텍스트는 액자 밖 | 3D 자체가 상품일 때 — 포트폴리오, 프로덕트 뷰어, 3D 스튜디오 |
|
||||
| **재질** | 배경 전면. 텍스트가 그 위에 앉는다 | 공간감이 목적일 때 — 랜딩, 브랜드 사이트 |
|
||||
|
||||
**둘을 섞지 마라.** 재질로 쓸 씬을 액자에 가두면 "저 상자는 뭐냐"는 질문을 받는다.
|
||||
액자는 안에 든 것이 볼 가치가 있다고 선언하는 장치라서, 그 안에 배경 무늬가 들어 있으면
|
||||
선언과 내용이 어긋난다. 실측에서 그렇게 만들었다가 되돌렸다.
|
||||
|
||||
### 주인공일 때 — 액자 안
|
||||
|
||||
> **텍스트를 3D 위에 얹지 마라.**
|
||||
|
||||
겹치면 두 가지가 동시에 일어난다. 3D 는 텍스트 가독성을 위해 흐리고 어둡게 눌려 얼룩이 되고,
|
||||
텍스트는 대비를 확보하려고 글로우·스크림·그림자를 달게 된다. **둘 다 소리치다 둘 다 죽는다.**
|
||||
|
|
@ -47,6 +59,22 @@ h1 이 36px 이어도 히어로가 압도적이다.
|
|||
|
||||
캔버스 위에 글자를 올려야 한다면 **하나만** 올려라. 그리고 거기엔 바탕을 깔아라.
|
||||
|
||||
### 재질일 때 — 배경 전면
|
||||
|
||||
주인공이 아니라 공간을 만드는 표면이다. 규칙이 반대가 된다.
|
||||
|
||||
- **대비를 낮춘다.** 주인공일 때의 값을 그대로 쓰면 글자 뒤에서 형광 막대처럼 튄다.
|
||||
실측 교정: 테두리 광 계수 `0.85 → 0.42`, 알파 `0.72 → 0.34`
|
||||
- **경계를 푼다.** 평면의 UV 테두리를 좁게 잡으면 사각형 프레임이 또렷하게 서서
|
||||
추상 패턴이 아니라 '상자'로 읽힌다. `smoothstep(0.0, 0.05, ...)` → `0.17` 로 넓히고 제곱해 번지게 한다
|
||||
- **느리게 움직인다.** 스크롤 전 구간을 다 통과시키면 시선을 끌어 글자와 싸운다. 이동량을 60% 로 줄였다
|
||||
- **글자 자리에 스크림을 깐다.** 배경이 균일하게 밝으면 본문 대비가 무너진다.
|
||||
전면을 덮지 말고 **글자가 앉는 쪽만** 눌러라 — 반대쪽을 열어둬야 배경이 살아 있다
|
||||
- **아래를 마스크로 지운다.** 섹션 경계에서 배경이 가로선으로 끊기면 두 장의 종이처럼 보인다.
|
||||
`mask-image: linear-gradient(to bottom, black 62%, transparent 96%)`
|
||||
- **캔버스 크기는 `ResizeObserver` 로 따라간다.** `window.resize` 만 들으면 폰트가 늦게 오거나
|
||||
콘텐츠가 접힐 때 캔버스가 옛 크기로 남아 배경이 화면 일부만 덮는다. 그건 resize 이벤트가 아니다
|
||||
|
||||
---
|
||||
|
||||
## 0. 채택 게이트 — "쓰지 않는다"를 먼저 통과시켜라
|
||||
|
|
|
|||
|
|
@ -165,6 +165,68 @@
|
|||
|
||||
---
|
||||
|
||||
## 웨이트와 컨트롤 — 자주 빠지는 두 축
|
||||
|
||||
타입 스케일·색·간격은 대부분 토큰으로 잡는다. **웨이트와 컨트롤 치수는 거의 안 잡는다.**
|
||||
그래서 SSOT 가 거기서 먼저 깨진다.
|
||||
|
||||
### 웨이트는 세 개다
|
||||
|
||||
```css
|
||||
--weight-body: 400;
|
||||
--weight-medium: 500;
|
||||
--weight-strong: 600;
|
||||
```
|
||||
|
||||
컴포넌트마다 `font-weight: 560` 같은 값을 직접 쓰기 시작하면 페이지에 웨이트가 여섯 종이 된다.
|
||||
실측 사례: designpaca 소개 페이지가 `400 / 500 / 560 / 600 / 620 / 660` 을 동시에 쓰고 있었고,
|
||||
그중 셋은 토큰에 없는 값이었다.
|
||||
|
||||
가변폰트는 660 같은 중간값도 그려준다. 그래서 더 위험하다 — **표준 축(400/500/600/700)을
|
||||
벗어나면 힌팅이 흐려지고, 폴백 폰트에서는 아예 다른 굵기로 떨어진다.**
|
||||
|
||||
**웨이트로 위계를 만들려 들지 마라.** R1 실측(zed.dev 모바일)에서 `font-weight: 400` 이 832회,
|
||||
나머지 전부 합쳐 12회였다. 위계는 크기와 색이 만든다.
|
||||
|
||||
### 버튼처럼 생긴 것은 전부 같은 치수를 쓴다
|
||||
|
||||
```css
|
||||
--control-h: 2.75rem; /* 44px — 터치 타깃 */
|
||||
--control-h-sm: 2.25rem; /* 36px — 헤더 등 조밀한 자리 */
|
||||
--control-pad-x: var(--space-3);
|
||||
```
|
||||
|
||||
컴포넌트마다 높이와 패딩을 다시 정하면 그때부터 SSOT 가 아니다.
|
||||
실측 사례: 같은 페이지의 두 버튼이 높이 36 vs 44, 패딩 16 vs 8, 테두리 1px vs 0,
|
||||
웨이트 400 vs 500 이었다. 규칙이 없으니 전부 달랐다.
|
||||
반대로 R1 의 두 버튼은 `h36 · pad-x10 · r4 · w400 · 14px` 로 **픽셀 단위까지 같았다.**
|
||||
|
||||
### 떠 있는 헤더는 높이를 토큰으로 내놔야 한다
|
||||
|
||||
```css
|
||||
--header-h: calc(var(--space-3) + var(--space-2) * 2 + var(--control-h-sm));
|
||||
```
|
||||
|
||||
`position: fixed` 헤더는 문서 흐름에서 빠져 있어서, 첫 섹션의 상단 여백과 앵커 목적지
|
||||
(`scroll-margin-top`)가 그 높이를 알아야 한다. 값을 각자 손으로 맞춰두면
|
||||
**간격 토큰을 `clamp()` 로 바꾸는 순간 관계가 끊어져 제목이 헤더 뒤로 들어간다.**
|
||||
값이 아니라 관계를 토큰으로 둬라.
|
||||
|
||||
### 큰 간격은 화면에 반응해야 한다
|
||||
|
||||
작은 값(4~24px)은 고정, 큰 값만 `clamp()`.
|
||||
|
||||
```css
|
||||
--space-6: clamp(2.5rem, 1.6rem + 2.6vw, 4rem);
|
||||
--space-7: clamp(3.5rem, 1.9rem + 4.6vw, 7rem);
|
||||
--gutter: clamp(2rem, 1.4rem + 1.8vw, 2.5rem);
|
||||
```
|
||||
|
||||
고정으로 두면 모바일에서 섹션 상하 여백 112px 이 그대로 들어가 **화면 높이의 26% 를 여백이 먹는다.**
|
||||
좌우 여백도 마찬가지다 — R1 모바일 실측은 32px 이었다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 성능 예산
|
||||
|
||||
**여기서 정한다. 구현 후에 재면 이미 늦었다.**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue