Files
heygen-com--hyperframes/skills/hyperframes-animation/examples/comparison-split-cards.html
T
wehub-resource-sync 85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:58:35 +08:00

650 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Scene 09 — Split Comparison Reveal</title>
<!--
HyperFrames composition.tsx.
Choreography (3 phases, 5 seconds total):
0.17 0.83s Title slides down from top: "Build Video With HyperFrames"
0.50 1.83s Left card "HTML Composition" enters from left (+16° rotateY)
0.83 1.83s Right card "Render Pipeline" enters from right (-16° rotateY)
1.67 2.17s Left pill badge "Seekable Timeline" pops in with back.out(1.7)
2.00 2.50s Right pill badge "Render Ready" pops in
0 5.00s Continuous floating: cards y ±6 px / rotation ±1° (phase-opposed)
Badges y ±5 px (slow shared sine)
and .card-tilt (static rotateY + float rotation) so entry and float
don't fight on the same alias
- All continuous floating consolidated into one shared scene-ticker onUpdate
(6 gsap.set calls per frame, batched by the browser)
- Phase offset Math.PI between the two cards' floats — opposed breathing
- Card images are HyperFrames workflow mockups (no asset files needed)
- Ambient dual-glow via two radial-gradients in a single overlay
-->
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
:root {
--bg-dark: #0a1415;
--text-primary: #ffffff;
--text-secondary: rgba(255, 255, 255, 0.65);
--brand-cyan: #18d9e8;
--brand-cyan-glow: rgba(24, 217, 232, 1);
--brand-green: #7bea5a;
--brand-green-glow: rgba(123, 234, 90, 1);
--glass-bg: rgba(255, 255, 255, 0.08);
--border: rgba(255, 255, 255, 0.12);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background: var(--bg-dark);
font-family:
"Inter",
system-ui,
-apple-system,
sans-serif;
color: var(--text-primary);
}
.stage {
position: absolute;
inset: 0;
overflow: hidden;
}
/* ============================================================
BACKGROUND
============================================================ */
.bg {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 35% 50%, rgba(24, 217, 232, 0.2), transparent 45%),
radial-gradient(ellipse at 72% 50%, rgba(123, 234, 90, 0.16), transparent 45%),
linear-gradient(135deg, #3a3a3a 0%, #17211f 30%, #0b2328 48%, #1f3518 70%, #343434 100%);
}
/* ============================================================
TITLE
============================================================ */
.title {
position: absolute;
top: 60px;
left: 50%;
transform: translateX(-50%);
font-size: 88px;
font-weight: 700;
color: var(--text-primary);
text-align: center;
letter-spacing: 0;
white-space: nowrap;
will-change: transform, opacity;
/* initial opacity 0 + y -40 set via gsap.set */
}
.title .accent {
color: var(--brand-cyan);
text-shadow: 0 0 34px rgba(24, 217, 232, 0.28);
}
/* ============================================================
CARDS ROW
============================================================ */
.cards-row {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
gap: 76px;
padding-top: 30px;
perspective: 980px;
perspective-origin: 50% 42%;
transform-style: preserve-3d;
}
/* Two nested wrappers per card */
.card {
transform-style: preserve-3d;
}
.card-pos {
perspective: 980px;
perspective-origin: 50% 45%;
transform-style: preserve-3d;
will-change: transform, opacity;
/* initial x/scale/opacity set via gsap.set */
}
.card-tilt {
transform-style: preserve-3d;
will-change: transform;
/* initial rotationY set via gsap.set */
}
.card-content {
width: 720px;
transform-style: preserve-3d;
}
.card-image {
width: 100%;
height: 500px;
border-radius: 24px;
overflow: hidden;
border: 1px solid var(--border);
position: relative;
transform: translateZ(0) rotateX(0.01deg);
backface-visibility: hidden;
}
.card-left .card-tilt {
transform-origin: 100% 50%;
}
.card-right .card-tilt {
transform-origin: 0% 50%;
}
.card-label {
margin-top: 26px;
text-align: center;
font-size: 54px;
font-weight: 700;
color: var(--text-primary);
transform: translateZ(34px);
text-shadow: 0 0 30px rgba(24, 217, 232, 0.16);
}
.card-subtitle {
margin-top: 8px;
text-align: center;
font-size: 24px;
color: var(--text-secondary);
transform: translateZ(26px);
}
/* Left card: shadow falls right (positive x in box-shadow = right) */
.card-left .card-image {
box-shadow:
30px 30px 60px rgba(0, 0, 0, 0.45),
0 0 60px rgba(24, 217, 232, 0.2);
}
/* Right card: shadow falls left */
.card-right .card-image {
box-shadow:
-30px 30px 60px rgba(0, 0, 0, 0.45),
0 0 60px rgba(123, 234, 90, 0.2);
}
/* ============================================================
CARD IMAGE PLACEHOLDERS (mock UI)
============================================================ */
/* Left card — HTML Composition: gradient + timed clip grid */
.card-left .card-image {
background: linear-gradient(
135deg,
rgba(12, 38, 42, 0.96) 0%,
rgba(18, 66, 63, 0.9) 52%,
rgba(32, 57, 38, 0.94) 100%
);
}
.mock-templates {
position: absolute;
inset: 0;
padding: 56px 66px;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 22px;
opacity: 0.22;
}
.mock-thumb {
background: linear-gradient(135deg, rgba(24, 217, 232, 0.2), rgba(123, 234, 90, 0.08));
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
align-items: flex-end;
justify-content: flex-start;
padding: 14px;
transform: translateZ(18px);
box-shadow: 0 14px 30px rgba(0, 0, 0, 0.18);
}
.mock-thumb .label {
font-size: 18px;
font-weight: 600;
color: rgba(255, 255, 255, 0.8);
}
/* Right card — Render Pipeline: validation rows */
.card-right .card-image {
background: linear-gradient(
135deg,
rgba(23, 45, 34, 0.96) 0%,
rgba(17, 70, 73, 0.9) 50%,
rgba(38, 57, 45, 0.94) 100%
);
}
.mock-team {
position: absolute;
inset: 0;
padding: 66px 72px;
display: flex;
flex-direction: column;
gap: 20px;
justify-content: center;
opacity: 0.24;
}
.mock-row {
display: flex;
align-items: center;
gap: 14px;
padding: 15px 20px;
background: rgba(255, 255, 255, 0.06);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.08);
transform: translateZ(18px);
box-shadow: 0 14px 30px rgba(0, 0, 0, 0.18);
}
.mock-avatar {
width: 44px;
height: 44px;
border-radius: 50%;
flex-shrink: 0;
background: linear-gradient(135deg, var(--brand-green), var(--brand-cyan));
}
.mock-avatar.purple {
background: linear-gradient(135deg, var(--brand-cyan), #38bdf8);
}
.mock-avatar.green {
background: linear-gradient(135deg, var(--brand-green), var(--brand-cyan));
}
.mock-avatar.orange {
background: linear-gradient(135deg, #facc15, var(--brand-green));
}
.mock-row .name {
font-size: 22px;
font-weight: 600;
color: rgba(255, 255, 255, 0.85);
}
.mock-row .status {
margin-left: auto;
font-size: 16px;
color: var(--brand-green);
}
/* ============================================================
FLOATING BADGES
============================================================ */
.badge {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 22px;
border-radius: 999px;
background-color: var(--glass-bg);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
will-change: transform, opacity;
z-index: 50;
}
.badge-left {
border: 1px solid rgba(24, 217, 232, 0.4);
box-shadow: 0 0 30px rgba(24, 217, 232, 0.35);
}
.badge-right {
border: 1px solid rgba(123, 234, 90, 0.4);
box-shadow: 0 0 30px rgba(123, 234, 90, 0.35);
}
.badge-icon-wrap {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--bg-dark);
flex-shrink: 0;
}
.badge-left .badge-icon-wrap {
background-color: var(--brand-cyan);
}
.badge-right .badge-icon-wrap {
background-color: var(--brand-green);
}
.badge-label {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
}
/* ============================================================
AMBIENT GLOW + VIGNETTE
============================================================ */
.ambient-glow {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 30% 50%, var(--brand-cyan-glow) 0%, transparent 35%),
radial-gradient(ellipse at 70% 50%, var(--brand-green-glow) 0%, transparent 35%);
opacity: 0.13;
pointer-events: none;
}
.vignette {
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.45) 100%);
pointer-events: none;
z-index: 400;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="main"
data-start="0"
data-duration="5"
data-width="1920"
data-height="1080"
>
<div
class="stage clip"
data-start="0"
data-duration="5"
data-track-index="1"
id="scene-stage"
>
<div class="bg"></div>
<!-- Title -->
<div class="title" id="title">Build Video With <span class="accent">HyperFrames</span></div>
<!-- Cards row -->
<div class="cards-row">
<!-- Left card: HTML Composition -->
<div class="card card-left" id="card-left">
<div class="card-pos">
<div class="card-tilt">
<div class="card-content">
<div class="card-image">
<div class="mock-templates">
<div class="mock-thumb"><span class="label">HTML</span></div>
<div class="mock-thumb"><span class="label">CSS</span></div>
<div class="mock-thumb"><span class="label">GSAP</span></div>
<div class="mock-thumb"><span class="label">Audio</span></div>
<div class="mock-thumb"><span class="label">Captions</span></div>
<div class="mock-thumb"><span class="label">Assets</span></div>
</div>
</div>
<div class="card-label">HTML Composition</div>
<div class="card-subtitle">Timed DOM clips, media, and motion</div>
</div>
</div>
</div>
</div>
<!-- Right card: Render Pipeline -->
<div class="card card-right" id="card-right">
<div class="card-pos">
<div class="card-tilt">
<div class="card-content">
<div class="card-image">
<div class="mock-team">
<div class="mock-row">
<div class="mock-avatar purple"></div>
<span class="name">Register timeline</span>
<span class="status">● seekable</span>
</div>
<div class="mock-row">
<div class="mock-avatar green"></div>
<span class="name">Validate layout</span>
<span class="status">● clean</span>
</div>
<div class="mock-row">
<div class="mock-avatar orange"></div>
<span class="name">Render frames</span>
<span class="status">● stable</span>
</div>
<div class="mock-row">
<div class="mock-avatar"></div>
<span class="name">Publish MP4</span>
<span class="status">● ready</span>
</div>
</div>
</div>
<div class="card-label">Render Pipeline</div>
<div class="card-subtitle">Preview, check, render, publish</div>
</div>
</div>
</div>
</div>
</div>
<!-- Floating badges -->
<div class="badge badge-left" id="badge-left">
<div class="badge-icon-wrap">
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 2L2 7L12 12L22 7L12 2Z" />
<path d="M2 17L12 22L22 17" />
<path d="M2 12L12 17L22 12" />
</svg>
</div>
<span class="badge-label">Seekable Timeline</span>
</div>
<div class="badge badge-right" id="badge-right">
<div class="badge-icon-wrap">
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M17 21V19C17 16.7909 15.2091 15 13 15H5C2.79086 15 1 16.7909 1 19V21" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21V19C22.9986 17.1771 21.765 15.5857 20 15.13" />
<path
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"
/>
</svg>
</div>
<span class="badge-label">Render Ready</span>
</div>
<div class="ambient-glow"></div>
</div>
<div class="vignette"></div>
</div>
<script>
/* ================================================================
CONSTANTS
================================================================ */
const W = 1920,
H = 1080;
const TOTAL_DUR = 5.0;
const TIMING = {
// Phase 1: title
titleAt: 0.17,
titleDur: 0.67,
// Phase 2: cards
leftAt: 0.5,
rightAt: 0.83,
entryDur: 0.7,
slideDist: 100,
baseTilt: 18,
// Phase 3: badges
badgeLeftAt: 1.67,
badgeRightAt: 2.0,
badgeEntryDur: 0.5,
// Continuous float
floatYSpeed: 0.02 * 30, // = 0.6 rad/sec
floatYAmp: 6,
floatRSpeed: 0.015 * 30, // = 0.45 rad/sec
floatRAmp: 1,
badgeYSpeed: 0.025 * 30, // = 0.75 rad/sec
badgeYAmp: 5,
};
/* ================================================================
BADGE POSITIONS — set via CSS left/top once (not tweened)
================================================================ */
const badgeLeftEl = document.getElementById("badge-left");
const badgeRightEl = document.getElementById("badge-right");
badgeLeftEl.style.left = W * 0.12 + "px"; // 230 px
badgeLeftEl.style.top = H * 0.35 + "px"; // 378 px
badgeLeftEl.style.position = "absolute";
badgeRightEl.style.left = W * 0.75 + "px"; // 1440 px
badgeRightEl.style.top = H * 0.38 + "px"; // 410 px
badgeRightEl.style.position = "absolute";
/* ================================================================
INITIAL STATES (via gsap.set, before the timeline runs)
================================================================ */
gsap.set("#title", { opacity: 0, y: -40 });
gsap.set("#card-left .card-pos", { x: -TIMING.slideDist, scale: 0.8, opacity: 0, y: 0 });
gsap.set("#card-right .card-pos", { x: TIMING.slideDist, scale: 0.8, opacity: 0, y: 0 });
gsap.set("#card-left .card-tilt", { rotationY: TIMING.baseTilt });
gsap.set("#card-right .card-tilt", { rotationY: -TIMING.baseTilt });
gsap.set(["#badge-left", "#badge-right"], { scale: 0, opacity: 0, y: 0 });
/* ================================================================
TIMELINE
================================================================ */
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
window.__timelines["main"] = tl;
/* ----------------------------------------------------------------
PHASE 1: Title slides down
---------------------------------------------------------------- */
tl.to(
"#title",
{
opacity: 1,
y: 0,
duration: TIMING.titleDur,
ease: "power3.out", // spring(stiffness:100, damping:16)
},
TIMING.titleAt,
);
/* ----------------------------------------------------------------
PHASE 2: Cards enter from opposite sides
---------------------------------------------------------------- */
tl.to(
"#card-left .card-pos",
{
x: 0,
scale: 1,
opacity: 1,
duration: TIMING.entryDur,
ease: "power3.out",
},
TIMING.leftAt,
);
tl.to(
"#card-right .card-pos",
{
x: 0,
scale: 1,
opacity: 1,
duration: TIMING.entryDur,
ease: "power3.out",
},
TIMING.rightAt,
);
/* ----------------------------------------------------------------
PHASE 3: Badges pop in
---------------------------------------------------------------- */
tl.to(
"#badge-left",
{
scale: 1,
opacity: 1,
duration: TIMING.badgeEntryDur,
ease: "back.out(1.7)",
},
TIMING.badgeLeftAt,
);
tl.to(
"#badge-right",
{
scale: 1,
opacity: 1,
duration: TIMING.badgeEntryDur,
ease: "back.out(1.7)",
},
TIMING.badgeRightAt,
);
/* ----------------------------------------------------------------
CONTINUOUS FLOATING — shared scene-ticker onUpdate
Six gsap.set calls per frame, batched by the browser.
---------------------------------------------------------------- */
const leftPos = document.querySelector("#card-left .card-pos");
const rightPos = document.querySelector("#card-right .card-pos");
const leftTilt = document.querySelector("#card-left .card-tilt");
const rightTilt = document.querySelector("#card-right .card-tilt");
tl.to(
{ tick: 0 },
{
tick: 1,
duration: TOTAL_DUR,
ease: "none",
onUpdate: function () {
const t = tl.time();
// Cards float in opposition (phase π apart).
const lY = Math.sin(t * TIMING.floatYSpeed) * TIMING.floatYAmp;
const lR = Math.sin(t * TIMING.floatRSpeed) * TIMING.floatRAmp;
const rY = Math.sin(t * TIMING.floatYSpeed + Math.PI) * TIMING.floatYAmp;
const rR = Math.sin(t * TIMING.floatRSpeed + Math.PI) * TIMING.floatRAmp;
gsap.set(leftPos, { y: lY });
gsap.set(rightPos, { y: rY });
gsap.set(leftTilt, { rotationY: TIMING.baseTilt + lR });
gsap.set(rightTilt, { rotationY: -TIMING.baseTilt + rR });
// Badges — small shared y oscillation.
const bY = Math.sin(t * TIMING.badgeYSpeed) * TIMING.badgeYAmp;
gsap.set(badgeLeftEl, { y: bY });
gsap.set(badgeRightEl, { y: bY });
},
},
0,
);
</script>
</body>
</html>