HyperFrames
HTML이 비디오의 진실 공급원(source of truth)입니다. 컴포지션은 타이밍을 위한 data-* 속성, 애니메이션을 위한 GSAP 타임라인, 그리고 모양을 위한 CSS가 포함된 HTML 파일입니다. 프레임워크가 클립 가시성, 미디어 재생 및 타임라인 동기화를 처리합니다.
접근 방식
HTML을 작성하기 전에 높은 수준에서 생각하세요:
- 무엇 — 시청자는 무엇을 경험해야 할까요? 내러티브 아크, 핵심 순간, 감정적 비트를 식별하세요.
- 구조 — 몇 개의 컴포지션이 있고, 그중 서브 컴포지션인지 인라인인지, 어떤 트랙이 무엇(비디오, 오디오, 오버레이, 캡션)을 담당하는지 확인하세요.
- 타이밍 — 어떤 클립이 지속 시간을 결정하고, 전환이 어디에 위치하며, 페이싱은 어떻게 될지 결정하세요.
- 레이아웃 — 먼저 최종 상태를 구축하세요. 아래 '애니메이션 전 레이아웃'을 참조하세요.
- 애니메이션 — 그런 다음 아래 규칙을 사용하여 모션을 추가하세요.
작은 편집(색상 변경, 타이밍 조정, 요소 하나 추가)의 경우 바로 규칙으로 건너뛰세요.
시각적 아이덴티티 게이트
다음 순서로 확인하세요:
- 프로젝트에 DESIGN.md가 있습니까? → 읽으세요. 정확한 색상, 글꼴, 모션 규칙 및 '하지 말아야 할 것' 제약 조건을 사용하세요.
- visual-style.md가 있습니까? → 읽으세요.
style_prompt_full및 구조화된 필드를 적용하세요. (참고:visual-style.md는 프로젝트별 파일입니다.visual-styles.md는 8개의 명명된 프리셋이 있는 스타일 라이브러리입니다 — 다른 파일입니다.) - 사용자가 스타일을 지정했습니까? (예: '스위스 펄스', '어둡고 기술적인', '럭셔리 브랜드') → visual-styles.md에서 8개의 명명된 프리셋을 읽으세요. 최소한의 DESIGN.md를 생성하세요:
## 스타일 프롬프트(한 문단),## 색상(역할이 있는 3-5개의 16진수 값),## 타이포그래피(1-2개의 글꼴 패밀리),## 하지 말아야 할 것(3-5개의 안티패턴). - 위 항목에 해당하는 것이 없습니까? → HTML을 작성하기 전에 3가지 질문을 하세요:
- 분위기는 무엇인가요? (폭발적 / 영화적 / 유동적 / 기술적 / 혼란스러움 / 따뜻함)
- 밝은 캔버스인가요, 어두운 캔버스인가요?
- 특정 브랜드 색상, 글꼴 또는 시각적 참조가 있습니까? 그런 다음 답변에서 최소한의 DESIGN.md를 생성하세요.
모든 컴포지션은 그 팔레트와 타이포그래피를 DESIGN.md, visual-style.md 또는 명시적인 사용자 방향으로 추적해야 합니다. #333, #3b82f6 또는 Roboto를 사용하고 있다면 — 이 단계를 건너뛴 것입니다.
모션 기본값, 크기 조정, 진입 패턴 및 이징에 대해서는 house-style.md를 따르세요. 하우스 스타일은 요소가 어떻게 움직이는지 처리합니다. DESIGN.md는 요소가 어떻게 보이는지 처리합니다.
애니메이션 전 레이아웃
각 요소를 가장 눈에 띄는 순간 — 완전히 진입하고, 올바르게 배치되며, 아직 종료되지 않은 프레임 — 에 있어야 할 위치에 배치하세요. 먼저 정적 HTML+CSS로 작성하세요. 아직 GSAP는 사용하지 마세요.
이것이 중요한 이유: 요소를 애니메이션 시작 상태(화면 밖, 0으로 축소, 투명도 0)에 배치하고 도착해야 할 위치로 트윈하면, 최종 레이아웃을 추측하는 것입니다. 겹침은 비디오가 렌더링될 때까지 보이지 않습니다. 먼저 최종 상태를 구축하면 모션을 추가하기 전에 레이아웃 문제를 보고 수정할 수 있습니다.
프로세스
- 각 장면의 히어로 프레임을 식별하세요 — 대부분의 요소가 동시에 보이는 순간입니다. 이것이 구축할 레이아웃입니다.
- 해당 프레임에 대한 정적 CSS를 작성하세요.
.scene-content컨테이너는width: 100%; height: 100%; padding: Npx;와display: flex; flex-direction: column; gap: Npx; box-sizing: border-box를 사용하여 전체 장면을 채워야 합니다. 패딩을 사용하여 콘텐츠를 안쪽으로 밀어 넣으세요 — 콘텐츠 컨테이너에position: absolute; top: Npx를 절대 사용하지 마세요. 절대 위치 콘텐츠 컨테이너는 콘텐츠가 남은 공간보다 높을 때 오버플로됩니다.position: absolute는 장식용으로만 예약하세요. gsap.from()을 사용하여 진입 애니메이션을 추가하세요 — 화면 밖/보이지 않는 상태에서 CSS 위치로 부터 애니메이션합니다. CSS 위치가 진실입니다. 트윈은 그곳에 도달하는 과정을 설명합니다.gsap.to()를 사용하여 종료 애니메이션을 추가하세요 — CSS 위치에서 화면 밖/보이지 않는 상태 로 애니메이션합니다.
예시
/* scene-content가 장면을 채우고, 패딩이 콘텐츠를 배치합니다 */
.scene-content {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
padding: 120px 160px;
gap: 24px;
box-sizing: border-box;
}
.title {
font-size: 120px;
}
.subtitle {
font-size: 42px;
}
/* 컨테이너는 모든 장면 크기(1920x1080, 1080x1920 등)를 채웁니다.
패딩이 콘텐츠를 배치합니다. Flex + gap이 간격을 처리합니다. */
잘못된 예 — 하드코딩된 차원과 절대 위치:
.scene-content {
position: absolute;
top: 200px;
left: 160px;
width: 1920px;
height: 1080px;
display: flex; /* ... */
}
// 3단계: 해당 위치로 애니메이션 진입
tl.from(".title", { y: 60, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
tl.from(".subtitle", { y: 40, opacity: 0, duration: 0.5, ease: "power3.out" }, 0.2);
tl.from(".logo", { scale: 0.8, opacity: 0, duration: 0.4, ease: "power2.out" }, 0.3);
// 4단계: 해당 위치에서 애니메이션 종료
tl.to(".title", { y: -40, opacity: 0, duration: 0.4, ease: "power2.in" }, 3);
tl.to(".subtitle", { y: -30, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.1);
tl.to(".logo", { scale: 0.9, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.2);
요소가 시간에 따라 공간을 공유할 때
같은 영역에서 요소 A가 종료된 후 요소 B가 진입하는 경우, 두 요소 모두 각각의 히어로 프레임에 대해 올바른 CSS 위치를 가져야 합니다. 타임라인 순서는 시각적으로 공존하지 않음을 보장합니다. 그러나 레이아웃 단계를 건너뛰면 타이밍 오류로 인해 실수로 겹치는 경우를 발견하지 못할 수 있습니다.
의도적인 겹침으로 간주되는 것
계층화된 효과(텍스트 뒤의 글로우, 그림자 요소, 배경 패턴)와 z-스택 디자인(카드 스택, 깊이 레이어)은 의도적인 것입니다. 레이아웃 단계는 의도하지 않은 겹침 — 두 개의 제목이 서로 위에 떨어지거나, 통계가 레이블을 가리거나, 콘텐츠가 프레임 밖으로 흘러나가는 경우 — 을 잡기 위한 것입니다.
데이터 속성
모든 클립
| 속성 | 필수 | 값 |
|---|---|---|
id | 예 | 고유 식별자 |
data-start | 예 | 초 단위 또는 클립 ID 참조 ("el-1", "intro + 2") |
data-duration | img/div/컴포지션에 필수 | 초 단위. 비디오/오디오는 미디어 지속 시간을 기본값으로 사용 |
data-track-index | 예 | 정수. 같은 트랙 클립은 겹칠 수 없습니다. |
data-media-start | 아니요 | 소스의 트림 오프셋(초 단위) |
data-volume | 아니요 | 0-1 (기본값 1) |
data-track-index는 시각적 레이어링에 영향을 주지 않습니다 — CSS z-index를 사용하세요.
컴포지션 클립
| 속성 | 필수 | 값 |
|---|---|---|
data-composition-id | 예 | 고유 컴포지션 ID |
data-start | 예 | 시작 시간 (루트 컴포지션: "0" 사용) |
data-duration | 예 | GSAP 타임라인 지속 시간보다 우선 |
data-width / data-height | 예 | 픽셀 차원 (1920x1080 또는 1080x1920) |
data-composition-src | 아니요 | 외부 HTML 파일 경로 |
컴포지션 구조
data-composition-src를 통해 로드되는 서브 컴포지션은 <template> 래퍼를 사용합니다. 독립 실행형 컴포지션(메인 index.html)은 <template>을 사용하지 않습니다 — data-composition-id div를 직접 <body>에 배치합니다. 독립 실행형 파일에 <template>을 사용하면 브라우저에서 모든 콘텐츠가 숨겨져 렌더링이 중단됩니다.
서브 컴포지션 구조:
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<!-- 콘텐츠 -->
<style>
[data-composition-id="my-comp"] {
/* 범위가 지정된 스타일 */
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// 트윈...
window.__timelines["my-comp"] = tl;
</script>
</div>
</template>
루트에서 로드: <div id="el-1" data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="10" data-track-index="1"></div>
비디오 및 오디오
비디오는 muted playsinline이어야 합니다. 오디오는 항상 별도의 <audio> 요소입니다:
<video
id="el-v"
data-start="0"
data-duration="30"
data-track-index="0"
src="video.mp4"
muted
playsinline
></video>
<audio
id="el-a"
data-start="0"
data-duration="30"
data-track-index="2"
src="video.mp4"
data-volume="1"
></audio>
타임라인 계약
- 모든 타임라인은
{ paused: true }로 시작합니다 — 플레이어가 재생을 제어합니다 - 모든 타임라인을 등록하세요:
window.__timelines["<composition-id>"] = tl - 프레임워크가 서브 타임라인을 자동으로 중첩합니다 — 수동으로 추가하지 마세요
- 지속 시간은 GSAP 타임라인 길이가 아닌
data-duration에서 가져옵니다 - 지속 시간을 설정하기 위해 빈 트윈을 만들지 마세요
규칙 (협상 불가)
결정론적: Math.random(), Date.now() 또는 시간 기반 로직을 사용하지 마세요. 의사 난수 값이 필요하면 시드된 PRNG(예: mulberry32)를 사용하세요.
GSAP: 시각적 속성(opacity, x, y, scale, rotation, color, backgroundColor, borderRadius, 변환)에만 애니메이션을 적용하세요. visibility, display를 애니메이션하거나 video.play()/audio.play()를 호출하지 마세요.
애니메이션 충돌: 여러 타임라인에서 동시에 같은 요소의 같은 속성에 애니메이션을 적용하지 마세요.
repeat: -1 금지: 무한 반복 타임라인은 캡처 엔진을 중단시킵니다. 컴포지션 지속 시간에서 정확한 반복 횟수를 계산하세요: repeat: Math.ceil(duration / cycleDuration) - 1.
동기식 타임라인 구성: async/await, setTimeout 또는 Promise 내부에서 타임라인을 구성하지 마세요. 캡처 엔진은 페이지 로드 후 window.__timelines를 동기식으로 읽습니다. 글꼴은 컴파일러에 의해 포함되므로 즉시 사용 가능합니다 — 글꼴 로딩을 기다릴 필요가 없습니다.
절대 하지 말 것:
window.__timelines등록을 잊지 마세요- 오디오에 비디오를 사용하지 마세요 — 항상 음소거된 비디오 + 별도의
<audio> - 비디오를 시간이 지정된 div 안에 중첩하지 마세요 — 시간이 지정되지 않은 래퍼를 사용하세요
data-layer(data-track-index사용) 또는data-end(data-duration사용)를 사용하지 마세요- 비디오 요소 차원에 애니메이션을 적용하지 마세요 — 래퍼 div에 애니메이션을 적용하세요
- 미디어에서 play/pause/seek을 호출하지 마세요 — 프레임워크가 재생을 소유합니다
data-composition-id없이 최상위 컨테이너를 만들지 마세요- 타임라인 또는 트윈에
repeat: -1을 사용하지 마세요 — 항상 유한 반복 - 타임라인을 비동기식으로 구성하지 마세요 (async, setTimeout, Promise 내부)
- 이후 장면의 클립 요소에
gsap.set()을 사용하지 마세요 — 페이지 로드 시 DOM에 존재하지 않습니다. 대신 클립의data-start시간 이후 또는 그 시간에 타임라인 내부에서tl.set(selector, vars, timePosition)을 사용하세요. - 콘텐츠 텍스트에
<br>을 사용하지 마세요 — 강제 줄바꿈은 실제 렌더링된 글꼴 너비를 고려하지 않습니다. 자연스럽게 줄바꿈되는 텍스트 +<br>은 원치 않는 추가 줄바꿈을 생성하여 겹침을 유발합니다. 대신max-width를 통해 텍스트를 줄바꿈하세요. 예외: 각 단어가 의도적으로 자체 라인에 있는 짧은 표시 제목(예: 130px에서 "THE\nIMMORTAL\nGAME").
장면 전환 (협상 불가)
모든 다중 장면 컴포지션은 다음 규칙을 모두 따라야 합니다. 하나라도 위반하면 깨진 컴포지션입니다.
- 항상 장면 간 전환을 사용하세요. 점프 컷은 금지됩니다. 예외 없음.
- 항상 모든 장면에 진입 애니메이션을 사용하세요. 모든 요소는
gsap.from()을 통해 애니메이션으로 진입합니다. 어떤 요소도 완전히 형성된 상태로 나타날 수 없습니다. 장면에 5개의 요소가 있으면 5개의 진입 트윈이 필요합니다. - 마지막 장면을 제외하고는 종료 애니메이션을 절대 사용하지 마세요. 즉, 전환이 실행되기 전에 투명도를 0으로, y를 화면 밖으로, scale을 0으로 또는 다른 '나가기' 애니메이션으로 만드는
gsap.to()가 없어야 합니다. 전환 자체가 종료입니다. 나가는 장면의 콘텐츠는 전환이 시작되는 순간 완전히 보여야 합니다. - 마지막 장면만: 마지막 장면은 요소를 페이드 아웃할 수 있습니다(예: 검은색으로 페이드). 이것이
gsap.to(..., { opacity: 0 })가 허용되는 유일한 장면입니다.
잘못된 예 — 전환 전 종료 애니메이션:
// 금지됨 — 전환이 사용할 수 있기 전에 장면을 비웁니다
tl.to("#s1-title", { opacity: 0, y: -40, duration: 0.4 }, 6.5);
tl.to("#s1-subtitle", { opacity: 0, duration: 0.3 }, 6.7);
// 전환이 빈 프레임에서 실행됩니다
올바른 예 — 진입만, 전환이 종료를 처리:
// 장면 1 진입 애니메이션
tl.from("#s1-title", { y: 50, opacity: 0, duration: 0.7, ease: "power3.out" }, 0.3);
tl.from("#s1-subtitle", { y: 30, opacity: 0, duration: 0.5, ease: "power2.out" }, 0.6);
// 종료 트윈 없음 — 7.2초의 전환이 장면 변경을 처리합니다
// 장면 2 진입 애니메이션
tl.from("#s2-heading", { x: -40, opacity: 0, duration: 0.6, ease: "expo.out" }, 8.0);
애니메이션 가드레일
- 첫 번째 애니메이션을 0.1-0.3초 오프셋하세요 (t=0이 아님)
- 진입 트윈 전체에 이징을 다양하게 하세요 — 장면당 적어도 3개의 다른 이징을 사용하세요
- 장면 내에서 진입 패턴을 반복하지 마세요
- 어두운 배경에서 전체 화면 선형 그래디언트를 피하세요 (H.264 밴딩 — 방사형 또는 단색 + 지역화된 글로우 사용)
- 렌더링된 비디오의 경우 60px+ 제목, 20px+ 본문, 16px+ 데이터 레이블
- 숫자 열에
font-variant-numeric: tabular-nums
visual-style.md 또는 애니메이션 방향이 제공되지 않은 경우, 미적 기본값에 대해 house-style.md를 따르세요.
타이포그래피 및 에셋
- 글꼴: CSS에서 원하는
font-family를 작성하기만 하면 됩니다 — 컴파일러가 지원되는 글꼴을 자동으로 포함합니다. 글꼴이 지원되지 않으면 컴파일러가 경고합니다. - 외부 미디어에
crossorigin="anonymous"추가 - …
[truncated for translation]