MotionCraft Builder
이 스킬은 에이전트가 소규모 스튜디오에서 수상작을 출시하는 방식으로 모션 웹사이트를 제작하도록 가르칩니다: 하나의 아이디어, 글로 작성된 계획, 회색 상자로 동작하는 메커니즘, 그다음 드레싱, 그리고 엄격한 검토와 수정 패스. 모든 결과물은 참조 문서에 기반합니다. 이 파일이 워크플로우입니다.
기본 스택: 의미론적 HTML, CSS, 바닐라 자바스크립트 모듈, 가져오기 맵을 통한 three.js 0.170, 빌드 단계 없음. GSAP나 Lenis는 레시피에서 필요한 경우에만 추가합니다. 모든 페이지는 WebGL이 꺼진 상태에서도 작동하고, 읽히고, 전환되어야 합니다.
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
}
}
</script>
워크플로우
다음 단계를 순서대로 실행하세요. 효과로 건너뛰지 마세요. 각 단계에는 다음 단계로 넘어가기 전에 고객에게 보여줄 수 있는 결과물이 있습니다.
1단계. 하나의 아이디어 찾기
브리프를 읽고 고객의 실제 세계를 나열하세요: 그들이 다루는 물건, 출력하는 문서, 현장의 소리, 직원들이 하루 종일 사용하는 동사. 그런 다음 한 문장을 작성하세요:
[브랜드]은(는) 사용자가 [그들의 세계에 있는 물건]을(를) [동사]하면 [그들이 판매하는 가치]를(를) 보여주는 사이트입니다.
가상 브랜드 예시:
- Cinderford Coffee Roasters: 사용자가 하나의 원두를 로스팅 과정(녹색 → 퍼스트 크랙 → 풀 시티)을 스크롤하면, 색상이 변함에 따라 로스트 로그에 풍미 노트가 인쇄됩니다.
- Lakemont Bike Fitters: 사용자가 라이더의 안장 높이를 드래그하면 실시간 측면 보기에 무릎 각도 아크가 다시 그려집니다.
- Halvorsen Tide Charters: 사용자가 조석 시계를 스크롤하면 보트가 실제 부두에 맞춰 오르내립니다.
아이디어 규칙 (참조: 제작 규칙 1~3 참조):
- 하나의 물건, 하나의 동사, 하나의 세계. "그리고"를 두 번 사용해야 한다면 두 개의 아이디어입니다. 하나를 선택하세요.
- 은유는 고객의 세계에서 와야 하며, 절대 트렌드에서 가져오지 않습니다. 정비소는 수리 지시서로 말하고, 식료품점은 주간 전단지로, 교통 기관은 노선도로 말합니다.
- 강조 색상은 하나의 역할만 부여하세요. 빨간색이 "결함 발견"을 의미한다면, 페이지의 다른 어떤 곳에서도 다른 의미로 사용하지 마세요.
- 로더, 커서, 내비게이션, 호버 상태, 전환, 404 페이지 모두 동일한 언어를 사용합니다.
결과물: 문장, 동사, 강조 색상과 그 의미, 그리고 각각이 탈락된 이유를 한 줄씩 적은 세 개의 기각된 아이디어.
2단계. 페이지 아키텍처 선택
page-architecture.md를 열고 아이디어와 동일한 세계에서 나온 내비게이션 아키텍처, 히어로 구성 및 섹션 시퀀스를 선택합니다. 이전 프로젝트의 뼈대를 절대 재사용하지 마세요. 선택한 내용을 프로젝트 README에 기록하여 다음 빌드에서 피할 수 있도록 합니다.
결과물: 사용한 명명된 패턴을 포함한 짧은 아키텍처 카드(내비게이션, 히어로, 섹션 목록, 푸터, 404).
3단계. 비트 시트 작성
코드를 작성하기 전에 시나리오를 먼저 작성하세요. beat-sheet.md의 템플릿을 사용하세요. 각 비트는 스크롤 범위, 방문자가 보는 내용, 움직이는 요소, 카피, 카메라, 전화기 프레이밍, 축소 모션 버전 및 비용을 명시합니다. 실제 예제는 요구되는 상세 수준을 보여줍니다.
결과물: 작성된 비트 시트. 이후의 모든 결정은 이 비트 시트에 대해 검증됩니다. 섹션이 1단계의 문장을 제공하지 않으면, 비용이 적게 드는 여기서 삭제하세요.
4단계. 메커니즘 회색 상자 구현
자리 표시자 지오메트리와 자리 표시자 카피로 모션을 구축하세요. 이 단계는 어떤 에셋이 존재하기 전에 타이밍, 카메라 및 상호작용을 증명합니다.
- DOM을 먼저 작성하세요: 실제 제목, 실제 카피 블록, 실제 버튼과 폼, 각 비트당 하나의
<section class="shot">. 스크립트가 꺼진 상태에서 페이지가 위에서 아래로 읽혀야 합니다. - DOM 뒤에 하나의 고정 캔버스와 하나의 렌더 루프를 추가하세요. 스크롤, 포인터, 오디오 및 렌더는 해당 루프를 공유합니다(규칙 8).
- 스크롤을 비트를 세는 단일 부동 소수점
t에 매핑하고, 스무드스텝 홀드가 있는 샷 목록을 통해 카메라를 이동시킵니다(스크롤 및 카메라 레시피). - 모든 객체에 회색 상자, 구체 및 평면을 사용하세요. 스토리가 회색으로 작동하지 않으면 드레싱이 저장하지 않습니다.
- 일시 중지 컨트롤과 축소 모션 분기를 지금 추가하세요. 마지막이 아닌 지금입니다(상호작용 레시피).
그레이박스 시작 코드. 페이지에는 위의 가져오기 맵, 고정 캔버스 및 SHOTS의 각 항목당 하나의 .shot 섹션이 필요합니다:
<style>
#gl { position: fixed; inset: 0; width: 100%; height: 100%; z-index: -1; display: block; }
.shot { min-height: 100svh; display: grid; align-items: center; padding: 16px 6vw; }
.no-gl #gl { display: none; }
</style>
<canvas id="gl" aria-hidden="true"></canvas>
<main>
<section class="shot" id="top"><h1>Headline from beat 0</h1></section>
<section class="shot" id="detail"><h2>Beat 1</h2></section>
<section class="shot" id="side"><h2>Beat 2</h2></section>
</main>
<script type="module" src="main.js"></script>
main.js:
import * as THREE from "three";
const canvas = document.getElementById("gl");
let renderer = null;
try { renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: "high-performance" }); }
catch { document.body.classList.add("no-gl"); } // DOM 페이지는 독립적으로 작동합니다
if (renderer) {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111114);
scene.add(new THREE.HemisphereLight(0xffffff, 0x222233, 1.4));
const camera = new THREE.PerspectiveCamera(35, 1, 0.1, 100);
// 비트 시트가 승인될 때까지 히어로 객체의 회색 대체물
scene.add(new THREE.Mesh(new THREE.BoxGeometry(2, 1, 1.2), new THREE.MeshStandardMaterial({ color: 0x8a8a8a, roughness: 0.6 })));
const V = (x, y, z) => new THREE.Vector3(x, y, z);
const SHOTS = [
{ pos: V(4, 3, 8), at: V(-1.5, 0, 0) }, // 히어로: 객체는 헤드라인 오른쪽
{ pos: V(0, 9, 0.1), at: V(0, 0, 0) }, // 탑뷰 디테일
{ pos: V(-6, 1.2, 3), at: V(1.5, 0, 0) }, // 낮은 측면, 카피는 오른쪽
];
const sections = [...document.querySelectorAll(".shot")];
let centers = [];
const measure = () => { centers = sections.map((s) => s.offsetTop + s.offsetHeight / 2); };
const smooth = (a, b, x) => { const k = Math.min(1, Math.max(0, (x - a) / (b - a))); return k * k * (3 - 2 * k); };
function scrollT() {
const c = scrollY + innerHeight / 2;
if (!centers.length || c <= centers[0]) return 0;
for (let i = 0; i < centers.length - 1; i++) if (c < centers[i + 1]) return i + (c - centers[i]) / (centers[i + 1] - centers[i]);
return centers.length - 1;
}
function resize() {
renderer.setPixelRatio(Math.min(devicePixelRatio, innerWidth < 760 ? 1.5 : 1.75));
renderer.setSize(innerWidth, innerHeight, false);
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
measure();
}
addEventListener("resize", resize);
document.fonts?.ready.then(measure);
resize();
const pos = new THREE.Vector3(), at = new THREE.Vector3();
let raf = 0;
function frame() {
raf = requestAnimationFrame(frame);
const t = scrollT(), i0 = Math.floor(t), i1 = Math.min(SHOTS.length - 1, i0 + 1);
const f = smooth(0.15, 0.85, t - i0); // 각 샷에서 유지, 중간에서 이동
pos.lerpVectors(SHOTS[i0].pos, SHOTS[i1].pos, f);
at.lerpVectors(SHOTS[i0].at, SHOTS[i1].at, f);
camera.position.copy(pos);
camera.lookAt(at);
renderer.render(scene, camera);
}
raf = requestAnimationFrame(frame);
document.addEventListener("visibilitychange", () => {
cancelAnimationFrame(raf); // 두 개의 루프가 실행되지 않도록 함
if (!document.hidden) raf = requestAnimationFrame(frame);
});
}
4단계 종료 기준: 모든 비트를 앞뒤로 스크롤했을 때 카메라가 양방향 동일하게 도착합니다. 모든 샷은 해당 카피를 위한 공간을 남깁니다. 전화기 프레이밍이 390 x 844에서 작동합니다. 일시 중지 컨트롤이 모든 연속 모션을 중지합니다. WebGL이 비활성화된 상태에서 페이지가 읽힙니다.
5단계. 드레싱하기
이제 회색을 실제 세계로 교체합니다. 기법 인덱스에서 레시피를 가져옵니다. 각 레이어 후 비트 시트를 확인하면서 다음 순서로 드레싱합니다:
- 형태: 프리미티브, glTF, 필름 또는 심도 사진으로 제작된 히어로 객체.
- 조명: 키, 림 및 하나의 동기가 있는 강조 조명. 표면에 놓인 모든 것 아래에 접촉 그림자를 추가합니다.
- 재질 및 텍스처: 사진 텍스처보다 먼저 캔버스로 그린 레이블과 전면판. 선명하고 작으며 편집 가능합니다.
- 타이포그래피: 최대 두 개의 서체. 헤드라인 모션은 페이지의 리듬에 연결되며, 절대 일반적인 페이드 인이 아닙니다.
- 사운드: 선택 사항, 옵트인, Web Audio로 합성하여 바이트 비용이 들지 않습니다.
- 작은 보상: 하나의 숨겨진 제스처, 유휴 상태, 브랜드 언어의 404 페이지.
아무도 눈치채지 못하는 물리 효과보다는 상호작용에 프레임 시간을 사용하세요. 비용이 많이 드는 것은 가짜로 만듭니다(규칙 17).
6단계. 검토 및 수정
검토하기 전까지는 완료되지 않았습니다. 페이지를 motion-review 스킬(../motion-review/SKILL.md)에 전달하여 평가 기준, QA 체크리스트 및 데스크톱/전화기 크기의 섹션 스크린샷을 받고, motion-analytics 스킬(../motion-analytics/SKILL.md)에 프레임 시간, 무게 및 참조 비교 평가를 위해 전달하세요. 그런 다음 가장 심각한 문제를 먼저 수정하고 다시 검토합니다. 완료된 것으로 간주하는 모든 작업에 대해 최소 두 번의 전체 검토 및 수정 패스를 수행합니다.
공식 검토 전 빠른 자체 확인:
- 아래로 스크롤한 후 다시 맨 위로 스크롤합니다. 모든 리빌이 깔끔하게 역전되어야 합니다.
- 모션 중간에 일시 중지 컨트롤을 누릅니다. 모든 연속적인 움직임이 중지되어야 하며, 페이지는 여전히 구성된 상태로 보여야 합니다.
- OS에서 축소 모션을 켭니다. 페이지가 스틸 이미지로도 동일한 스토리를 전달해야 합니다.
- 콘솔을 엽니다. 오류가 없어야 하며, 사용자가 발생시킨 경고도 없어야 합니다.
- 전화기 너비 390px: 11px 미만의 텍스트, 44px 미만의 탭 대상, 가로 스크롤이 없어야 합니다.
7단계. 출시
페이지 폴더(index.html, style.css, main.js 및 모듈, assets/), 한 문장 아이디어가 포함된 README, 아키텍처 카드, 비트 시트, 라이선스가 포함된 에셋 목록, 그리고 변경할 가치가 있는 튜닝 값을 제공하세요. 전체 페이지를 10MB 미만으로 유지하세요.
확고한 원칙
- 아래에는 실제 HTML이 있습니다. 콘텐츠, 내비게이션 및 폼은 DOM에 있습니다. WebGL은
no-gl폴백이 있는 향상된 기능입니다. - 하나의 클록. 프레임별 값을 프레임워크 상태에 절대 넣지 마세요. 일반 변수, 유니폼 및 CSS 사용자 정의 속성을 사용하세요.
- 5초 이상 실행되는 모든 모션에 대해 표시 가능한 일시 중지 컨트롤과 완전한
prefers-reduced-motion분기가 있어야 합니다. - 스크롤은 절대 하이재킹되지 않습니다. 기본 스크롤바가 작동하고, 키보드 스크롤이 작동하며, 앵커 링크가 점프합니다.
- 데모에는 가상의 브랜드와 창작된 사람들을 사용하세요. 참조 사이트의 코드, 텍스트, 에셋 또는 히어로 컨셉을 절대 복사하지 마세요. 아이디어를 연구하고 직접 작성하세요.
- 페이지의 모든 숫자(가격, 시간, 통계)는 그럴듯하고 일관되며 고객의 로케일에 맞아야 합니다.
참조 맵
| 파일 | 사용 시기 |
|---|---|
| references/craft-rules.md | 취향 관련 결정을 내릴 때. 구체적인 값이 포함된 30개의 규칙. |
| references/beat-sheet.md | 스토리 계획 시. 템플릿 및 실제 예제 포함. |
| references/page-architecture.md | 템플릿 획일성을 피하면서 내비게이션, 히어로 및 섹션을 선택할 때. |
| references/techniques/INDEX.md | 필요, 비용 또는 와우 요소별로 레시피를 찾을 때. |
| techniques/scroll-and-camera.md | 스크롤 매핑, 카메라 샷, 전화기 프레이밍, 모드 값. |
| techniques/scene-craft.md | three.js 드레싱: 콜아웃, 모프, 도시, 하늘, 레이블, 그림자. |
| techniques/film-and-photo.md | 비디오 히어로, 루프, 필름 타이밍 카피, 추적 핫스팟, 심도 사진. |
| techniques/sound-and-rhythm.md | Web Audio 보이스, 스케줄링, 비트 동기화 비주얼. |
| techniques/interaction-and-access.md | 일시 중지 컨트롤, 앵커, 홀드 게이트, 드래그 뷰, 품질 계층. |
RegesCore AI MotionCraft Skills의 일부, LIKE A KING INC 제작. 무료 모션 템플릿 및 전체 카탈로그는 animatedwebs.com에서 확인하세요.