Setting the file. One moment. Comparison Split Cards · Hyperframes Animation · heygen-com/hyperframes · Skills DocsBlueprints Index
(opens in a new tab)
examples/comparison-split-cards.html
HTML·649 lines·21 KB
8 <!--
9 HyperFrames composition.tsx.
10
11 Choreography (3 phases, 5 seconds total):
12 0.17 – 0.83s Title slides down from top: "Build Video With HyperFrames"
13 0.50 – 1.83s Left card "HTML Composition" enters from left (+16° rotateY)
14 0.83 – 1.83s Right card "Render Pipeline" enters from right (-16° rotateY)
15 1.67 – 2.17s Left pill badge "Seekable Timeline" pops in with back.out(1.7)
16 2.00 – 2.50s Right pill badge "Render Ready" pops in
17 0 – 5.00s Continuous floating: cards y ±6 px / rotation ±1° (phase-opposed)
18 Badges y ±5 px (slow shared sine)
19 and .card-tilt (static rotateY + float rotation) so entry and float
20 don't fight on the same alias
21 - All continuous floating consolidated into one shared scene-ticker onUpdate
22 (6 gsap.set calls per frame, batched by the browser)
23 - Phase offset Math.PI between the two cards' floats — opposed breathing
24 - Card images are HyperFrames workflow mockups (no asset files needed)
25 - Ambient dual-glow via two radial-gradients in a single overlay
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-dark: #0a1415;
33 --text-primary: #ffffff;
34 --text-secondary: rgba(255, 255, 255, 0.65);
35 --brand-cyan: #18d9e8;
36 --brand-cyan-glow: rgba(24, 217, 232, 1);
37 --brand-green: #7bea5a;
38 --brand-green-glow: rgba(123, 234, 90, 1);
39 --glass-bg: rgba(255, 255, 255, 0.08);
40 --border: rgba(255, 255, 255, 0.12);
41 }
42 * {
43 margin: 0;
44 padding: 0;
45 box-sizing: border-box;
46 }
47 html,
48 body {
49 margin: 0;
50 width: 1920px;
51 height: 1080px;
52 overflow: hidden;
53 background: var(--bg-dark);
54 font-family:
55 "Inter",
56 system-ui,
57 -apple-system,
58 sans-serif;
59 color: var(--text-primary);
60 }
61
62 .stage {
63 position: absolute;
64 inset: 0;
65 overflow: hidden;
66 }
67
68 /* ============================================================
69 BACKGROUND
70 ============================================================ */
71 .bg {
72 position: absolute;
73 inset: 0;
74 background:
75 radial-gradient(ellipse at 35% 50%, rgba(24, 217, 232, 0.2), transparent 45%),
76 radial-gradient(ellipse at 72% 50%, rgba(123, 234, 90, 0.16), transparent 45%),
77 linear-gradient(135deg, #3a3a3a 0%, #17211f 30%, #0b2328 48%, #1f3518 70%, #343434 100%);
78 }
79
80 /* ============================================================
81 TITLE
82 ============================================================ */
83 .title {
84 position: absolute;
85 top: 60px;
86 left: 50%;
87 transform: translateX(-50%);
88 font-size: 88px;
89 font-weight: 700;
90 color: var(--text-primary);
91 text-align: center;
92 letter-spacing: 0;
93 white-space: nowrap;
94 will-change: transform, opacity;
95 /* initial opacity 0 + y -40 set via gsap.set */
96 }
97 .title .accent {
98 color: var(--brand-cyan);
99 text-shadow: 0 0 34px rgba(24, 217, 232, 0.28);
100 }
101
102 /* ============================================================
103 CARDS ROW
104 ============================================================ */
105 .cards-row {
106 position: absolute;
107 top: 50%;
108 left: 50%;
109 transform: translate(-50%, -50%);
110 display: flex;
111 gap: 76px;
112 padding-top: 30px;
113 perspective: 980px;
114 perspective-origin: 50% 42%;
115 transform-style: preserve-3d;
116 }
117
118 /* Two nested wrappers per card */
119 .card {
120 transform-style: preserve-3d;
121 }
122 .card-pos {
123 perspective: 980px;
124 perspective-origin: 50% 45%;
125 transform-style: preserve-3d;
126 will-change: transform, opacity;
127 /* initial x/scale/opacity set via gsap.set */
128 }
129 .card-tilt {
130 transform-style: preserve-3d;
131 will-change: transform;
132 /* initial rotationY set via gsap.set */
133 }
134 .card-content {
135 width: 720px;
136 transform-style: preserve-3d;
137 }
138 .card-image {
139 width: 100%;
140 height: 500px;
141 border-radius: 24px;
142 overflow: hidden;
143 border: 1px solid var(--border);
144 position: relative;
145 transform: translateZ(0) rotateX(0.01deg);
146 backface-visibility: hidden;
147 }
148 .card-left .card-tilt {
149 transform-origin: 100% 50%;
150 }
151 .card-right .card-tilt {
152 transform-origin: 0% 50%;
153 }
154 .card-label {
155 margin-top: 26px;
156 text-align: center;
157 font-size: 54px;
158 font-weight: 700;
159 color: var(--text-primary);
160 transform: translateZ(34px);
161 text-shadow: 0 0 30px rgba(24, 217, 232, 0.16);
162 }
163 .card-subtitle {
164 margin-top: 8px;
165 text-align: center;
166 font-size: 24px;
167 color: var(--text-secondary);
168 transform: translateZ(26px);
169 }
170
171 /* Left card: shadow falls right (positive x in box-shadow = right) */
172 .card-left .card-image {
173 box-shadow:
174 30px 30px 60px rgba(0, 0, 0, 0.45),
175 0 0 60px rgba(24, 217, 232, 0.2);
176 }
177 /* Right card: shadow falls left */
178 .card-right .card-image {
179 box-shadow:
180 -30px 30px 60px rgba(0, 0, 0, 0.45),
181 0 0 60px rgba(123, 234, 90, 0.2);
182 }
183
184 /* ============================================================
185 CARD IMAGE PLACEHOLDERS (mock UI)
186 ============================================================ */
187 /* Left card — HTML Composition: gradient + timed clip grid */
188 .card-left .card-image {
189 background: linear-gradient(
190 135deg,
191 rgba(12, 38, 42, 0.96) 0%,
192 rgba(18, 66, 63, 0.9) 52%,
193 rgba(32, 57, 38, 0.94) 100%
194 );
195 }
196 .mock-templates {
197 position: absolute;
198 inset: 0;
199 padding: 56px 66px;
200 display: grid;
201 grid-template-columns: repeat(3, 1fr);
202 grid-template-rows: repeat(2, 1fr);
203 gap: 22px;
204 opacity: 0.22;
205 }
206 .mock-thumb {
207 background: linear-gradient(135deg, rgba(24, 217, 232, 0.2), rgba(123, 234, 90, 0.08));
208 border-radius: 14px;
209 border: 1px solid rgba(255, 255, 255, 0.1);
210 display: flex;
211 align-items: flex-end;
212 justify-content: flex-start;
213 padding: 14px;
214 transform: translateZ(18px);
215 box-shadow: 0 14px 30px rgba(0, 0, 0, 0.18);
216 }
217 .mock-thumb .label {
218 font-size: 18px;
219 font-weight: 600;
220 color: rgba(255, 255, 255, 0.8);
221 }
222
223 /* Right card — Render Pipeline: validation rows */
224 .card-right .card-image {
225 background: linear-gradient(
226 135deg,
227 rgba(23, 45, 34, 0.96) 0%,
228 rgba(17, 70, 73, 0.9) 50%,
229 rgba(38, 57, 45, 0.94) 100%
230 );
231 }
232 .mock-team {
233 position: absolute;
234 inset: 0;
235 padding: 66px 72px;
236 display: flex;
237 flex-direction: column;
238 gap: 20px;
239 justify-content: center;
240 opacity: 0.24;
241 }
242 .mock-row {
243 display: flex;
244 align-items: center;
245 gap: 14px;
246 padding: 15px 20px;
247 background: rgba(255, 255, 255, 0.06);
248 border-radius: 12px;
249 border: 1px solid rgba(255, 255, 255, 0.08);
250 transform: translateZ(18px);
251 box-shadow: 0 14px 30px rgba(0, 0, 0, 0.18);
252 }
253 .mock-avatar {
254 width: 44px;
255 height: 44px;
256 border-radius: 50%;
257 flex-shrink: 0;
258 background: linear-gradient(135deg, var(--brand-green), var(--brand-cyan));
259 }
260 .mock-avatar.purple {
261 background: linear-gradient(135deg, var(--brand-cyan), #38bdf8);
262 }
263 .mock-avatar.green {
264 background: linear-gradient(135deg, var(--brand-green), var(--brand-cyan));
265 }
266 .mock-avatar.orange {
267 background: linear-gradient(135deg, #facc15, var(--brand-green));
268 }
269 .mock-row .name {
270 font-size: 22px;
271 font-weight: 600;
272 color: rgba(255, 255, 255, 0.85);
273 }
274 .mock-row .status {
275 margin-left: auto;
276 font-size: 16px;
277 color: var(--brand-green);
278 }
279
280 /* ============================================================
281 FLOATING BADGES
282 ============================================================ */
283 .badge {
284 display: flex;
285 align-items: center;
286 gap: 12px;
287 padding: 14px 22px;
288 border-radius: 999px;
289 background-color: var(--glass-bg);
290 backdrop-filter: blur(10px);
291 -webkit-backdrop-filter: blur(10px);
292 will-change: transform, opacity;
293 z-index: 50;
294 }
295 .badge-left {
296 border: 1px solid rgba(24, 217, 232, 0.4);
297 box-shadow: 0 0 30px rgba(24, 217, 232, 0.35);
298 }
299 .badge-right {
300 border: 1px solid rgba(123, 234, 90, 0.4);
301 box-shadow: 0 0 30px rgba(123, 234, 90, 0.35);
302 }
303 .badge-icon-wrap {
304 width: 36px;
305 height: 36px;
306 border-radius: 50%;
307 display: flex;
308 align-items: center;
309 justify-content: center;
310 color: var(--bg-dark);
311 flex-shrink: 0;
312 }
313 .badge-left .badge-icon-wrap {
314 background-color: var(--brand-cyan);
315 }
316 .badge-right .badge-icon-wrap {
317 background-color: var(--brand-green);
318 }
319 .badge-label {
320 font-size: 24px;
321 font-weight: 600;
322 color: var(--text-primary);
323 white-space: nowrap;
324 }
325
326 /* ============================================================
327 AMBIENT GLOW + VIGNETTE
328 ============================================================ */
329 .ambient-glow {
330 position: absolute;
331 inset: 0;
332 background:
333 radial-gradient(ellipse at 30% 50%, var(--brand-cyan-glow) 0%, transparent 35%),
334 radial-gradient(ellipse at 70% 50%, var(--brand-green-glow) 0%, transparent 35%);
335 opacity: 0.13;
336 pointer-events: none;
337 }
338 .vignette {
339 position: absolute;
340 inset: 0;
341 background: radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.45) 100%);
342 pointer-events: none;
343 z-index: 400;
344 }
345 </style>
346 </head>
347 <body>
348 <div
349 id="root"
350 data-composition-id="main"
351 data-start="0"
352 data-duration="5"
353 data-width="1920"
354 data-height="1080"
355 >
356 <div
357 class="stage clip"
358 data-start="0"
359 data-duration="5"
360 data-track-index="1"
361 id="scene-stage"
362 >
363 <div class="bg"></div>
364
365 <!-- Title -->
366 <div class="title" id="title">Build Video With <span class="accent">HyperFrames</span></div>
367
368 <!-- Cards row -->
369 <div class="cards-row">
370 <!-- Left card: HTML Composition -->
371 <div class="card card-left" id="card-left">
372 <div class="card-pos">
373 <div class="card-tilt">
374 <div class="card-content">
375 <div class="card-image">
376 <div class="mock-templates">
377 <div class="mock-thumb"><span class="label">HTML</span></div>
378 <div class="mock-thumb"><span class="label">CSS</span></div>
379 <div class="mock-thumb"><span class="label">GSAP</span></div>
380 <div class="mock-thumb"><span class="label">Audio</span></div>
381 <div class="mock-thumb"><span class="label">Captions</span></div>
382 <div class="mock-thumb"><span class="label">Assets</span></div>
383 </div>
384 </div>
385 <div class="card-label">HTML Composition</div>
386 <div class="card-subtitle">Timed DOM clips, media, and motion</div>
387 </div>
388 </div>
389 </div>
390 </div>
391
392 <!-- Right card: Render Pipeline -->
393 <div class="card card-right" id="card-right">
394 <div class="card-pos">
395 <div class="card-tilt">
396 <div class="card-content">
397 <div class="card-image">
398 <div class="mock-team">
399 <div class="mock-row">
400 <div class="mock-avatar purple"></div>
401 <span class="name">Register timeline</span>
402 <span class="status">● seekable</span>
403 </div>
404 <div class="mock-row">
405 <div class="mock-avatar green"></div>
406 <span class="name">Validate layout</span>
407 <span class="status">● clean</span>
408 </div>
409 <div class="mock-row">
410 <div class="mock-avatar orange"></div>
411 <span class="name">Render frames</span>
412 <span class="status">● stable</span>
413 </div>
414 <div class="mock-row">
415 <div class="mock-avatar"></div>
416 <span class="name">Publish MP4</span>
417 <span class="status">● ready</span>
418 </div>
419 </div>
420 </div>
421 <div class="card-label">Render Pipeline</div>
422 <div class="card-subtitle">Preview, check, render, publish</div>
423 </div>
424 </div>
425 </div>
426 </div>
427 </div>
428
429 <!-- Floating badges -->
430 <div class="badge badge-left" id="badge-left">
431 <div class="badge-icon-wrap">
432 <svg
433 width="18"
434 height="18"
435 viewBox="0 0 24 24"
436 fill="none"
437 stroke="currentColor"
438 stroke-width="2"
439 stroke-linecap="round"
440 stroke-linejoin="round"
441 aria-hidden="true"
442 >
443 <path d="M12 2L2 7L12 12L22 7L12 2Z" />
444 <path d="M2 17L12 22L22 17" />
445 <path d="M2 12L12 17L22 12" />
446 </svg>
447 </div>
448 <span class="badge-label">Seekable Timeline</span>
449 </div>
450
451 <div class="badge badge-right" id="badge-right">
452 <div class="badge-icon-wrap">
453 <svg
454 width="18"
455 height="18"
456 viewBox="0 0 24 24"
457 fill="none"
458 stroke="currentColor"
459 stroke-width="2"
460 stroke-linecap="round"
461 stroke-linejoin="round"
462 aria-hidden="true"
463 >
464 <path d="M17 21V19C17 16.7909 15.2091 15 13 15H5C2.79086 15 1 16.7909 1 19V21" />
465 <circle cx="9" cy="7" r="4" />
466 <path d="M23 21V19C22.9986 17.1771 21.765 15.5857 20 15.13" />
467 <path
468 d="M16 3.13C17.7699 3.58317 19.0078 5.17799 19.0078 7.005C19.0078 8.83201 17.7699 10.4268 16 10.88"
469 />
470 </svg>
471 </div>
472 <span class="badge-label">Render Ready</span>
473 </div>
474
475 <div class="ambient-glow"></div>
476 </div>
477
478 <div class="vignette"></div>
479 </div>
480
481 <script>
482 /* ================================================================
483 CONSTANTS
484 ================================================================ */
485 const W = 1920,
486 H = 1080;
487 const TOTAL_DUR = 5.0;
488
489 const TIMING = {
490 // Phase 1: title
491 titleAt: 0.17,
492 titleDur: 0.67,
493
494 // Phase 2: cards
495 leftAt: 0.5,
496 rightAt: 0.83,
497 entryDur: 0.7,
498 slideDist: 100,
499 baseTilt: 18,
500
501 // Phase 3: badges
502 badgeLeftAt: 1.67,
503 badgeRightAt: 2.0,
504 badgeEntryDur: 0.5,
505
506 // Continuous float
507 floatYSpeed: 0.02 * 30, // = 0.6 rad/sec
508 floatYAmp: 6,
509 floatRSpeed: 0.015 * 30, // = 0.45 rad/sec
510 floatRAmp: 1,
511 badgeYSpeed: 0.025 * 30, // = 0.75 rad/sec
512 badgeYAmp: 5,
513 };
514
515 /* ================================================================
516 BADGE POSITIONS — set via CSS left/top once (not tweened)
517 ================================================================ */
518 const badgeLeftEl = document.getElementById("badge-left");
519 const badgeRightEl = document.getElementById("badge-right");
520 badgeLeftEl.style.left = W * 0.12 + "px"; // 230 px
521 badgeLeftEl.style.top = H * 0.35 + "px"; // 378 px
522 badgeLeftEl.style.position = "absolute";
523 badgeRightEl.style.left = W * 0.75 + "px"; // 1440 px
524 badgeRightEl.style.top = H * 0.38 + "px"; // 410 px
525 badgeRightEl.style.position = "absolute";
526
527 /* ================================================================
528 INITIAL STATES (via gsap.set, before the timeline runs)
529 ================================================================ */
530 gsap.set("#title", { opacity: 0, y: -40 });
531
532 gsap.set("#card-left .card-pos", { x: -TIMING.slideDist, scale: 0.8, opacity: 0, y: 0 });
533 gsap.set("#card-right .card-pos", { x: TIMING.slideDist, scale: 0.8, opacity: 0, y: 0 });
534 gsap.set("#card-left .card-tilt", { rotationY: TIMING.baseTilt });
535 gsap.set("#card-right .card-tilt", { rotationY: -TIMING.baseTilt });
536
537 gsap.set(["#badge-left", "#badge-right"], { scale: 0, opacity: 0, y: 0 });
538
539 /* ================================================================
540 TIMELINE
541 ================================================================ */
542 window.__timelines = window.__timelines || {};
543 const tl = gsap.timeline({ paused: true });
544 window.__timelines["main"] = tl;
545
546 /* ----------------------------------------------------------------
547 PHASE 1: Title slides down
548 ---------------------------------------------------------------- */
549 tl.to(
550 "#title",
551 {
552 opacity: 1,
553 y: 0,
554 duration: TIMING.titleDur,
555 ease: "power3.out", // spring(stiffness:100, damping:16)
556 },
557 TIMING.titleAt,
558 );
559
560 /* ----------------------------------------------------------------
561 PHASE 2: Cards enter from opposite sides
562 ---------------------------------------------------------------- */
563 tl.to(
564 "#card-left .card-pos",
565 {
566 x: 0,
567 scale: 1,
568 opacity: 1,
569 duration: TIMING.entryDur,
570 ease: "power3.out",
571 },
572 TIMING.leftAt,
573 );
574
575 tl.to(
576 "#card-right .card-pos",
577 {
578 x: 0,
579 scale: 1,
580 opacity: 1,
581 duration: TIMING.entryDur,
582 ease: "power3.out",
583 },
584 TIMING.rightAt,
585 );
586
587 /* ----------------------------------------------------------------
588 PHASE 3: Badges pop in
589 ---------------------------------------------------------------- */
590 tl.to(
591 "#badge-left",
592 {
593 scale: 1,
594 opacity: 1,
595 duration: TIMING.badgeEntryDur,
596 ease: "back.out(1.7)",
597 },
598 TIMING.badgeLeftAt,
599 );
600
601 tl.to(
602 "#badge-right",
603 {
604 scale: 1,
605 opacity: 1,
606 duration: TIMING.badgeEntryDur,
607 ease: "back.out(1.7)",
608 },
609 TIMING.badgeRightAt,
610 );
611
612 /* ----------------------------------------------------------------
613 CONTINUOUS FLOATING — shared scene-ticker onUpdate
614 Six gsap.set calls per frame, batched by the browser.
615 ---------------------------------------------------------------- */
616 const leftPos = document.querySelector("#card-left .card-pos");
617 const rightPos = document.querySelector("#card-right .card-pos");
618 const leftTilt = document.querySelector("#card-left .card-tilt");
619 const rightTilt = document.querySelector("#card-right .card-tilt");
620
621 tl.to(
622 { tick: 0 },
623 {
624 tick: 1,
625 duration: TOTAL_DUR,
626 ease: "none",
627 onUpdate: function () {
628 const t = tl.time();
629 // Cards float in opposition (phase π apart).
630 const lY = Math.sin(t * TIMING.floatYSpeed) * TIMING.floatYAmp;
631 const lR = Math.sin(t * TIMING.floatRSpeed) * TIMING.floatRAmp;
632 const rY = Math.sin(t * TIMING.floatYSpeed + Math.PI) * TIMING.floatYAmp;
633 const rR = Math.sin(t * TIMING.floatRSpeed + Math.PI) * TIMING.floatRAmp;
634 gsap.set(leftPos, { y: lY });
635 gsap.set(rightPos, { y: rY });
636 gsap.set(leftTilt, { rotationY: TIMING.baseTilt + lR });
637 gsap.set(rightTilt, { rotationY: -TIMING.baseTilt + rR });
638
639 // Badges — small shared y oscillation.
640 const bY = Math.sin(t * TIMING.badgeYSpeed) * TIMING.badgeYAmp;
641 gsap.set(badgeLeftEl, { y: bY });
642 gsap.set(badgeRightEl, { y: bY });
643 },
644 },
645 0,
646 );
647 </script>
648 </body>
649</html>