Setting the file. One moment. Brand Reveal Assemble Zoom · Hyperframes Animation · heygen-com/hyperframes · Skills DocsBlueprints Index
(opens in a new tab)
examples/brand-reveal-assemble-zoom.html
HTML·382 lines·12 KB
8 <!--
9 HyperFrames composition.tsx.
10
11 Choreography (5 phases, 5 seconds total):
12 0.00 – 0.67s "Just use" assembles in a discrete sequence (with hold)
13 0.73 – 1.23s Pink logo pops in with back.out elastic
14 1.50 – 2.20s "Just use" slides left + fades; container recenters around brand
15 2.67 – 3.57s Camera zooms 5.5× into the logo (scale + counter-translate)
16 3.67 – 5.00s Logo breathes (sine onUpdate, multiplicative on pop scale)
17
18 Key differences :
19 - Single paused GSAP timeline registered to window.__timelines["main"]
20 - brandTextWidth measured via getBoundingClientRect after document.fonts.ready
21 - HERO_FINAL_OFFSET_X derived from real measurement, not estimate
22 - Breathing uses the onUpdate (multiplicative) form so it multiplies
23 onto the pop scale, not a fromTo + yoyo which would overwrite the pop value
24 - Three nested transform layers: zoom-scale → zoom-translate → recenter-shift
25 - Logo is loaded from a static PNG asset
26 -->
27
28 <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
29
30 <style>
31 :root {
32 --bg: #000000;
33 --text-white: #ffffff;
34 --pink: #e91e63;
35 }
36 * {
37 margin: 0;
38 padding: 0;
39 box-sizing: border-box;
40 }
41 html,
42 body {
43 margin: 0;
44 width: 1920px;
45 height: 1080px;
46 overflow: hidden;
47 background: var(--bg);
48 font-family: "Google Sans", "Roboto", Inter, system-ui, sans-serif;
49 color: var(--text-white);
50 }
51
52 .stage {
53 position: absolute;
54 inset: 0;
55 display: flex;
56 align-items: center;
57 justify-content: center;
58 overflow: hidden;
59 }
60
61 /* Three nested transform layers (outer → inner) */
62 .zoom-scale {
63 transform-origin: center center;
64 will-change: transform;
65 display: flex;
66 align-items: center;
67 justify-content: center;
68 }
69 .zoom-translate {
70 will-change: transform;
71 display: flex;
72 align-items: center;
73 justify-content: center;
74 }
75 .recenter-shift {
76 display: flex;
77 align-items: center;
78 }
79
80 /* Companion text — fixed width to prevent assembly jitter */
81 .companion {
82 width: 600px;
83 display: flex;
84 justify-content: flex-end;
85 margin-right: 30px;
86 white-space: nowrap;
87 color: var(--text-white);
88 font-size: 140px;
89 font-weight: 400;
90 line-height: 1;
91 }
92
93 /* Brand group: text + hero icon */
94 .brand-group {
95 display: flex;
96 align-items: center;
97 gap: 20px;
98 }
99 .brand-text {
100 color: var(--text-white);
101 font-size: 140px;
102 font-weight: 700;
103 white-space: nowrap;
104 line-height: 1;
105 }
106 .hero {
107 display: flex;
108 align-items: center;
109 justify-content: center;
110 width: 140px;
111 height: 140px;
112 /* initial scale(0) set by GSAP fromTo */
113 }
114 .hero .logo-mark {
115 width: 100%;
116 height: 100%;
117 display: block;
118 }
119
120 /* Hidden probe used for text measurement */
121 .measure-probe {
122 position: absolute;
123 left: -99999px;
124 top: -99999px;
125 visibility: hidden;
126 white-space: pre;
127 }
128 </style>
129 </head>
130 <body>
131 <div
132 id="root"
133 data-composition-id="main"
134 data-start="0"
135 data-duration="5"
136 data-width="1920"
137 data-height="1080"
138 >
139 <div
140 class="stage clip"
141 data-start="0"
142 data-duration="5"
143 data-track-index="1"
144 id="brand-stage"
145 >
146 <div class="zoom-scale" data-layout-allow-overflow>
147 <div class="zoom-translate">
148 <div class="recenter-shift">
149 <div class="companion">
150 <span class="companion-text">J</span>
151 </div>
152
153 <div class="brand-group" data-layout-allow-overflow>
154 <span class="brand-text">Hyperframes</span>
155 <div class="hero">
156 <!-- Inline-SVG placeholder mark — swap for your logo image -->
157 <svg
158 class="logo-mark"
159 viewBox="0 0 100 100"
160 role="img"
161 aria-label="hyperframes logo"
162 >
163 <defs>
164 <linearGradient id="hfMark" x1="0" y1="0" x2="1" y2="1">
165 <stop offset="0" stop-color="#8b7bff" />
166 <stop offset="1" stop-color="#3ddc97" />
167 </linearGradient>
168 </defs>
169 <rect x="4" y="4" width="92" height="92" rx="22" fill="url(#hfMark)" />
170 <text
171 x="50"
172 y="63"
173 text-anchor="middle"
174 font-family="Inter, system-ui, sans-serif"
175 font-size="40"
176 font-weight="800"
177 fill="#fff"
178 >
179 HF
180 </text>
181 </svg>
182 </div>
183 </div>
184 </div>
185 </div>
186 </div>
187 </div>
188 </div>
189
190 <script>
191 /* ================================================================
192 CONSTANTS — composition dimensions baked in.
193 ================================================================ */
194 const W = 1920,
195 H = 1080;
196 const TOTAL_DURATION = 5.0;
197
198 const COMPANION_WIDTH = 600;
199 const COMPANION_GAP = 30; // margin-right on .companion
200 const BRAND_FONT_SIZE = 140;
201 const HERO_GAP = 20;
202 const HERO_SIZE = 140;
203
204 const TIMING = {
205 // Phase 1: companion assembly
206 textStart: 0.0,
207 textEnd: 0.67,
208
209 // Phase 2: hero pop
210 popStart: 0.73,
211 popDur: 0.5,
212
213 // Phase 3: slide-out + recenter
214 slideStart: 1.5,
215 slideDur: 0.7,
216
217 // Phase 4: zoom
218 zoomStart: 2.67,
219 zoomDur: 0.9,
220
221 // Phase 5: breathing
222 breathStart: 3.67,
223 };
224
225 const FINAL_RECENTER_OFFSET = -180; // pre-calculated, tuned for visual feel
226
227 /* ================================================================
228 DISCRETE TEXT SEQUENCE for the companion assembly.
229 Converted from the source frame-based sequence to seconds at 30fps.
230 ================================================================ */
231 const SEQUENCE = [
232 { t: 0.0, text: "J" },
233 { t: 0.07, text: "Jus" },
234 { t: 0.13, text: "Just" },
235 { t: 0.27, text: "Just" }, // hold for pacing
236 { t: 0.4, text: "Just u" },
237 { t: 0.53, text: "Just us" },
238 { t: 0.67, text: "Just use" },
239 ];
240
241 /* ================================================================
242 TIMELINE — built synchronously so HyperFrames can seek it.
243 ================================================================ */
244 window.__timelines = window.__timelines || {};
245 const tl = gsap.timeline({ paused: true });
246 window.__timelines["main"] = tl;
247
248 {
249 /* Measure the brand text width with a hidden DOM probe. */
250 const probe = document.createElement("span");
251 probe.className = "measure-probe";
252 probe.style.font = `700 ${BRAND_FONT_SIZE}px "Google Sans", "Roboto", Inter, system-ui, sans-serif`;
253 probe.style.whiteSpace = "pre";
254 probe.style.lineHeight = "1";
255 probe.textContent = "Hyperframes"; // MUST match the rendered .brand-text casing
256 document.body.appendChild(probe);
257 const brandTextWidth = probe.getBoundingClientRect().width;
258 probe.remove();
259
260 /* Derive the hero's post-Phase-3 offset from viewport center.
261 baseHeroOffset = (C + G + B + L) / 2 <- heroSize cancels out
262 HERO_FINAL_OFFSET_X = baseHeroOffset + FINAL_RECENTER_OFFSET */
263 const baseHeroOffset = (COMPANION_WIDTH + COMPANION_GAP + brandTextWidth + HERO_GAP) / 2;
264 const HERO_FINAL_OFFSET_X = baseHeroOffset + FINAL_RECENTER_OFFSET;
265
266 /* ============================================================
267 PHASE 1: Discrete companion assembly
268 ============================================================ */
269 const textEl = document.querySelector(".companion-text");
270
271 for (const entry of SEQUENCE) {
272 tl.set(textEl, { textContent: entry.text }, entry.t);
273 }
274
275 /* ============================================================
276 PHASE 2: Hero pops in (elastic)
277 ============================================================ */
278 tl.fromTo(
279 ".hero",
280 { scale: 0 },
281 {
282 scale: 1,
283 duration: TIMING.popDur,
284 ease: "back.out(2)", // spring(stiffness:200, damping:12)
285 },
286 TIMING.popStart,
287 );
288
289 /* ============================================================
290 PHASE 3: Companion exit + container recenter
291 Two concurrent tweens at the same timeline position.
292 ============================================================ */
293 tl.to(
294 ".companion",
295 {
296 opacity: 0,
297 x: -80,
298 duration: TIMING.slideDur,
299 ease: "power3.out", // spring(stiffness:100, damping:20)
300 },
301 TIMING.slideStart,
302 );
303
304 tl.to(
305 ".recenter-shift",
306 {
307 x: FINAL_RECENTER_OFFSET,
308 duration: TIMING.slideDur,
309 ease: "power3.out",
310 },
311 TIMING.slideStart,
312 );
313
314 /* ============================================================
315 PHASE 4: Zoom — scale (outer) + counter-translate (middle) + brand text exits.
316 ============================================================ */
317 tl.to(
318 ".zoom-scale",
319 {
320 scale: 5.5,
321 duration: TIMING.zoomDur,
322 ease: "power2.out", // spring(stiffness:80, damping:20, mass:1.5)
323 },
324 TIMING.zoomStart,
325 );
326
327 tl.to(
328 ".zoom-translate",
329 {
330 x: -HERO_FINAL_OFFSET_X,
331 y: 0,
332 duration: TIMING.zoomDur,
333 ease: "power2.out",
334 },
335 TIMING.zoomStart,
336 );
337
338 // Brand text fades out + slides left so the logo gets all the zoom space.
339 tl.to(
340 ".brand-text",
341 {
342 opacity: 0,
343 x: -600,
344 duration: TIMING.zoomDur * 0.4,
345 ease: "power2.out",
346 },
347 TIMING.zoomStart,
348 );
349
350 /* ============================================================
351 PHASE 5: Breathing — onUpdate so it MULTIPLIES on the hero's
352 final pop scale, doesn't overwrite it.
353 ============================================================ */
354 const heroEl = document.querySelector(".hero");
355 const HERO_FINAL_SCALE = 1.0;
356 const SCALE_PERIOD = 1.5; // seconds per cycle
357 const SCALE_AMP = 0.04;
358 const ROTATE_AMP = 2;
359
360 const breathDur = TOTAL_DURATION - TIMING.breathStart;
361
362 tl.to(
363 { tick: 0 },
364 {
365 tick: 1,
366 duration: breathDur,
367 ease: "none",
368 onUpdate: function () {
369 const idleTime = Math.max(0, tl.time() - TIMING.breathStart);
370 const omega = (idleTime / SCALE_PERIOD) * Math.PI * 2;
371 gsap.set(heroEl, {
372 scale: HERO_FINAL_SCALE * (1 + Math.sin(omega) * SCALE_AMP),
373 rotation: Math.sin(omega) * ROTATE_AMP,
374 });
375 },
376 },
377 TIMING.breathStart,
378 );
379 }
380 </script>
381 </body>
382</html>