feat(skill): route fixes, delivery report, composition diversity, platform refresh
- 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
This commit is contained in:
parent
9bcb45cbd3
commit
88c11682b1
51 changed files with 5834 additions and 2259 deletions
|
|
@ -10,11 +10,12 @@
|
|||
|---|---|
|
||||
| **three를 쓸지 아직 안 정했다** | **§0만.** 대부분 여기서 "안 쓴다"로 끝나고, **그게 가장 흔한 정답이다** |
|
||||
| 쓰기로 했다 | §1 버전 핀 + §2 함정 + **§3 코드 A(지연 로드)** — 이 셋이 최소 필수 |
|
||||
| WebGPURenderer·TSL로 바꿀지 고민한다 | §1-b. 기본은 "바꾸지 않는다" |
|
||||
| 배경 이펙트가 필요하다 | + §4 코드 B (메시 그라디언트) |
|
||||
| 모바일·저사양을 만난다 | + §5 코드 C (품질 티어) + §8 성능 |
|
||||
| 이미지 그리드를 강화한다 | + §6 코드 D |
|
||||
| 다른 패턴을 찾는다 | §7 표에서 고르고 `research/three/02-patterns.md`를 열어라 |
|
||||
| 감사 직전 | §9 Lighthouse + §10 폴백 + §11 체크리스트 |
|
||||
| 다른 패턴을 찾는다 | §7 표에서 고르고, 같은 행의 "구현 출발점"(three.js·drei 공식 API)에서 시작한다 |
|
||||
| 구현을 마무리할 때 | §9 Lighthouse + §10 폴백 + §11 자기 점검. 납품 판정은 [preflight.md](preflight.md) X4 |
|
||||
|
||||
§0을 통과하지 못하면 나머지는 읽을 필요가 없다. **"안 쓴다"는 실패가 아니라 이 스킬의 규칙이다.**
|
||||
|
||||
|
|
@ -201,7 +202,7 @@ mountScene({
|
|||
### 채택 조건 — 넷 다 만족해야 한다
|
||||
|
||||
1. CSS/SVG로 같은 인상이 안 나온다
|
||||
2. 추가분이 **+200KB(gzip) 이내**다 (데모·포트폴리오 브리프면 +600KB, 단 올렸다고 명시)
|
||||
2. 추가분이 `tokens.md` §5 예산 표의 "WebGL/3D 추가분" 안이다(값은 그 표가 정본이다 — 여기서 다시 정하지 않는다). 데모·포트폴리오 브리프에서 상한을 올렸다면 올렸다고 명시한다
|
||||
3. **폴백을 같이 만든다.** 폴백 없이 채택하지 않는다
|
||||
4. 2단계 한 문장 컨셉을 **강화**한다. "있으면 멋있어서"는 이유가 아니다
|
||||
|
||||
|
|
@ -217,12 +218,20 @@ mountScene({
|
|||
|
||||
| 경고 | 내용 |
|
||||
|---|---|
|
||||
| **three 0.186 금지** | `postprocessing`의 peer가 `three >=0.168.0 <0.186.0`. 올리면 포스트프로세싱이 깨진다 → **0.185.1에 핀** |
|
||||
| **R3F v9 = React 19 전용** | peer `react >=19 <19.3`. 19.3+는 아직 지원 없음(v10 alpha) |
|
||||
| **three 상한은 postprocessing이 정한다** | 위 블록은 검증한 조합이다. `postprocessing` 6.39.5부터 peer가 `three >=0.168.0 <0.187.0`이라 0.186.x도 허용된다(확인 2026-10-06). 이 값은 몇 달마다 바뀌므로 채택할 때 `npm view postprocessing peerDependencies`로 다시 확인하고 그 상한 아래에 핀한다. 상한을 넘기면 포스트프로세싱이 깨진다. 올릴 때는 three Migration Guide의 해당 구간을 읽는다 |
|
||||
| **R3F v9 = React 19 전용** | 9.8부터 peer가 `react >=19 <19.4`다(React 19.3 지원). 그 위는 `npm view @react-three/fiber peerDependencies`로 확인한다. v10은 아직 alpha다 |
|
||||
| **three 중복 설치 금지** | 두 벌이면 `instanceof` 검사가 전부 깨진다. `overrides`로 단일화 |
|
||||
| **바닐라 우선** | 페이지가 React가 아니면 R3F를 쓰지 마라. React+R3F만 +75KB다 |
|
||||
| **drei는 named import** | 배럴 임포트는 +100KB다 |
|
||||
|
||||
### 1-b. 렌더러는 WebGLRenderer가 기본이다 (확인 2026-10-06)
|
||||
|
||||
three.js는 두 렌더러를 함께 유지한다. 공식 안내(`threejs.org/docs/llms.txt`)는 `WebGLRenderer`를 "default, mature"로, `WebGPURenderer`(`three/webgpu`)를 TSL 커스텀 셰이더·컴퓨트 셰이더·노드 머티리얼이 필요할 때의 선택으로 적는다. 이 문서의 코드는 전부 WebGLRenderer + GLSL `ShaderMaterial` 기준이다.
|
||||
|
||||
- **바꾸지 않는다**: 셰이더 플레인·이미지 왜곡 같은 이 문서의 패턴. "최신이라서"는 이유가 아니다.
|
||||
- **후보로 올린다**: GPU 컴퓨트가 필요한 대량 파티클·유체, 노드 기반 포스트프로세싱(`RenderPipeline`, r183+). 이때 커스텀 셰이더는 TSL(`three/tsl`)로 쓴다 — 이 문서의 GLSL을 그대로 옮길 수 있다고 가정하지 않는다. `three/webgpu` 빌드 크기는 gzip으로 직접 재서 [tokens.md](tokens.md) §5 예산과 대조한다.
|
||||
- WebGPU 자체는 Baseline Limited다[WEB-BASELINE]. `WebGPURenderer`는 WebGPU가 없으면 WebGL 2 백엔드로 떨어지지만, 두 백엔드의 결과가 같은지는 `forceWebGL: true`로 직접 확인한다. 초기화는 `await renderer.init()`이다. 폴백 규칙(§0 채택 조건 3)은 렌더러와 무관하다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 오늘 사람 잡는 함정
|
||||
|
|
@ -230,7 +239,7 @@ mountScene({
|
|||
| 함정 | 증상 | 고치는 법 |
|
||||
|---|---|---|
|
||||
| **sRGB 자동 지정 제거** [R3F v9] | 이미지가 어둡고 채도가 죽는다 | 컬러 텍스처에 `tex.colorSpace = THREE.SRGBColorSpace`. 노멀/러프니스맵엔 **지정 금지** |
|
||||
| **`Clock` deprecated** [r183+] | 곧 제거됨 | `renderer.setAnimationLoop((t) => …)`의 `t`(ms)를 쓴다 |
|
||||
| **`Clock` deprecated** [r183+] | 곧 제거됨 | `renderer.setAnimationLoop((t) => …)`의 `t`(ms)를 쓴다. 델타·경과 시간이 필요하면 `THREE.Timer`(공식 대체, r179부터 코어) |
|
||||
| **`RGBELoader` 개명** [r180+] | import 실패 | `import { HDRLoader } from 'three/addons/loaders/HDRLoader.js'` |
|
||||
| **ACES 톤매핑 기본값** | 플랫 그라디언트가 뿌옇게 죽는다 | 2D 셰이더면 `toneMapping = THREE.NoToneMapping` (R3F는 `<Canvas flat>`) |
|
||||
| **셰이더 색이 틀림** | hex를 `vec3`에 하드코딩 | 셰이더 안 `vec3`는 **linear 값**이다. 색은 JS에서 `THREE.Color`로 넘겨라 |
|
||||
|
|
@ -666,24 +675,26 @@ void main() {
|
|||
|
||||
---
|
||||
|
||||
## 7. 패턴 카탈로그 — 코드는 `research/three/02-patterns.md`에
|
||||
## 7. 패턴 카탈로그 — 고르고, 공식 API에서 시작한다
|
||||
|
||||
| 효과 | 언제 | 비용 | 문서 |
|
||||
| 효과 | 언제 | 비용 | 구현 출발점 |
|
||||
|---|---|---|---|
|
||||
| SDF 도트 그리드 + 마우스 트레일 | 텍스트가 위에 올라가는 배경. 가독성을 안 해친다 | +0KB · 낮음 | P2 |
|
||||
| 파티클 필드 (Points) | 먼지·별 레이어. 5만 개 이하 | +0KB · 중간 | P3 |
|
||||
| GPGPU 플로우필드 파티클 | 10만 개 이상, 유체 같은 흐름 | +0KB · 높음 | P4 |
|
||||
| InstancedMesh 그리드 웨이브 | 입체 격자 인터랙션. 드로우콜 1 | +0KB · 중간 | P5 |
|
||||
| 스크롤 카메라 리그 (Lenis+ScrollTrigger) | 스크롤텔링. WebGL이 페이지를 지배할 때 | **+45KB** · 낮음 | P6 |
|
||||
| drei ScrollControls | R3F에서 페이지 전체가 캔버스일 때 | +0KB · 낮음 | P7 |
|
||||
| 이미지 전환 (displacement) | 슬라이더·캐러셀 | +0KB · 낮음 | P9 |
|
||||
| 유리·굴절 (MeshTransmissionMaterial) | 중심 오브제. **가장 비싸다** | +0KB · **매우높음** | P10 |
|
||||
| 3D 텍스트 (troika SDF) | WebGL 안의 대형 타이포 | **+55KB** · 낮음 | P11 |
|
||||
| 포스트프로세싱 (bloom/CA/grain) | 발광·필름 룩 | **+32KB** · 중간~높음 | P12 |
|
||||
| GLTF 제품 씬 (DRACO/KTX2) | 제품이 주인공 | **+15KB** + 모델 · 중간 | P13 |
|
||||
| 노이즈 디졸브 리빌 | 등장·전환 | +0KB · 낮음 | P15 |
|
||||
| SDF 도트 그리드 + 마우스 트레일 | 텍스트가 위에 올라가는 배경. 가독성을 안 해친다 | +0KB · 낮음 | §4 코드 B의 풀스크린 셰이더 플레인 + 포인터 좌표 uniform. 도트는 프래그먼트 셰이더의 SDF 원 |
|
||||
| 파티클 필드 (Points) | 먼지·별 레이어. 5만 개 이하 | +0KB · 중간 | `THREE.Points` + `BufferGeometry` + `PointsMaterial`(또는 작은 커스텀 셰이더) |
|
||||
| GPGPU 플로우필드 파티클 | 10만 개 이상, 유체 같은 흐름 | +0KB · 높음 | three 예제 모듈의 `GPUComputationRenderer`(`three/examples/jsm/misc/`) |
|
||||
| InstancedMesh 그리드 웨이브 | 입체 격자 인터랙션. 드로우콜 1 | +0KB · 중간 | `THREE.InstancedMesh` + `setMatrixAt` + `instanceMatrix.needsUpdate` |
|
||||
| 스크롤 카메라 리그 (Lenis+ScrollTrigger) | 스크롤텔링. WebGL이 페이지를 지배할 때 | **+45KB** · 낮음 | GSAP ScrollTrigger 골격([motion.md](motion.md) §4) + Lenis. 카메라는 진행도로만 보간 |
|
||||
| drei ScrollControls | R3F에서 페이지 전체가 캔버스일 때 | +0KB · 낮음 | `@react-three/drei`의 `ScrollControls` + `useScroll` |
|
||||
| 이미지 전환 (displacement) | 슬라이더·캐러셀 | +0KB · 낮음 | §6 코드 D 골격 + 변위 텍스처 uniform으로 두 텍스처 보간 |
|
||||
| 유리·굴절 (MeshTransmissionMaterial) | 중심 오브제. **가장 비싸다** | +0KB · **매우높음** | drei `MeshTransmissionMaterial` — §8의 5번 제약을 먼저 읽는다 |
|
||||
| 3D 텍스트 (troika SDF) | WebGL 안의 대형 타이포 | **+55KB** · 낮음 | `troika-three-text`의 `Text` |
|
||||
| 포스트프로세싱 (bloom/CA/grain) | 발광·필름 룩 | **+32KB** · 중간~높음 | `postprocessing` 패키지의 `EffectComposer` + 효과별 `EffectPass` |
|
||||
| GLTF 제품 씬 (DRACO/KTX2) | 제품이 주인공 | **+15KB** + 모델 · 중간 | `GLTFLoader` + `DRACOLoader` + `KTX2Loader`(디코더 파일은 self-host) |
|
||||
| 노이즈 디졸브 리빌 | 등장·전환 | +0KB · 낮음 | 셰이더에서 노이즈 값과 진행도 uniform을 비교해 `discard`, 경계에 발광 띠 |
|
||||
|
||||
**+45KB 이상 항목은 예산 계산에 반드시 넣어라.** 스크롤 리그(GSAP+Lenis)는 **한 줄도 안 썼는데 +200KB 예산의 22%를 먹고 시작한다.** 여기에 troika(+55KB)와 포스트프로세싱(+32KB)을 얹으면 +132KB — 예산의 66%가 라이브러리로만 사라지고 실제 씬에 쓸 몫은 68KB뿐이다.
|
||||
**+45KB 이상 항목은 예산 계산에 반드시 넣어라.** 번들 크기는 조사 시점(2026-08) 값이므로 채택할 때 build 후 gzip으로 다시 잰다. 예: WebGL 추가분 예산이 +200KB(`tokens.md` §5 기본값)라면 스크롤 리그(GSAP+Lenis)는 **한 줄도 안 썼는데 예산의 22%를 먹고 시작한다.** 여기에 troika(+55KB)와 포스트프로세싱(+32KB)을 얹으면 +132KB — 예산의 66%가 라이브러리로만 사라지고 실제 씬에 쓸 몫은 68KB뿐이다.
|
||||
|
||||
이 표의 구현은 이 문서 밖의 코드 저장소에 의존하지 않는다. 버전은 §1 핀을 따르고, API는 three.js·drei 공식 문서와 예제(threejs.org/examples)에서 확인한다. 외부 예제 코드를 옮길 때는 라이선스를 확인하고, 그대로 붙이기 전에 §2 함정·§8 성능 항목과 대조한다.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -735,25 +746,22 @@ WebGL 랜딩페이지가 42점에서 98점으로 간 실측 사례의 핵심은
|
|||
|
||||
---
|
||||
|
||||
## 11. 채택 체크리스트
|
||||
## 11. 채택 체크리스트 (구현 중 자기 점검 — 납품 판정은 preflight.md X4)
|
||||
|
||||
5단계 프리플라이트에서 그대로 돌린다. **하나라도 실패하면 채택 취소이거나 4-3 복귀다.**
|
||||
채택과 구현 중에 확인한다. **하나라도 실패하면 채택 취소이거나 4-3 복귀다.** 납품 판정은 [preflight.md](preflight.md) X4가 하고, 캔버스를 끈 상태의 완성은 M-A, 감소 모션은 A7, WebGL 추가분 실측은 P2가 판정한다.
|
||||
|
||||
- [ ] §0 채택 조건 4개를 통과했다 (CSS/SVG로 안 되고, 예산 내이고, 폴백이 있고, 컨셉을 강화한다)
|
||||
- [ ] WebGL 추가분을 **실측**했다(build 후 gzip). 예산 내이거나, 올린 이유를 명시했다
|
||||
- [ ] `three`가 **초기 번들에 없다.** Network 탭에서 LCP 이후 요청됨을 확인했다
|
||||
- [ ] 셰이더의 색·시간이 3단계 토큰(`--accent`, `--dur-*`)에서 파생됐다. hex 하드코딩이 없다
|
||||
- [ ] 컬러 텍스처에 `colorSpace = SRGBColorSpace`를 지정했다
|
||||
- [ ] DPR을 클램프했다. `IntersectionObserver` + `visibilitychange`로 렌더를 멈춘다
|
||||
- [ ] `renderer.info.render.calls` ≤ 150(데스크톱) / 60(모바일)
|
||||
- [ ] 캔버스를 `display:none`으로 껐을 때 페이지가 완성돼 있다 (프리플라이트 0-A와 동일)
|
||||
- [ ] `prefers-reduced-motion`에서 애니메이션이 멈추고 폴백이 보인다
|
||||
- [ ] 캔버스에 `aria-hidden="true"`, 히어로 텍스트는 DOM에 있다
|
||||
- [ ] 컨텍스트 로스를 강제(`WEBGL_lose_context.loseContext()`)했을 때 크래시 없이 폴백이 뜬다
|
||||
- [ ] 라우트 이탈 시 `dispose()`가 호출된다. 3분 실행 후 `renderer.info.memory`가 증가하지 않는다
|
||||
- [ ] 실기기(중저가 안드로이드)에서 30fps 이상이다
|
||||
- [ ] 6단계 `design.md`에 **채택한 이펙트 · 실측 추가분(KB) · 그 폴백**을 적었다
|
||||
- §0 채택 조건 4개를 통과했다 (CSS/SVG로 안 되고, 예산 내이고, 폴백이 있고, 컨셉을 강화한다)
|
||||
- `three`가 **초기 번들에 없다.** Network 탭에서 LCP 이후 요청됨을 확인했다
|
||||
- 셰이더의 색·시간이 3단계 토큰(`--accent`, `--dur-*`)에서 파생됐다. hex 하드코딩이 없다
|
||||
- 컬러 텍스처에 `colorSpace = SRGBColorSpace`를 지정했다
|
||||
- DPR을 클램프했다. `IntersectionObserver` + `visibilitychange`로 렌더를 멈춘다
|
||||
- `renderer.info.render.calls` ≤ 150(데스크톱) / 60(모바일)
|
||||
- 캔버스에 `aria-hidden="true"`, 히어로 텍스트는 DOM에 있다
|
||||
- 컨텍스트 로스를 강제(`WEBGL_lose_context.loseContext()`)했을 때 크래시 없이 폴백이 뜬다
|
||||
- 라우트 이탈 시 `dispose()`가 호출된다. 3분 실행 후 `renderer.info.memory`가 증가하지 않는다
|
||||
- 실기기(중저가 안드로이드)에서 30fps 이상이다
|
||||
- 6단계 `design.md`에 **채택한 이펙트 · 실측 추가분(KB) · 그 폴백**을 적었다
|
||||
|
||||
하드 게이트 중 셋이 여기서 자주 걸린다: **#1**(셰이더 hex 하드코딩) · **#8**(`transform`/`opacity` 외 애니메이션 — 캔버스 페이드는 `opacity`로) · **#10**(`scroll` 리스너 — rAF에서 `scrollY`를 읽어라).
|
||||
여기서 자주 걸리는 것은 셋이다: 셰이더 hex 하드코딩(토큰 위생, preflight M-D) · `transform`/`opacity` 외 애니메이션(캔버스 페이드는 `opacity`로, [motion.md](motion.md) §2) · `scroll` 리스너(rAF에서 `scrollY`를 읽어라, motion.md §5 게이트 직결 항목).
|
||||
|
||||
> 근거: research/three/01~04 (조사일 2026-08-20)
|
||||
> 근거: designpaca 저장소의 three.js 조사 노트(조사일 2026-08-20). 설치본에는 포함되지 않으며, 이 문서의 결론만으로 작업할 수 있게 썼다.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue