HyperFrames
HTML이 비디오의 단일 진리 소스입니다. 컴포지션은 data-* 속성(타이밍), GSAP 타임라인(애니메이션), CSS(외형)를 포함하는 HTML 파일입니다. 프레임워크가 클립 가시성, 미디어 재생 및 타임라인 동기화를 처리합니다.
접근 방식
HTML을 작성하기 전에 높은 수준에서 생각하십시오:
- 무엇(What) — 시청자가 무엇을 경험해야 합니까? 내러티브 아크, 핵심 순간, 감정적 포인트를 식별하십시오.
- 구조(Structure) — 몇 개의 컴포지션이 필요한지, 어떤 것이 하위 컴포지션인지 인라인인지, 어떤 트랙이 무엇을 담당하는지(비디오, 오디오, 오버레이, 캡션).
- 타이밍(Timing) — 어떤 클립이 지속 시간을 결정하는지, 전환이 어디에 위치하는지, 페이싱은 어떤지.
- 레이아웃(Layout) — 먼저 최종 상태를 구축하십시오. 아래 "애니메이션 전 레이아웃" 참조.
- 애니메이션(Animate) — 그런 다음 아래 규칙을 사용하여 모션을 추가하십시오.
작은 편집(색상 수정, 타이밍 조정, 요소 하나 추가)의 경우 규칙으로 바로 건너뛰십시오.
시각적 아이덴티티 게이트
다음 순서로 확인하십시오:
- 프로젝트에 DESIGN.md가 있습니까? → 읽으십시오. 정확한 색상, 글꼴, 모션 규칙 및 "하지 말아야 할 것" 제약 조건을 사용하십시오.
- visual-style.md가 있습니까? → 읽으십시오.
style_prompt_full및 구조화된 필드를 적용하십시오. (참고:visual-style.md는 프로젝트별 파일입니다.visual-styles.md는 8개의 명명된 프리셋이 있는 스타일 라이브러리입니다 — 서로 다른 파일입니다.) - 사용자가 스타일을 명명했습니까? (예: "Swiss Pulse", "어둡고 테크니컬한", "럭셔리 브랜드") → 8개의 명명된 프리셋에 대해 visual-styles.md를 읽으십시오. 다음을 포함하는 최소한의 DESIGN.md를 생성하십시오:
## Style Prompt(한 문단),## Colors(역할과 함께 3-5개의 헥스 값),## Typography(1-2개의 글꼴 패밀리),## What NOT to Do(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가 장면을 채우고, padding이 콘텐츠를 배치합니다 */
.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/compositions에 필수 | 초. 비디오/오디오는 미디어 지속 시간으로 기본 설정됩니다. |
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() 또는 시간 기반 로직을 사용하지 마십시오. 의사 난수 값(예: mulberry32)이 필요하면 시드된 PRNG를 사용하십시오.
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()을 통해 IN으로 애니메이션됩니다. 어떤 요소도 완전히 형성된 상태로 나타날 수 없습니다. 장면에 5개의 요소가 있으면 5개의 등장 트윈이 필요합니다. - 마지막 장면을 제외하고는 절대 퇴장 애니메이션을 사용하지 마십시오. 즉: 전환이 실행되기 전에 불투명도를 0으로, y를 화면 밖으로, scale을 0으로 또는 다른 "out" 애니메이션을 수행하는
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"를 추가하십시오.
…
[번역을 위해 잘림]