HyperFrames
HTML은 비디오의 진실 공급원(source of truth)입니다. 컴포지션은 타이밍을 위한 data-* 속성, 애니메이션을 위한 GSAP 타임라인, 그리고 외형을 위한 CSS를 포함한 HTML 파일입니다. 프레임워크는 클립 가시성, 미디어 재생, 타임라인 동기화를 처리합니다.
접근 방식
HTML을 작성하기 전에 높은 수준에서 생각하세요:
- 무엇을(What) — 시청자가 무엇을 경험해야 합니까? 내러티브 아크, 주요 순간, 감정적 비트를 식별하세요.
- 구조(Structure) — 얼마나 많은 컴포지션이 있으며, 어떤 것이 서브 컴포지션이고 어떤 것이 인라인인지, 어떤 트랙이 무엇을 전달하는지(비디오, 오디오, 오버레이, 캡션).
- 타이밍(Timing) — 어떤 클립이 지속 시간을 결정하며, 전환이 어디에 위치하는지, 페이싱은 어떠한지.
- 레이아웃(Layout) — 먼저 최종 상태를 구축하세요. 아래의 '레이아웃을 애니메이션보다 먼저'를 참조하세요.
- 애니메이션(Animate) — 그런 다음 아래 규칙을 사용하여 모션을 추가하세요.
작은 수정(색상 변경, 타이밍 조정, 요소 하나 추가)의 경우 바로 규칙으로 건너뛰세요.
시각적 아이덴티티 게이트
다음 순서대로 확인하세요:
- 프로젝트에 DESIGN.md가 존재합니까? → 읽으세요. 정확한 색상, 글꼴, 모션 규칙, 그리고 '하지 말아야 할 것(What NOT to Do)' 제약 조건을 사용하세요.
- visual-style.md가 존재합니까? → 읽으세요.
style_prompt_full및 구조화된 필드를 적용하세요. (참고:visual-style.md는 프로젝트별 파일입니다.visual-styles.md는 8개의 명명된 프리셋이 있는 스타일 라이브러리입니다 — 다른 파일입니다.) - 사용자가 스타일을 지정했습니까? (예: "Swiss Pulse", "어둡고 테크니컬한", "럭셔리 브랜드") → visual-styles.md에서 8개의 명명된 프리셋을 읽으세요. 최소한의 DESIGN.md를 생성하세요:
## Style Prompt(한 문단),## Colors(역할과 함께 3-5개의 16진수 값),## 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 위치는 기본 진실(ground truth)입니다. 트윈은 그곳에 도달하기 위한 여정을 설명합니다.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: 해당 위치에서 애니메이션 OUT
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()을 통해 IN으로 애니메이션됩니다. 어떤 요소도 완전히 형성된 상태로 나타날 수 없습니다. 씬에 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"추가
캡션 및 자막
HyperFrames는 선택적 캡션 시스템을 지원합니다. 타임라인의 특정 시간에 표시되는 캡션 요소를 포함하는 전용 HTML 파일입니다.
캡션 구조
<div data-composition-id="captions" data-width="1920" data-height="1080">
<div class="caption" data-start="0" data-duration="3">안녕하세요, 세계.</div>
<div class="caption" data-start="3.5" data-duration="2.5">이것은 캡션입니다.</div>
<style>
.caption { /* 스타일 */ }
</style>
</div>
각 캡션 요소는 data-start와 data-duration을 사용하여 시간을 지정합니다. 캡션은 data-track-index를 통해 메인 타임라인과 동기화될 수 있습니다.
텍스트 음성 변환 (TTS)
텍스트 음성 변환 오디오를 생성하려면 transcribe 및 tts CLI 명령을 사용하세요 (hyperframes-cli 스킬 참조). 생성된 오디오 파일은 표준 <audio> 요소를 사용하여 컴포지션에 통합될 수 있습니다.
오디오 반응형 애니메이션
오디오 주파수 또는 비트에 반응하는 애니메이션을 만들려면 GSAP 타임라인과 CSS 애니메이션을 결합하세요. 예를 들어:
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.beat-reactive {
animation: pulse 0.5s ease-in-out infinite;
animation-play-state: paused; /* 기본적으로 일시 중지 */
}
JavaScript를 사용하여 오디오 이벤트(예: timeupdate)를 수신하고 CSS 클래스를 토글하거나 GSAP 트윈을 트리거합니다.
애니메이션 텍스트 하이라이트
텍스트 하이라이트(마커 스위프, 손으로 그린 원, 스크리블 등)의 경우 오버레이 SVG 또는 CSS 의사 요소를 사용하여 하이라이트 효과를 만듭니다. GSAP로 하이라이트 요소의 clip-path 또는 opacity를 애니메이션합니다.
예시: 전체 컴포지션
[콘텐츠는 원본과 동일하게 유지되며, 구조는 예시를 보여줍니다.]