chore: import upstream snapshot with attribution
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Docs / Validate docs (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Sync skills to ClawHub / Publish changed skills (push) Waiting to run
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
@@ -0,0 +1,947 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>app-showcase</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background-color: #0a0a0f;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
data-composition-id="app-showcase"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
data-start="0"
|
||||
data-duration="5.5"
|
||||
>
|
||||
<!-- Background -->
|
||||
<div class="s3-bg" id="s3Bg"></div>
|
||||
<div class="s3-bg-glow" id="s3BgGlow"></div>
|
||||
|
||||
<!-- Stage wrapper for ambient scale -->
|
||||
<div class="s3-stage" id="s3Stage">
|
||||
<!-- ═══════════════════════════════════════
|
||||
LEFT PHONE — Lifestyle / Motivational
|
||||
═══════════════════════════════════════ -->
|
||||
<div class="s3-phone s3-phone-left" id="s3PhoneLeft">
|
||||
<div class="s3-bezel">
|
||||
<div class="s3-notch"></div>
|
||||
<div class="s3-screen s3-screen-left">
|
||||
<!-- Abstract exercise shapes -->
|
||||
<div class="left-shapes">
|
||||
<svg viewBox="0 0 304 400" xmlns="http://www.w3.org/2000/svg" fill="none">
|
||||
<!-- Abstract running figure — geometric -->
|
||||
<circle cx="152" cy="80" r="28" fill="#271f15" opacity="0.15" />
|
||||
<line
|
||||
x1="152"
|
||||
y1="108"
|
||||
x2="152"
|
||||
y2="200"
|
||||
stroke="#271f15"
|
||||
stroke-width="6"
|
||||
stroke-linecap="round"
|
||||
opacity="0.15"
|
||||
/>
|
||||
<line
|
||||
x1="152"
|
||||
y1="145"
|
||||
x2="115"
|
||||
y2="175"
|
||||
stroke="#271f15"
|
||||
stroke-width="6"
|
||||
stroke-linecap="round"
|
||||
opacity="0.15"
|
||||
/>
|
||||
<line
|
||||
x1="152"
|
||||
y1="145"
|
||||
x2="195"
|
||||
y2="165"
|
||||
stroke="#271f15"
|
||||
stroke-width="6"
|
||||
stroke-linecap="round"
|
||||
opacity="0.15"
|
||||
/>
|
||||
<line
|
||||
x1="152"
|
||||
y1="200"
|
||||
x2="120"
|
||||
y2="260"
|
||||
stroke="#271f15"
|
||||
stroke-width="6"
|
||||
stroke-linecap="round"
|
||||
opacity="0.15"
|
||||
/>
|
||||
<line
|
||||
x1="152"
|
||||
y1="200"
|
||||
x2="190"
|
||||
y2="255"
|
||||
stroke="#271f15"
|
||||
stroke-width="6"
|
||||
stroke-linecap="round"
|
||||
opacity="0.15"
|
||||
/>
|
||||
<!-- Decorative arcs -->
|
||||
<path
|
||||
d="M60 300 Q152 240 244 300"
|
||||
stroke="#e4fa72"
|
||||
stroke-width="3"
|
||||
fill="none"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<path
|
||||
d="M40 330 Q152 260 264 330"
|
||||
stroke="#e4fa72"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
opacity="0.3"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="left-bottom">
|
||||
<div class="left-tagline" id="s3Tagline">Unleash Full Potential</div>
|
||||
<div class="left-cta">START NOW</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════
|
||||
CENTER PHONE — Dashboard (Hero)
|
||||
═══════════════════════════════════════ -->
|
||||
<div class="s3-phone s3-phone-center" id="s3PhoneCenter">
|
||||
<div class="s3-bezel">
|
||||
<div class="s3-notch"></div>
|
||||
<div class="s3-screen s3-screen-center">
|
||||
<!-- Header -->
|
||||
<div class="center-header">
|
||||
<div class="center-avatar">JM</div>
|
||||
<div class="center-user-info">
|
||||
<div class="center-name">James Medrano</div>
|
||||
<div class="center-subtitle">Premium Member</div>
|
||||
</div>
|
||||
<div class="center-bell">
|
||||
<svg viewBox="0 0 20 20" width="20" height="20" fill="none">
|
||||
<path
|
||||
d="M10 2a5 5 0 00-5 5v3l-1.5 2.5h13L15 10V7a5 5 0 00-5-5z"
|
||||
stroke="#7c857c"
|
||||
stroke-width="1.4"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 16.5a2 2 0 004 0"
|
||||
stroke="#7c857c"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<circle cx="14" cy="5" r="3" fill="#e4fa72" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Weekly Goal -->
|
||||
<div class="center-goal">
|
||||
<div class="goal-label">Weekly Goal</div>
|
||||
<div class="goal-ring-wrap">
|
||||
<svg class="goal-ring" viewBox="0 0 140 140">
|
||||
<circle
|
||||
class="ring-track"
|
||||
cx="70"
|
||||
cy="70"
|
||||
r="52"
|
||||
fill="none"
|
||||
stroke="#333"
|
||||
stroke-width="10"
|
||||
/>
|
||||
<circle
|
||||
class="ring-fill"
|
||||
id="s3Ring"
|
||||
cx="70"
|
||||
cy="70"
|
||||
r="52"
|
||||
fill="none"
|
||||
stroke="#e4fa72"
|
||||
stroke-width="10"
|
||||
stroke-linecap="round"
|
||||
transform="rotate(-90 70 70)"
|
||||
/>
|
||||
</svg>
|
||||
<div class="goal-count">
|
||||
<span class="goal-num">2</span>
|
||||
<span class="goal-sep">/</span>
|
||||
<span class="goal-den">3</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="goal-days">
|
||||
<span class="day done">M</span>
|
||||
<span class="day done">T</span>
|
||||
<span class="day">W</span>
|
||||
<span class="day">T</span>
|
||||
<span class="day">F</span>
|
||||
<span class="day">S</span>
|
||||
<span class="day">S</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Your Progress -->
|
||||
<div class="center-progress">
|
||||
<div class="progress-header">
|
||||
<span class="progress-title">Your Progress</span>
|
||||
<span class="progress-link">See all</span>
|
||||
</div>
|
||||
<div class="progress-bars">
|
||||
<div class="bar-row">
|
||||
<div class="bar-icon">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14" fill="none">
|
||||
<path
|
||||
d="M8 2l1.5 3H13l-2.5 2 1 3L8 8.5 4.5 10l1-3L3 5h3.5z"
|
||||
fill="#e4fa72"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="bar-info">
|
||||
<span class="bar-name">Running</span>
|
||||
<span class="bar-val">5.2 km</span>
|
||||
</div>
|
||||
<div class="bar-track"><div class="bar-value bar-v1" id="s3Bar1"></div></div>
|
||||
</div>
|
||||
<div class="bar-row">
|
||||
<div class="bar-icon">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14" fill="none">
|
||||
<circle cx="8" cy="8" r="5" stroke="#e4fa72" stroke-width="2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="bar-info">
|
||||
<span class="bar-name">Cycling</span>
|
||||
<span class="bar-val">12.8 km</span>
|
||||
</div>
|
||||
<div class="bar-track"><div class="bar-value bar-v2" id="s3Bar2"></div></div>
|
||||
</div>
|
||||
<div class="bar-row">
|
||||
<div class="bar-icon">
|
||||
<svg viewBox="0 0 16 16" width="14" height="14" fill="none">
|
||||
<rect
|
||||
x="3"
|
||||
y="6"
|
||||
width="10"
|
||||
height="6"
|
||||
rx="1"
|
||||
stroke="#e4fa72"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<path d="M6 6V4a2 2 0 014 0v2" stroke="#e4fa72" stroke-width="1.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="bar-info">
|
||||
<span class="bar-name">Strength</span>
|
||||
<span class="bar-val">45 min</span>
|
||||
</div>
|
||||
<div class="bar-track"><div class="bar-value bar-v3" id="s3Bar3"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════
|
||||
RIGHT PHONE — Calories Data
|
||||
═══════════════════════════════════════ -->
|
||||
<div class="s3-phone s3-phone-right" id="s3PhoneRight">
|
||||
<div class="s3-bezel">
|
||||
<div class="s3-notch"></div>
|
||||
<div class="s3-screen s3-screen-right">
|
||||
<div class="right-header">
|
||||
<div class="right-title">Burned Calories</div>
|
||||
<div class="right-value">740 <span class="right-unit">kcal</span></div>
|
||||
</div>
|
||||
|
||||
<div class="right-graph-wrap">
|
||||
<svg class="right-graph" viewBox="0 0 260 130" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Grid lines -->
|
||||
<line
|
||||
x1="10"
|
||||
y1="25"
|
||||
x2="250"
|
||||
y2="25"
|
||||
stroke="#d9e0bc"
|
||||
stroke-width="0.5"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<line
|
||||
x1="10"
|
||||
y1="55"
|
||||
x2="250"
|
||||
y2="55"
|
||||
stroke="#d9e0bc"
|
||||
stroke-width="0.5"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<line
|
||||
x1="10"
|
||||
y1="85"
|
||||
x2="250"
|
||||
y2="85"
|
||||
stroke="#d9e0bc"
|
||||
stroke-width="0.5"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<line
|
||||
x1="10"
|
||||
y1="115"
|
||||
x2="250"
|
||||
y2="115"
|
||||
stroke="#d9e0bc"
|
||||
stroke-width="0.5"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<!-- Area fill -->
|
||||
<path
|
||||
class="graph-area"
|
||||
id="s3GraphArea"
|
||||
d="M10,105 L50,80 90,90 130,45 170,60 210,25 250,35 250,115 10,115 Z"
|
||||
fill="#e4fa72"
|
||||
opacity="0.15"
|
||||
/>
|
||||
<!-- Line -->
|
||||
<polyline
|
||||
class="graph-line"
|
||||
id="s3GraphLine"
|
||||
points="10,105 50,80 90,90 130,45 170,60 210,25 250,35"
|
||||
fill="none"
|
||||
stroke="#e4fa72"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<!-- Dots -->
|
||||
<circle class="graph-dot" cx="10" cy="105" r="3.5" fill="#e4fa72" />
|
||||
<circle class="graph-dot" cx="50" cy="80" r="3.5" fill="#e4fa72" />
|
||||
<circle class="graph-dot" cx="90" cy="90" r="3.5" fill="#e4fa72" />
|
||||
<circle class="graph-dot" cx="130" cy="45" r="3.5" fill="#e4fa72" />
|
||||
<circle class="graph-dot" cx="170" cy="60" r="3.5" fill="#e4fa72" />
|
||||
<circle class="graph-dot" cx="210" cy="25" r="3.5" fill="#e4fa72" />
|
||||
<circle class="graph-dot" cx="250" cy="35" r="3.5" fill="#e4fa72" />
|
||||
</svg>
|
||||
<div class="right-labels">
|
||||
<span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span
|
||||
><span>Sat</span><span>Sun</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-stats">
|
||||
<div class="stat-pill">
|
||||
<span class="stat-dot" style="background: #e4fa72"></span>
|
||||
<span class="stat-label">Active</span>
|
||||
<span class="stat-num">540</span>
|
||||
</div>
|
||||
<div class="stat-pill">
|
||||
<span class="stat-dot" style="background: #7c857c"></span>
|
||||
<span class="stat-label">Rest</span>
|
||||
<span class="stat-num">200</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap");
|
||||
|
||||
[data-composition-id="app-showcase"] {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
font-family: "DM Sans", sans-serif;
|
||||
color: #271f15;
|
||||
}
|
||||
|
||||
/* ── Background ── */
|
||||
[data-composition-id="app-showcase"] .s3-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(160deg, #f1f2ec 0%, #e8ecda 40%, #f1f2ec 100%);
|
||||
}
|
||||
[data-composition-id="app-showcase"] .s3-bg-glow {
|
||||
position: absolute;
|
||||
width: 900px;
|
||||
height: 900px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: radial-gradient(circle, rgba(228, 250, 114, 0.18) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* ── Stage ── */
|
||||
[data-composition-id="app-showcase"] .s3-stage {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
/* ── Phones ── */
|
||||
[data-composition-id="app-showcase"] .s3-phone {
|
||||
position: absolute;
|
||||
width: 320px;
|
||||
height: 650px;
|
||||
top: 215px;
|
||||
left: 800px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .s3-phone-center {
|
||||
z-index: 30;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .s3-phone-left {
|
||||
z-index: 20;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .s3-phone-right {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
/* ── Bezel ── */
|
||||
[data-composition-id="app-showcase"] .s3-bezel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 42px;
|
||||
background: #271f15;
|
||||
padding: 8px;
|
||||
box-shadow:
|
||||
0 24px 64px rgba(39, 31, 21, 0.35),
|
||||
0 4px 16px rgba(39, 31, 21, 0.15);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .s3-notch {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100px;
|
||||
height: 26px;
|
||||
background: #271f15;
|
||||
border-radius: 0 0 18px 18px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* ── Screens ── */
|
||||
[data-composition-id="app-showcase"] .s3-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 36px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Left screen */
|
||||
[data-composition-id="app-showcase"] .s3-screen-left {
|
||||
background: linear-gradient(160deg, #d9e0bc 0%, #c8d4a0 50%, #b8c78a 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .left-shapes {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .left-shapes svg {
|
||||
width: 200px;
|
||||
height: 280px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .left-bottom {
|
||||
padding: 0 28px 40px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .left-tagline {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #271f15;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .left-cta {
|
||||
display: inline-block;
|
||||
background: #271f15;
|
||||
color: #e4fa72;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 10px 28px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
/* Center screen */
|
||||
[data-composition-id="app-showcase"] .s3-screen-center {
|
||||
background: #1a1a1a;
|
||||
padding: 42px 22px 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
[data-composition-id="app-showcase"] .center-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .center-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #e4fa72, #d9e0bc);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #271f15;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .center-user-info {
|
||||
flex: 1;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .center-name {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #f1f2ec;
|
||||
line-height: 1.2;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .center-subtitle {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #7c857c;
|
||||
margin-top: 1px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .center-bell {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Weekly Goal */
|
||||
[data-composition-id="app-showcase"] .center-goal {
|
||||
background: #242424;
|
||||
border-radius: 20px;
|
||||
padding: 18px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .goal-label {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #7c857c;
|
||||
align-self: flex-start;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .goal-ring-wrap {
|
||||
position: relative;
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .goal-ring {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .ring-track {
|
||||
opacity: 0.3;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .goal-count {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .goal-num {
|
||||
font-size: 36px;
|
||||
font-weight: 700;
|
||||
color: #e4fa72;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .goal-sep {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
color: #7c857c;
|
||||
margin: 0 2px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .goal-den {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
color: #7c857c;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .goal-days {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .day {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: #7c857c;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .day.done {
|
||||
background: #e4fa72;
|
||||
color: #271f15;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Progress section */
|
||||
[data-composition-id="app-showcase"] .center-progress {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .progress-title {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #f1f2ec;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .progress-link {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #e4fa72;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .progress-bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .bar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .bar-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
background: #2a2a2a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .bar-info {
|
||||
width: 70px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .bar-name {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #f1f2ec;
|
||||
line-height: 1.2;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .bar-val {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
color: #7c857c;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .bar-track {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: #2a2a2a;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .bar-value {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
background: #e4fa72;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
/* Right screen */
|
||||
[data-composition-id="app-showcase"] .s3-screen-right {
|
||||
background: #f1f2ec;
|
||||
padding: 42px 20px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .right-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .right-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #7c857c;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .right-value {
|
||||
font-size: 40px;
|
||||
font-weight: 700;
|
||||
color: #271f15;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .right-unit {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
color: #7c857c;
|
||||
}
|
||||
|
||||
[data-composition-id="app-showcase"] .right-graph-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .right-graph {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .right-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 6px;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
color: #7c857c;
|
||||
}
|
||||
|
||||
[data-composition-id="app-showcase"] .right-stats {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .stat-pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: #e8ecda;
|
||||
border-radius: 20px;
|
||||
padding: 8px 14px;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .stat-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .stat-label {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #7c857c;
|
||||
}
|
||||
[data-composition-id="app-showcase"] .stat-num {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #271f15;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var S = '[data-composition-id="app-showcase"] ';
|
||||
window.__timelines = window.__timelines || {};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
// ── SVG setup ──
|
||||
var graphLine = document.querySelector(S + "#s3GraphLine");
|
||||
var graphLen = graphLine.getTotalLength();
|
||||
gsap.set(graphLine, { strokeDasharray: graphLen, strokeDashoffset: graphLen });
|
||||
|
||||
var graphArea = document.querySelector(S + "#s3GraphArea");
|
||||
gsap.set(graphArea, { opacity: 0 });
|
||||
|
||||
var graphDots = document.querySelectorAll(S + ".graph-dot");
|
||||
gsap.set(graphDots, { opacity: 0, scale: 0 });
|
||||
|
||||
var CIRC = 2 * 3.14159265 * 52;
|
||||
gsap.set(S + "#s3Ring", { strokeDasharray: CIRC, strokeDashoffset: CIRC });
|
||||
|
||||
// ── Initial states ──
|
||||
gsap.set(S + ".s3-bg", { opacity: 0 });
|
||||
gsap.set(S + ".s3-bg-glow", { opacity: 0 });
|
||||
// Center phone: starts scaled down + hidden
|
||||
gsap.set(S + "#s3PhoneCenter", { opacity: 0, scale: 0.85 });
|
||||
// Side phones: start stacked behind center (same position), hidden
|
||||
gsap.set(S + "#s3PhoneLeft", { opacity: 0, x: 0, rotation: 0 });
|
||||
gsap.set(S + "#s3PhoneRight", { opacity: 0, x: 0, rotation: 0 });
|
||||
gsap.set(S + ".bar-value", { width: "0%" });
|
||||
gsap.set(S + "#s3Tagline", { opacity: 0, y: 20 });
|
||||
gsap.set(S + ".left-cta", { opacity: 0, y: 12 });
|
||||
|
||||
// ════════════════════════════════════
|
||||
// BUILD PHASE (0–2s)
|
||||
// ════════════════════════════════════
|
||||
|
||||
// Background
|
||||
tl.to(S + ".s3-bg", { opacity: 1, duration: 0.8, ease: "power1.out" }, 0);
|
||||
tl.to(S + ".s3-bg-glow", { opacity: 1, duration: 1.2, ease: "power1.out" }, 0.2);
|
||||
|
||||
// Center phone — scales up first
|
||||
tl.to(
|
||||
S + "#s3PhoneCenter",
|
||||
{
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration: 0.7,
|
||||
ease: "back.out(1.4)",
|
||||
},
|
||||
0.2,
|
||||
);
|
||||
|
||||
// Side phones: opacity matches center (0.7s, back.out), fan starts at 0.1
|
||||
tl.to(S + "#s3PhoneLeft", { opacity: 1, duration: 0.7, ease: "back.out(1.4)" }, 0.3);
|
||||
tl.to(
|
||||
S + "#s3PhoneLeft",
|
||||
{
|
||||
x: -340,
|
||||
rotation: -12,
|
||||
duration: 1.2,
|
||||
ease: "expo.out",
|
||||
},
|
||||
0.3,
|
||||
);
|
||||
|
||||
tl.to(S + "#s3PhoneRight", { opacity: 1, duration: 0.7, ease: "back.out(1.4)" }, 0.4);
|
||||
tl.to(
|
||||
S + "#s3PhoneRight",
|
||||
{
|
||||
x: 340,
|
||||
rotation: 12,
|
||||
duration: 1.2,
|
||||
ease: "expo.out",
|
||||
},
|
||||
0.4,
|
||||
);
|
||||
|
||||
// ════════════════════════════════════
|
||||
// BREATHE PHASE (2–4s)
|
||||
// ════════════════════════════════════
|
||||
|
||||
// Progress ring fills to 2/3
|
||||
tl.to(
|
||||
S + "#s3Ring",
|
||||
{
|
||||
strokeDashoffset: CIRC * (1 - 2 / 3),
|
||||
duration: 1.2,
|
||||
ease: "circ.out",
|
||||
},
|
||||
1.8,
|
||||
);
|
||||
|
||||
// Activity bars stagger in
|
||||
tl.to(S + "#s3Bar1", { width: "75%", duration: 0.8, ease: "power3.out" }, 2.1);
|
||||
tl.to(S + "#s3Bar2", { width: "50%", duration: 0.8, ease: "power3.out" }, 2.3);
|
||||
tl.to(S + "#s3Bar3", { width: "90%", duration: 0.8, ease: "power3.out" }, 2.5);
|
||||
|
||||
// Graph line draws on
|
||||
tl.to(
|
||||
graphLine,
|
||||
{
|
||||
strokeDashoffset: 0,
|
||||
duration: 1.4,
|
||||
ease: "sine.inOut",
|
||||
},
|
||||
2.0,
|
||||
);
|
||||
|
||||
// Graph area fades in
|
||||
tl.to(graphArea, { opacity: 0.15, duration: 0.8, ease: "power1.out" }, 2.6);
|
||||
|
||||
// Graph dots pop in
|
||||
tl.to(
|
||||
graphDots,
|
||||
{
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
duration: 0.3,
|
||||
ease: "back.out(2)",
|
||||
stagger: { each: 0.06, ease: "power2.in" },
|
||||
},
|
||||
3.0,
|
||||
);
|
||||
|
||||
// Tagline and CTA on left phone
|
||||
tl.to(
|
||||
S + "#s3Tagline",
|
||||
{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
duration: 0.6,
|
||||
ease: "power2.out",
|
||||
},
|
||||
2.2,
|
||||
);
|
||||
tl.to(
|
||||
S + ".left-cta",
|
||||
{
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
duration: 0.5,
|
||||
ease: "power2.out",
|
||||
},
|
||||
2.5,
|
||||
);
|
||||
|
||||
// Ambient slow scale push
|
||||
tl.to(
|
||||
S + "#s3Stage",
|
||||
{
|
||||
scale: 1.02,
|
||||
duration: 3.5,
|
||||
ease: "sine.inOut",
|
||||
},
|
||||
2.0,
|
||||
);
|
||||
|
||||
// ════════════════════════════════════
|
||||
// RESOLVE PHASE (4–5.5s)
|
||||
// ════════════════════════════════════
|
||||
|
||||
// Subtle glow pulse on lime accent elements
|
||||
tl.to(S + ".ring-fill", { opacity: 0.65, duration: 0.6, ease: "sine.inOut" }, 4.0);
|
||||
tl.to(S + ".ring-fill", { opacity: 1, duration: 0.6, ease: "sine.inOut" }, 4.6);
|
||||
|
||||
tl.to(S + ".bar-value", { opacity: 0.7, duration: 0.6, ease: "sine.inOut" }, 4.1);
|
||||
tl.to(S + ".bar-value", { opacity: 1, duration: 0.6, ease: "sine.inOut" }, 4.7);
|
||||
|
||||
tl.to(graphLine, { opacity: 0.65, duration: 0.6, ease: "sine.inOut" }, 4.05);
|
||||
tl.to(graphLine, { opacity: 1, duration: 0.6, ease: "sine.inOut" }, 4.65);
|
||||
|
||||
window.__timelines["app-showcase"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "app-showcase",
|
||||
"type": "hyperframes:block",
|
||||
"title": "App Showcase",
|
||||
"description": "Fitness app product showcase with three floating smartphone screens",
|
||||
"tags": ["showcase", "app", "3d"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 5.5,
|
||||
"files": [
|
||||
{
|
||||
"path": "app-showcase.html",
|
||||
"target": "compositions/app-showcase.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/app-showcase.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/app-showcase.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #fdfefe;
|
||||
}
|
||||
|
||||
#root {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #fdfefe;
|
||||
color: #111315;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
.green-flash {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
background: #30d158;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.stage-content {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120px 160px;
|
||||
}
|
||||
|
||||
.amount-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1080px;
|
||||
min-height: 260px;
|
||||
}
|
||||
|
||||
.amount {
|
||||
position: relative;
|
||||
color: #111315;
|
||||
font-size: 190px;
|
||||
font-weight: 900;
|
||||
line-height: 0.9;
|
||||
letter-spacing: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
text-shadow:
|
||||
0 3px 0 rgba(255, 255, 255, 0.58),
|
||||
0 18px 36px rgba(17, 19, 21, 0.14),
|
||||
0 42px 92px rgba(17, 19, 21, 0.1);
|
||||
will-change: transform, color, opacity, text-shadow;
|
||||
}
|
||||
|
||||
.money-burst {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 8;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.money-icon {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
opacity: 0;
|
||||
filter: drop-shadow(0 20px 24px rgba(7, 84, 31, 0.2));
|
||||
will-change: transform, opacity, filter;
|
||||
}
|
||||
|
||||
.money-icon.bill {
|
||||
width: 96px;
|
||||
height: 52px;
|
||||
border-radius: 10px;
|
||||
background: #30d158;
|
||||
box-shadow:
|
||||
inset 0 0 0 3px rgba(7, 84, 31, 0.2),
|
||||
inset 0 -10px 18px rgba(7, 84, 31, 0.1),
|
||||
0 16px 26px rgba(7, 84, 31, 0.2),
|
||||
0 34px 64px rgba(7, 84, 31, 0.16);
|
||||
}
|
||||
|
||||
.money-icon.bill::before {
|
||||
position: absolute;
|
||||
inset: 9px 16px;
|
||||
border: 2px solid rgba(7, 84, 31, 0.28);
|
||||
border-radius: 999px;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.money-icon.coin {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 34% 30%, #fff7a6 0%, #ffd54f 40%, #d9a514 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 4px rgba(111, 76, 0, 0.12),
|
||||
inset 0 -8px 14px rgba(111, 76, 0, 0.1),
|
||||
0 16px 30px rgba(111, 76, 0, 0.18),
|
||||
0 34px 58px rgba(111, 76, 0, 0.12);
|
||||
}
|
||||
|
||||
.money-icon .mark {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: #07541f;
|
||||
font-size: 30px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.money-icon.coin .mark {
|
||||
color: #6f4c00;
|
||||
font-size: 28px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="apple-money-count"
|
||||
data-root="true"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div id="greenFlash" class="green-flash"></div>
|
||||
<div class="stage-content">
|
||||
<div class="amount-wrap">
|
||||
<div id="amount" class="amount">$0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="moneyBurst" class="money-burst" data-layout-allow-overflow></div>
|
||||
<audio
|
||||
id="sfxTrack"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-track-index="9"
|
||||
src="assets/sfx-production.wav"
|
||||
data-volume="0.92"
|
||||
></audio>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const amount = document.querySelector("#amount");
|
||||
const moneyBurst = document.querySelector("#moneyBurst");
|
||||
const countState = { value: 0 };
|
||||
|
||||
function formatMoney(value) {
|
||||
const bounded = Math.max(0, Math.min(10000, value));
|
||||
const rounded = Math.round(bounded);
|
||||
return "$" + rounded.toLocaleString("en-US");
|
||||
}
|
||||
|
||||
function renderAmount(value) {
|
||||
amount.textContent = formatMoney(value);
|
||||
}
|
||||
|
||||
const moneyIcons = [];
|
||||
const moneySpecs = [];
|
||||
|
||||
for (let i = 0; i < 62; i += 1) {
|
||||
const icon = document.createElement("div");
|
||||
const isCoin = i % 3 === 0;
|
||||
icon.className = "money-icon " + (isCoin ? "coin" : "bill");
|
||||
icon.innerHTML = '<span class="mark">$</span>';
|
||||
moneyBurst.appendChild(icon);
|
||||
moneyIcons.push(icon);
|
||||
|
||||
const angle = i * 2.399963229728653;
|
||||
const ring = i % 5;
|
||||
const radiusX = 260 + ring * 145 + (i % 7) * 12;
|
||||
const radiusY = 160 + ring * 78 + (i % 6) * 12;
|
||||
const offsetX = (i % 4) * 24 - 36;
|
||||
const offsetY = (i % 5) * 18 - 36;
|
||||
const x = Math.max(-865, Math.min(865, Math.cos(angle) * radiusX + offsetX));
|
||||
const y = Math.max(-465, Math.min(465, Math.sin(angle * 1.13) * radiusY + offsetY));
|
||||
|
||||
moneySpecs.push({
|
||||
x,
|
||||
y,
|
||||
delay: (i % 8) * 0.025,
|
||||
duration: 0.74 + (i % 5) * 0.045,
|
||||
fadeDelay: (i % 5) * 0.05,
|
||||
rotation: ((i * 43) % 160) - 80,
|
||||
scale: isCoin ? 0.72 + (i % 4) * 0.08 : 0.68 + (i % 5) * 0.07,
|
||||
});
|
||||
}
|
||||
|
||||
renderAmount(0);
|
||||
|
||||
const tl = gsap.timeline({
|
||||
paused: true,
|
||||
defaults: { ease: "power3.out" },
|
||||
});
|
||||
|
||||
gsap.set(moneyIcons, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
xPercent: -50,
|
||||
yPercent: -50,
|
||||
opacity: 0,
|
||||
scale: 0.18,
|
||||
transformOrigin: "50% 50%",
|
||||
});
|
||||
|
||||
tl.from(
|
||||
"#amount",
|
||||
{
|
||||
y: 26,
|
||||
opacity: 0,
|
||||
scale: 0.985,
|
||||
duration: 0.45,
|
||||
ease: "power2.out",
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
tl.to(
|
||||
countState,
|
||||
{
|
||||
value: 10000,
|
||||
duration: 3.16,
|
||||
ease: "none",
|
||||
onUpdate: () => renderAmount(countState.value),
|
||||
onComplete: () => renderAmount(10000),
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
tl.to(
|
||||
"#amount",
|
||||
{
|
||||
color: "#30d158",
|
||||
textShadow:
|
||||
"0 3px 0 rgba(255,255,255,0.52), 0 18px 40px rgba(48,209,88,0.3), 0 46px 96px rgba(7,84,31,0.2)",
|
||||
duration: 0.18,
|
||||
},
|
||||
3.16,
|
||||
);
|
||||
tl.to("#amount", { scale: 1.06, duration: 0.16, ease: "back.out(2.2)" }, 3.16);
|
||||
tl.to("#amount", { scale: 1, duration: 0.23, ease: "power2.out" }, 3.33);
|
||||
|
||||
tl.to("#greenFlash", { opacity: 0.34, duration: 0.08, ease: "none" }, 3.16);
|
||||
tl.to("#greenFlash", { opacity: 0.22, duration: 0.76, ease: "none" }, 3.24);
|
||||
tl.to("#greenFlash", { opacity: 0, duration: 0.16, ease: "power2.out" }, 4);
|
||||
|
||||
moneyIcons.forEach((icon, i) => {
|
||||
const spec = moneySpecs[i];
|
||||
tl.to(
|
||||
icon,
|
||||
{
|
||||
x: spec.x,
|
||||
y: spec.y,
|
||||
rotation: spec.rotation,
|
||||
scale: spec.scale,
|
||||
opacity: 1,
|
||||
duration: spec.duration,
|
||||
ease: "power4.out",
|
||||
},
|
||||
3.28 + spec.delay,
|
||||
);
|
||||
tl.to(
|
||||
icon,
|
||||
{
|
||||
y: spec.y + (spec.y < 0 ? -58 : 58),
|
||||
scale: spec.scale * 0.54,
|
||||
opacity: 0,
|
||||
duration: 0.38,
|
||||
ease: "power2.in",
|
||||
},
|
||||
4.18 + spec.fadeDelay,
|
||||
);
|
||||
});
|
||||
|
||||
tl.to("#amount", { opacity: 0, scale: 0.985, duration: 0.28, ease: "power2.in" }, 4.36);
|
||||
|
||||
window.__timelines["apple-money-count"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "apple-money-count",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Apple Money Count",
|
||||
"description": "Apple-style finance counter that counts from $0 to $10,000, flashes green, and bursts money icons with sound.",
|
||||
"tags": ["showcase", "finance", "kinetic", "youtube", "sfx"],
|
||||
"author": "Stronkter",
|
||||
"authorUrl": "https://x.com/Stronkter",
|
||||
"sourcePrompt": "📷HyperFrames by HeyGen Make me a five-second video of, on a white background, of a Apple-style bold font counting from $0 to $10,000. Once it counts to $10,000, it changes to a green color and the screen also flashes green for a second, and then money icons come out of the $10,000 amount all over the screen and then disappear.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 5,
|
||||
"files": [
|
||||
{
|
||||
"path": "apple-money-count.html",
|
||||
"target": "compositions/apple-money-count.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "assets/sfx-production.wav",
|
||||
"target": "assets/sfx-production.wav",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/apple-money-count.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/apple-money-count.png"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.9 MiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "blue-sweater-intro-video",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Blue Sweater Intro Video",
|
||||
"description": "Warm AI creator intro sequence that resolves into an X follow card for @_blue_sweater_.",
|
||||
"tags": ["showcase", "ai", "creator", "sfx"],
|
||||
"author": "Joe Sai",
|
||||
"authorUrl": "https://x.com/_blue_sweater_",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "blue-sweater-intro-video.html",
|
||||
"target": "compositions/blue-sweater-intro-video.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "assets/joe-sai-avatar.png",
|
||||
"target": "assets/joe-sai-avatar.png",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "assets/sfx/integrated-melodic-tech-mix.wav",
|
||||
"target": "assets/sfx/integrated-melodic-tech-mix.wav",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/blue-sweater-intro-video.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/blue-sweater-intro-video.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Bebas+Neue&family=JetBrains+Mono:wght@400;700&family=Lato:wght@400;700&display=block");
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="main"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<style>
|
||||
[data-composition-id="main"] {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #22223b;
|
||||
}
|
||||
|
||||
/* Blueprint grid */
|
||||
.bp-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.bp-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.bp-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 60px 60px 60px 40px;
|
||||
}
|
||||
.bp-divider {
|
||||
position: absolute;
|
||||
left: 1200px;
|
||||
top: 60px;
|
||||
bottom: 60px;
|
||||
width: 1px;
|
||||
background: #7678ed;
|
||||
opacity: 0.15;
|
||||
}
|
||||
.bp-number {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4em;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 60px;
|
||||
color: #7678ed;
|
||||
}
|
||||
|
||||
/* Right panel typography */
|
||||
.bp-name {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 38px;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 20px;
|
||||
color: #7678ed;
|
||||
}
|
||||
.bp-plabel {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
color: #7678ed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bp-prompt {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
padding: 16px 20px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
color: #7678ed;
|
||||
background: rgba(118, 120, 237, 0.06);
|
||||
border: 1px solid rgba(118, 120, 237, 0.12);
|
||||
}
|
||||
.bp-desc {
|
||||
font-family: "Lato", sans-serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
color: #9a8c98;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* GL scenes (hidden, just for texture capture) */
|
||||
.scene {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.scene-label {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 140px;
|
||||
letter-spacing: 0.06em;
|
||||
opacity: 0.12;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bp-grid"></div>
|
||||
<div class="bp-divider"></div>
|
||||
<div class="bp-number">08 / 14</div>
|
||||
|
||||
<!-- GL demo scenes (captured as textures, then hidden) -->
|
||||
<div id="s1" class="scene" style="background: #22223b">
|
||||
<div class="scene-label" style="color: #7678ed">SCENE A</div>
|
||||
</div>
|
||||
<div id="s2" class="scene" style="background: #7678ed; opacity: 0">
|
||||
<div class="scene-label" style="color: #22223b">SCENE B</div>
|
||||
</div>
|
||||
|
||||
<!-- GL canvas (left panel only) -->
|
||||
<canvas
|
||||
id="gl-canvas"
|
||||
width="1200"
|
||||
height="1080"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
"
|
||||
>
|
||||
</canvas>
|
||||
|
||||
<!-- Right panel metadata -->
|
||||
<div class="bp-right">
|
||||
<div class="bp-name">Chromatic Radial Split</div>
|
||||
<div class="bp-plabel">Prompt</div>
|
||||
<div class="bp-prompt">"use chromatic radial split shader transition"</div>
|
||||
<div class="bp-desc">
|
||||
RGB channels separate and converge radially. From-scene splits outward, to-scene converges
|
||||
inward.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="driver"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
var glCanvas = document.getElementById("gl-canvas");
|
||||
var gl = glCanvas.getContext("webgl", { preserveDrawingBuffer: true });
|
||||
if (!gl) {
|
||||
console.warn("WebGL not available");
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
} else {
|
||||
gl.viewport(0, 0, 1200, 1080);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
var sceneTextures = {};
|
||||
|
||||
function captureScene(sceneId) {
|
||||
var scene = document.getElementById(sceneId);
|
||||
var origOpacity = scene.style.opacity;
|
||||
var origZ = scene.style.zIndex;
|
||||
scene.style.opacity = "1";
|
||||
scene.style.zIndex = "999";
|
||||
scene.offsetHeight;
|
||||
var c = document.createElement("canvas");
|
||||
c.width = 1200;
|
||||
c.height = 1080;
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.fillStyle = window.getComputedStyle(scene).backgroundColor;
|
||||
ctx.fillRect(0, 0, 1200, 1080);
|
||||
var sr = scene.getBoundingClientRect();
|
||||
var els = scene.querySelectorAll("*");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i],
|
||||
cs = window.getComputedStyle(el);
|
||||
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
||||
var r = el.getBoundingClientRect();
|
||||
if (r.width < 1 || r.height < 1) continue;
|
||||
var x = r.left - sr.left,
|
||||
y = r.top - sr.top,
|
||||
w = r.width,
|
||||
h = r.height;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = parseFloat(cs.opacity) || 1;
|
||||
var bg = cs.backgroundColor;
|
||||
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(x, y, w, h);
|
||||
}
|
||||
var hasChildEls = el.querySelector("div,span,img,video");
|
||||
var text = "";
|
||||
for (var j = 0; j < el.childNodes.length; j++)
|
||||
if (el.childNodes[j].nodeType === 3) text += el.childNodes[j].textContent;
|
||||
text = text.trim();
|
||||
if (text && !hasChildEls) {
|
||||
ctx.font = cs.fontWeight + " " + cs.fontSize + " " + cs.fontFamily;
|
||||
ctx.fillStyle = cs.color;
|
||||
if (cs.letterSpacing && cs.letterSpacing !== "normal")
|
||||
ctx.letterSpacing = cs.letterSpacing;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(text, x + w / 2, y + h / 2);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
scene.style.opacity = origOpacity;
|
||||
scene.style.zIndex = origZ;
|
||||
var tex = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
||||
sceneTextures[sceneId] = tex;
|
||||
}
|
||||
|
||||
var vertSrc =
|
||||
"attribute vec2 a_pos; varying vec2 v_uv; void main(){" +
|
||||
"v_uv=a_pos*0.5+0.5; v_uv.y=1.0-v_uv.y; gl_Position=vec4(a_pos,0,1);}";
|
||||
var quadBuf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
||||
gl.STATIC_DRAW,
|
||||
);
|
||||
function compileShader(src, type) {
|
||||
var s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
|
||||
console.error("Shader:", gl.getShaderInfoLog(s));
|
||||
return s;
|
||||
}
|
||||
function mkProg(fragSrc) {
|
||||
var p = gl.createProgram();
|
||||
gl.attachShader(p, compileShader(vertSrc, gl.VERTEX_SHADER));
|
||||
gl.attachShader(p, compileShader(fragSrc, gl.FRAGMENT_SHADER));
|
||||
gl.linkProgram(p);
|
||||
if (!gl.getProgramParameter(p, gl.LINK_STATUS))
|
||||
console.error("Link:", gl.getProgramInfoLog(p));
|
||||
return p;
|
||||
}
|
||||
|
||||
var H =
|
||||
"precision mediump float;" +
|
||||
"varying vec2 v_uv;" +
|
||||
"uniform sampler2D u_from, u_to;" +
|
||||
"uniform float u_progress;" +
|
||||
"uniform vec2 u_resolution;\n";
|
||||
|
||||
var progPass = mkProg(H + "void main(){gl_FragColor=texture2D(u_from,v_uv);}");
|
||||
var progTrans = mkProg(
|
||||
H +
|
||||
"void main(){vec2 c=v_uv-.5;float fromShift=u_progress*.06;float fr=texture2D(u_from,clamp(v_uv+c*fromShift,0.,1.)).r;float fg=texture2D(u_from,v_uv).g;float fb=texture2D(u_from,clamp(v_uv-c*fromShift,0.,1.)).b;vec3 fromSplit=vec3(fr,fg,fb);float toShift=(1.-u_progress)*.06;float tr=texture2D(u_to,clamp(v_uv-c*toShift,0.,1.)).r;float tg=texture2D(u_to,v_uv).g;float tb=texture2D(u_to,clamp(v_uv+c*toShift,0.,1.)).b;vec3 toSplit=vec3(tr,tg,tb);gl_FragColor=vec4(mix(fromSplit,toSplit,u_progress),1.);}",
|
||||
);
|
||||
|
||||
function renderShader(prog, texFrom, texTo, progress) {
|
||||
gl.useProgram(prog);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texFrom);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_from"), 0);
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texTo);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_to"), 1);
|
||||
gl.uniform1f(gl.getUniformLocation(prog, "u_progress"), progress);
|
||||
gl.uniform2f(gl.getUniformLocation(prog, "u_resolution"), 1200, 1080);
|
||||
var pos = gl.getAttribLocation(prog, "a_pos");
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.enableVertexAttribArray(pos);
|
||||
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
function easeInOut(p) {
|
||||
return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
|
||||
}
|
||||
|
||||
captureScene("s1");
|
||||
captureScene("s2");
|
||||
glCanvas.style.display = "block";
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
document.querySelectorAll(".scene").forEach(function (s) {
|
||||
s.style.opacity = "0";
|
||||
});
|
||||
|
||||
var tl = gsap.timeline({
|
||||
paused: true,
|
||||
onUpdate: function () {
|
||||
var t = tl.time();
|
||||
if (t >= 1.0 && t < 3.0) {
|
||||
renderShader(
|
||||
progTrans,
|
||||
sceneTextures["s1"],
|
||||
sceneTextures["s2"],
|
||||
easeInOut((t - 1.0) / 2.0),
|
||||
);
|
||||
} else if (t >= 3.0) {
|
||||
renderShader(progPass, sceneTextures["s2"], sceneTextures["s2"], 0);
|
||||
} else {
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
tl.to({ v: 0 }, { v: 1, duration: 4, ease: "none" }, 0);
|
||||
window.__timelines["main"] = tl;
|
||||
} // end if (gl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "chromatic-radial-split",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Chromatic Radial Split",
|
||||
"description": "Shader transition with chromatic aberration radial split",
|
||||
"tags": ["transition", "shader"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 4,
|
||||
"files": [
|
||||
{
|
||||
"path": "chromatic-radial-split.html",
|
||||
"target": "compositions/chromatic-radial-split.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/chromatic-radial-split.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/chromatic-radial-split.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Bebas+Neue&family=JetBrains+Mono:wght@400;700&family=Lato:wght@400;700&display=block");
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="main"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<style>
|
||||
[data-composition-id="main"] {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #3d348b;
|
||||
}
|
||||
|
||||
/* Blueprint grid */
|
||||
.bp-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.bp-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.bp-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 60px 60px 60px 40px;
|
||||
}
|
||||
.bp-divider {
|
||||
position: absolute;
|
||||
left: 1200px;
|
||||
top: 60px;
|
||||
bottom: 60px;
|
||||
width: 1px;
|
||||
background: #f7b801;
|
||||
opacity: 0.15;
|
||||
}
|
||||
.bp-number {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4em;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 60px;
|
||||
color: #f7b801;
|
||||
}
|
||||
|
||||
/* Right panel typography */
|
||||
.bp-name {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 38px;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 20px;
|
||||
color: #f7b801;
|
||||
}
|
||||
.bp-plabel {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
color: #f7b801;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bp-prompt {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
padding: 16px 20px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
color: #f7b801;
|
||||
background: rgba(247, 184, 1, 0.06);
|
||||
border: 1px solid rgba(247, 184, 1, 0.12);
|
||||
}
|
||||
.bp-desc {
|
||||
font-family: "Lato", sans-serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
color: #a0a0c0;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* GL scenes (hidden, just for texture capture) */
|
||||
.scene {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.scene-label {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 140px;
|
||||
letter-spacing: 0.06em;
|
||||
opacity: 0.12;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bp-grid"></div>
|
||||
<div class="bp-divider"></div>
|
||||
<div class="bp-number">07 / 14</div>
|
||||
|
||||
<!-- GL demo scenes (captured as textures, then hidden) -->
|
||||
<div id="s1" class="scene" style="background: #3d348b">
|
||||
<div class="scene-label" style="color: #f7b801">SCENE A</div>
|
||||
</div>
|
||||
<div id="s2" class="scene" style="background: #f7b801; opacity: 0">
|
||||
<div class="scene-label" style="color: #3d348b">SCENE B</div>
|
||||
</div>
|
||||
|
||||
<!-- GL canvas (left panel only) -->
|
||||
<canvas
|
||||
id="gl-canvas"
|
||||
width="1200"
|
||||
height="1080"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
"
|
||||
>
|
||||
</canvas>
|
||||
|
||||
<!-- Right panel metadata -->
|
||||
<div class="bp-right">
|
||||
<div class="bp-name">Cinematic Zoom</div>
|
||||
<div class="bp-plabel">Prompt</div>
|
||||
<div class="bp-prompt">"use cinematic zoom shader transition"</div>
|
||||
<div class="bp-desc">
|
||||
Both scenes zoom-blur with per-channel radial offset. From zooms outward, to zooms inward
|
||||
from tight.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="driver"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
var glCanvas = document.getElementById("gl-canvas");
|
||||
var gl = glCanvas.getContext("webgl", { preserveDrawingBuffer: true });
|
||||
if (!gl) {
|
||||
console.warn("WebGL not available");
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
} else {
|
||||
gl.viewport(0, 0, 1200, 1080);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
var sceneTextures = {};
|
||||
|
||||
function captureScene(sceneId) {
|
||||
var scene = document.getElementById(sceneId);
|
||||
var origOpacity = scene.style.opacity;
|
||||
var origZ = scene.style.zIndex;
|
||||
scene.style.opacity = "1";
|
||||
scene.style.zIndex = "999";
|
||||
scene.offsetHeight;
|
||||
var c = document.createElement("canvas");
|
||||
c.width = 1200;
|
||||
c.height = 1080;
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.fillStyle = window.getComputedStyle(scene).backgroundColor;
|
||||
ctx.fillRect(0, 0, 1200, 1080);
|
||||
var sr = scene.getBoundingClientRect();
|
||||
var els = scene.querySelectorAll("*");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i],
|
||||
cs = window.getComputedStyle(el);
|
||||
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
||||
var r = el.getBoundingClientRect();
|
||||
if (r.width < 1 || r.height < 1) continue;
|
||||
var x = r.left - sr.left,
|
||||
y = r.top - sr.top,
|
||||
w = r.width,
|
||||
h = r.height;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = parseFloat(cs.opacity) || 1;
|
||||
var bg = cs.backgroundColor;
|
||||
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(x, y, w, h);
|
||||
}
|
||||
var hasChildEls = el.querySelector("div,span,img,video");
|
||||
var text = "";
|
||||
for (var j = 0; j < el.childNodes.length; j++)
|
||||
if (el.childNodes[j].nodeType === 3) text += el.childNodes[j].textContent;
|
||||
text = text.trim();
|
||||
if (text && !hasChildEls) {
|
||||
ctx.font = cs.fontWeight + " " + cs.fontSize + " " + cs.fontFamily;
|
||||
ctx.fillStyle = cs.color;
|
||||
if (cs.letterSpacing && cs.letterSpacing !== "normal")
|
||||
ctx.letterSpacing = cs.letterSpacing;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(text, x + w / 2, y + h / 2);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
scene.style.opacity = origOpacity;
|
||||
scene.style.zIndex = origZ;
|
||||
var tex = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
||||
sceneTextures[sceneId] = tex;
|
||||
}
|
||||
|
||||
var vertSrc =
|
||||
"attribute vec2 a_pos; varying vec2 v_uv; void main(){" +
|
||||
"v_uv=a_pos*0.5+0.5; v_uv.y=1.0-v_uv.y; gl_Position=vec4(a_pos,0,1);}";
|
||||
var quadBuf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
||||
gl.STATIC_DRAW,
|
||||
);
|
||||
function compileShader(src, type) {
|
||||
var s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
|
||||
console.error("Shader:", gl.getShaderInfoLog(s));
|
||||
return s;
|
||||
}
|
||||
function mkProg(fragSrc) {
|
||||
var p = gl.createProgram();
|
||||
gl.attachShader(p, compileShader(vertSrc, gl.VERTEX_SHADER));
|
||||
gl.attachShader(p, compileShader(fragSrc, gl.FRAGMENT_SHADER));
|
||||
gl.linkProgram(p);
|
||||
if (!gl.getProgramParameter(p, gl.LINK_STATUS))
|
||||
console.error("Link:", gl.getProgramInfoLog(p));
|
||||
return p;
|
||||
}
|
||||
|
||||
var H =
|
||||
"precision mediump float;" +
|
||||
"varying vec2 v_uv;" +
|
||||
"uniform sampler2D u_from, u_to;" +
|
||||
"uniform float u_progress;" +
|
||||
"uniform vec2 u_resolution;\n";
|
||||
|
||||
var progPass = mkProg(H + "void main(){gl_FragColor=texture2D(u_from,v_uv);}");
|
||||
var progTrans = mkProg(
|
||||
H +
|
||||
"void main(){vec2 d=v_uv-vec2(.5);float fromS=u_progress*.08;float toS=(1.-u_progress)*.06;float fr=0.,fg=0.,fb=0.;for(int i=0;i<12;i++){float f=float(i)/12.;fr+=texture2D(u_from,v_uv-d*(fromS*1.06)*f).r;fg+=texture2D(u_from,v_uv-d*fromS*f).g;fb+=texture2D(u_from,v_uv-d*(fromS*.94)*f).b;}vec3 fromBl=vec3(fr,fg,fb)/12.;float tr=0.,tg=0.,tb=0.;for(int i=0;i<12;i++){float f=float(i)/12.;tr+=texture2D(u_to,v_uv+d*(toS*1.06)*f).r;tg+=texture2D(u_to,v_uv+d*toS*f).g;tb+=texture2D(u_to,v_uv+d*(toS*.94)*f).b;}vec3 toBl=vec3(tr,tg,tb)/12.;gl_FragColor=vec4(mix(fromBl,toBl,u_progress),1.);}",
|
||||
);
|
||||
|
||||
function renderShader(prog, texFrom, texTo, progress) {
|
||||
gl.useProgram(prog);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texFrom);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_from"), 0);
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texTo);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_to"), 1);
|
||||
gl.uniform1f(gl.getUniformLocation(prog, "u_progress"), progress);
|
||||
gl.uniform2f(gl.getUniformLocation(prog, "u_resolution"), 1200, 1080);
|
||||
var pos = gl.getAttribLocation(prog, "a_pos");
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.enableVertexAttribArray(pos);
|
||||
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
function easeInOut(p) {
|
||||
return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
|
||||
}
|
||||
|
||||
captureScene("s1");
|
||||
captureScene("s2");
|
||||
glCanvas.style.display = "block";
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
document.querySelectorAll(".scene").forEach(function (s) {
|
||||
s.style.opacity = "0";
|
||||
});
|
||||
|
||||
var tl = gsap.timeline({
|
||||
paused: true,
|
||||
onUpdate: function () {
|
||||
var t = tl.time();
|
||||
if (t >= 1.0 && t < 3.0) {
|
||||
renderShader(
|
||||
progTrans,
|
||||
sceneTextures["s1"],
|
||||
sceneTextures["s2"],
|
||||
easeInOut((t - 1.0) / 2.0),
|
||||
);
|
||||
} else if (t >= 3.0) {
|
||||
renderShader(progPass, sceneTextures["s2"], sceneTextures["s2"], 0);
|
||||
} else {
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
tl.to({ v: 0 }, { v: 1, duration: 4, ease: "none" }, 0);
|
||||
window.__timelines["main"] = tl;
|
||||
} // end if (gl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "cinematic-zoom",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Cinematic Zoom",
|
||||
"description": "Shader transition with dramatic zoom blur",
|
||||
"tags": ["transition", "shader"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 4,
|
||||
"files": [
|
||||
{
|
||||
"path": "cinematic-zoom.html",
|
||||
"target": "compositions/cinematic-zoom.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/cinematic-zoom.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/cinematic-zoom.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Code 3D Extrude</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #05070b;
|
||||
}
|
||||
#root {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
}
|
||||
#gl {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="code-3d-extrude"
|
||||
data-start="0"
|
||||
data-duration="8"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<canvas id="gl" width="1920" height="1080"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
window.__TOKENS = {
|
||||
feature: {
|
||||
lang: "js",
|
||||
theme: "github-dark",
|
||||
bg: "#24292e",
|
||||
fg: "#e1e4e8",
|
||||
states: [
|
||||
{
|
||||
code: 'async function loadConfig(path) {\n const raw = await readFile(path, "utf8")\n const config = JSON.parse(raw)\n return validate(config)\n}',
|
||||
tokens: [
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-0",
|
||||
content: "async",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-2",
|
||||
content: "function",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-3",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-4",
|
||||
content: "loadConfig",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-5",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-6",
|
||||
content: "path",
|
||||
color: "#FFAB70",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-7",
|
||||
content: ") {",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-8",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-9",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-10",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-11",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-12",
|
||||
content: "raw",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-13",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-14",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-15",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-16",
|
||||
content: "await",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-17",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-18",
|
||||
content: "readFile",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-19",
|
||||
content: "(path,",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-20",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-21",
|
||||
content: '"utf8"',
|
||||
color: "#9ECBFF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-22",
|
||||
content: ")",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-23",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-24",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-25",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-26",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-27",
|
||||
content: "config",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-28",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-29",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-30",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-31",
|
||||
content: "JSON",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-32",
|
||||
content: ".",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-33",
|
||||
content: "parse",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-34",
|
||||
content: "(raw)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-35",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-36",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-37",
|
||||
content: "return",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-38",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-39",
|
||||
content: "validate",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-40",
|
||||
content: "(config)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-41",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-42",
|
||||
content: "}",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-43",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
window.__BLOCK = {
|
||||
id: "code-3d-extrude",
|
||||
effect: "extrude",
|
||||
seq: "feature",
|
||||
duration: 8,
|
||||
seed: 7,
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
// hf-code-anim-gl.js — WebGL code-animation engine for the "crazy" Code Animations blocks.
|
||||
//
|
||||
// Bridges the baked Shiki tokens (window.__TOKENS) into Three.js: the tokens are drawn once
|
||||
// to an offscreen 2D canvas (a CanvasTexture), which then drives a lit 3D slab, a dissolve
|
||||
// shader, or a GPU particle system. Determinism per the project contract:
|
||||
// - render only via tl.eventCallback("onUpdate", …) + one initial render() — never rAF
|
||||
// - all randomness from a seeded mulberry32 — never Math.random / Date.now
|
||||
// - canvas drawn once after document.fonts.ready (final glyph metrics)
|
||||
// - paused GSAP timeline registered on window.__timelines[id]; HF seeks it per frame
|
||||
//
|
||||
// Each block sets window.__BLOCK = { id, effect, seq, duration, seed } and includes this engine.
|
||||
(function () {
|
||||
"use strict";
|
||||
var T = window.__TOKENS || {};
|
||||
|
||||
// ---- seeded PRNG (no Math.random) -----------------------------------------
|
||||
function mulberry32(a) {
|
||||
return function () {
|
||||
a |= 0;
|
||||
a = (a + 0x6d2b79f5) | 0;
|
||||
var t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
// ---- split a token state into lines of {content,color} --------------------
|
||||
function splitLines(state) {
|
||||
var lines = [[]];
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
lines.push([]);
|
||||
continue;
|
||||
}
|
||||
lines[lines.length - 1].push(t);
|
||||
}
|
||||
while (lines.length && lines[lines.length - 1].length === 0) lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
// ---- THE BRIDGE: draw baked tokens to an offscreen 2D canvas ---------------
|
||||
// Returns { canvas, W, H, lines, lineH, padY, fontPx } so effects can sample it.
|
||||
function drawCodeToCanvas(state, opts) {
|
||||
opts = opts || {};
|
||||
var fontPx = opts.fontPx || 36;
|
||||
var lineH = opts.lineH || 54;
|
||||
var padX = opts.padX || 70;
|
||||
var padY = opts.padY || 60;
|
||||
var weight = opts.weight || 500;
|
||||
var font = weight + " " + fontPx + 'px "JetBrains Mono", monospace';
|
||||
|
||||
var lines = splitLines(state);
|
||||
var measure = document.createElement("canvas").getContext("2d");
|
||||
measure.font = font;
|
||||
var maxW = 0;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var w = 0;
|
||||
for (var j = 0; j < lines[i].length; j++)
|
||||
w += measure.measureText(lines[i][j].content).width;
|
||||
if (w > maxW) maxW = w;
|
||||
}
|
||||
var W = Math.max(2, Math.ceil(maxW + padX * 2));
|
||||
var H = Math.max(2, Math.ceil(lines.length * lineH + padY * 2));
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
var ctx = canvas.getContext("2d");
|
||||
if (opts.transparent) {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
} else {
|
||||
ctx.fillStyle = opts.bg || "#0b0f17";
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
}
|
||||
ctx.textBaseline = "top";
|
||||
ctx.font = font;
|
||||
for (var li = 0; li < lines.length; li++) {
|
||||
var x = padX;
|
||||
var y = padY + li * lineH;
|
||||
for (var k = 0; k < lines[li].length; k++) {
|
||||
var tok = lines[li][k];
|
||||
ctx.fillStyle = tok.color;
|
||||
ctx.fillText(tok.content, x, y);
|
||||
x += ctx.measureText(tok.content).width;
|
||||
}
|
||||
}
|
||||
return {
|
||||
canvas: canvas,
|
||||
W: W,
|
||||
H: H,
|
||||
lines: lines.length,
|
||||
lineH: lineH,
|
||||
padY: padY,
|
||||
fontPx: fontPx,
|
||||
};
|
||||
}
|
||||
|
||||
function fxExtrude(THREE, scene, camera, renderer, state, st, tl, dur) {
|
||||
scene.background = new THREE.Color(0x05070b);
|
||||
var code = drawCodeToCanvas(state, {
|
||||
fontPx: 38,
|
||||
lineH: 56,
|
||||
padX: 80,
|
||||
padY: 70,
|
||||
weight: 600,
|
||||
});
|
||||
var tex = new THREE.CanvasTexture(code.canvas);
|
||||
tex.minFilter = THREE.LinearFilter;
|
||||
tex.magFilter = THREE.LinearFilter;
|
||||
tex.generateMipmaps = false;
|
||||
|
||||
var aspect = code.W / code.H;
|
||||
var slabH = 6.2;
|
||||
var slabW = slabH * aspect;
|
||||
var depth = 0.5;
|
||||
|
||||
// beveled slab: rounded extruded rectangle
|
||||
var hw = slabW / 2;
|
||||
var hh = slabH / 2;
|
||||
var r = 0.18;
|
||||
var shape = new THREE.Shape();
|
||||
shape.moveTo(-hw + r, -hh);
|
||||
shape.lineTo(hw - r, -hh);
|
||||
shape.quadraticCurveTo(hw, -hh, hw, -hh + r);
|
||||
shape.lineTo(hw, hh - r);
|
||||
shape.quadraticCurveTo(hw, hh, hw - r, hh);
|
||||
shape.lineTo(-hw + r, hh);
|
||||
shape.quadraticCurveTo(-hw, hh, -hw, hh - r);
|
||||
shape.lineTo(-hw, -hh + r);
|
||||
shape.quadraticCurveTo(-hw, -hh, -hw + r, -hh);
|
||||
var geo = new THREE.ExtrudeGeometry(shape, {
|
||||
depth: depth,
|
||||
bevelEnabled: true,
|
||||
bevelThickness: 0.12,
|
||||
bevelSize: 0.1,
|
||||
bevelSegments: 3,
|
||||
steps: 1,
|
||||
});
|
||||
geo.center();
|
||||
// UVs from ExtrudeGeometry don't map the texture nicely to the face; remap front-face UVs from XY bounds.
|
||||
geo.computeBoundingBox();
|
||||
var bb = geo.boundingBox;
|
||||
var sizeX = bb.max.x - bb.min.x;
|
||||
var sizeY = bb.max.y - bb.min.y;
|
||||
var pos = geo.attributes.position;
|
||||
var uv = geo.attributes.uv;
|
||||
for (var vi = 0; vi < pos.count; vi++) {
|
||||
uv.setXY(vi, (pos.getX(vi) - bb.min.x) / sizeX, (pos.getY(vi) - bb.min.y) / sizeY);
|
||||
}
|
||||
uv.needsUpdate = true;
|
||||
|
||||
// Face is an unlit "screen" — shows the exact vivid Shiki colors (no PBR/emissive wash).
|
||||
// The 3D read comes from the lit, beveled EDGES + perspective, like a glowing monitor.
|
||||
var faceMat = new THREE.MeshBasicMaterial({ map: tex });
|
||||
var edgeMat = new THREE.MeshStandardMaterial({
|
||||
color: 0x141d2b,
|
||||
roughness: 0.3,
|
||||
metalness: 0.4,
|
||||
});
|
||||
// ExtrudeGeometry groups: 0 = front+back faces, 1 = sides. Front shows the code.
|
||||
var slab = new THREE.Mesh(geo, [faceMat, edgeMat]);
|
||||
scene.add(slab);
|
||||
|
||||
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
|
||||
var key = new THREE.DirectionalLight(0xbcd4ff, 1.25);
|
||||
key.position.set(5, 7, 9);
|
||||
scene.add(key);
|
||||
var rim = new THREE.DirectionalLight(0x4fffa0, 0.45);
|
||||
rim.position.set(-7, -3, 5);
|
||||
scene.add(rim);
|
||||
|
||||
camera.position.set(0, 0, 10);
|
||||
|
||||
// fit camera distance so the WHOLE slab fits (width-limited for wide code), ~78% fill
|
||||
var vfov = (camera.fov * Math.PI) / 180;
|
||||
var screenAspect = 1920 / 1080;
|
||||
var fitH = slabH / 2 / Math.tan(vfov / 2) / 0.78;
|
||||
var fitW = slabW / 2 / (Math.tan(vfov / 2) * screenAspect) / 0.78;
|
||||
var fitZ = Math.max(fitH, fitW);
|
||||
|
||||
st.rotY = -1.0;
|
||||
st.rotX = 0.4;
|
||||
st.camZ = fitZ + 6;
|
||||
tl.to(st, { rotY: 0, rotX: 0, camZ: fitZ, duration: dur * 0.6, ease: "power3.out" }, 0);
|
||||
// gentle breathing drift at rest (subtle — stays readable)
|
||||
tl.to(
|
||||
st,
|
||||
{ rotY: 0.06, rotX: -0.02, duration: dur * 0.4, ease: "sine.inOut" },
|
||||
dur * 0.6,
|
||||
);
|
||||
|
||||
return function () {
|
||||
slab.rotation.y = st.rotY;
|
||||
slab.rotation.x = st.rotX;
|
||||
camera.position.z = st.camZ;
|
||||
camera.lookAt(0, 0, 0);
|
||||
// boot loop's render() issues the single renderer.render() — don't double-render here
|
||||
};
|
||||
}
|
||||
|
||||
// ---- dispatch + boot ------------------------------------------------------
|
||||
function buildEffect(THREE, scene, camera, renderer, spec, st, tl) {
|
||||
var state = T[spec.seq].states[0];
|
||||
var dur = spec.duration;
|
||||
if (spec.effect === "extrude")
|
||||
return fxExtrude(THREE, scene, camera, renderer, state, st, tl, dur);
|
||||
return function () {};
|
||||
}
|
||||
|
||||
// Free GPU resources (renderer, geometries, materials, textures) on page teardown.
|
||||
// One-shot renders exit per job so nothing leaks there; this guards long-lived
|
||||
// Studio/player sessions that reload. Fires only on unload — never during capture.
|
||||
function disposeGL(renderer, scene) {
|
||||
scene.traverse(function (o) {
|
||||
if (o.geometry && o.geometry.dispose) o.geometry.dispose();
|
||||
var mats = o.material ? (Array.isArray(o.material) ? o.material : [o.material]) : [];
|
||||
mats.forEach(function (m) {
|
||||
if (m.map && m.map.dispose) m.map.dispose();
|
||||
if (
|
||||
m.uniforms &&
|
||||
m.uniforms.uTex &&
|
||||
m.uniforms.uTex.value &&
|
||||
m.uniforms.uTex.value.dispose
|
||||
) {
|
||||
m.uniforms.uTex.value.dispose();
|
||||
}
|
||||
if (m.dispose) m.dispose();
|
||||
});
|
||||
});
|
||||
if (renderer.dispose) renderer.dispose();
|
||||
}
|
||||
|
||||
function go() {
|
||||
var spec = window.__BLOCK;
|
||||
var root = document.getElementById("root");
|
||||
var glcanvas = document.getElementById("gl");
|
||||
var W = 1920;
|
||||
var H = 1080;
|
||||
window.__timelines = window.__timelines || {};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.__timelines[spec.id] = tl; // register synchronously
|
||||
|
||||
var THREE = window.THREE;
|
||||
var renderer = new THREE.WebGLRenderer({
|
||||
canvas: glcanvas,
|
||||
antialias: true,
|
||||
alpha: false,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
renderer.setPixelRatio(1);
|
||||
renderer.setSize(W, H, false);
|
||||
var scene = new THREE.Scene();
|
||||
var camera = new THREE.PerspectiveCamera(45, W / H, 0.1, 200);
|
||||
var st = {};
|
||||
|
||||
document.fonts.ready.then(function () {
|
||||
var renderFn = buildEffect(THREE, scene, camera, renderer, spec, st, tl);
|
||||
function render() {
|
||||
renderFn();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
tl.eventCallback("onUpdate", render);
|
||||
var dur = parseFloat(root.dataset.duration) || tl.duration();
|
||||
tl.to({}, { duration: dur }, 0); // pad to full composition length
|
||||
render(); // paint frame 0
|
||||
window.addEventListener("beforeunload", function () {
|
||||
disposeGL(renderer, scene);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go);
|
||||
else go();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-3d-extrude",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code 3D Extrude",
|
||||
"description": "Syntax-highlighted code on a lit, beveled 3D slab that rotates through real space and settles to a readable rest — true WebGL depth and lighting, not a 2D transform.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 8,
|
||||
"tags": ["code", "code-animation", "3d", "webgl", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-3d-extrude.html",
|
||||
"target": "compositions/code-3d-extrude.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-3d-extrude.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-3d-extrude.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,816 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Code Diff</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #05070b;
|
||||
}
|
||||
#root {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
color: #e6edf3;
|
||||
}
|
||||
.bg-field {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(1200px 700px at 50% 18%, #0e1726 0%, #070b12 55%, #05070b 100%);
|
||||
}
|
||||
.bg-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(88, 166, 255, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(88, 166, 255, 0.05) 1px, transparent 1px);
|
||||
background-size: 64px 64px;
|
||||
mask-image: radial-gradient(1100px 620px at 50% 42%, #000 0%, transparent 78%);
|
||||
}
|
||||
.bg-glow {
|
||||
position: absolute;
|
||||
width: 900px;
|
||||
height: 900px;
|
||||
border-radius: 50%;
|
||||
filter: blur(120px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bg-glow.a {
|
||||
background: #1f6feb55;
|
||||
left: -180px;
|
||||
top: -200px;
|
||||
}
|
||||
.bg-glow.b {
|
||||
background: #2ea04355;
|
||||
right: -200px;
|
||||
bottom: -260px;
|
||||
}
|
||||
.editor {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1380px;
|
||||
height: 800px;
|
||||
background: #0b0f17;
|
||||
border: 1px solid #1d2733;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 40px 120px rgba(0, 0, 0, 0.6),
|
||||
0 2px 0 rgba(255, 255, 255, 0.03) inset;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.titlebar {
|
||||
height: 56px;
|
||||
flex: 0 0 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 0 22px;
|
||||
background: linear-gradient(#11161f, #0c111a);
|
||||
border-bottom: 1px solid #1b2430;
|
||||
}
|
||||
.dots {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.r {
|
||||
background: #ff5f57;
|
||||
}
|
||||
.dot.y {
|
||||
background: #febc2e;
|
||||
}
|
||||
.dot.g {
|
||||
background: #28c840;
|
||||
}
|
||||
.filename {
|
||||
font-size: 19px;
|
||||
color: #8b98a9;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.filename .accent {
|
||||
color: #d6e2f0;
|
||||
}
|
||||
.code-surface {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.scene {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
.code {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0 44px 0 104px;
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
font-variant-ligatures: none;
|
||||
tab-size: 2;
|
||||
}
|
||||
.gutter {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
width: 84px;
|
||||
padding-right: 18px;
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
text-align: right;
|
||||
color: #828c9b;
|
||||
user-select: none;
|
||||
}
|
||||
.ln {
|
||||
display: block;
|
||||
height: 46px;
|
||||
}
|
||||
.line {
|
||||
display: block;
|
||||
min-height: 46px;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.tok {
|
||||
display: inline-block;
|
||||
white-space: pre;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.caret {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
height: 32px;
|
||||
margin-top: 6px;
|
||||
background: #58a6ff;
|
||||
border-radius: 1px;
|
||||
z-index: 3;
|
||||
}
|
||||
.hl-box {
|
||||
position: absolute;
|
||||
background: rgba(88, 166, 255, 0.16);
|
||||
border-left: 3px solid #58a6ff;
|
||||
border-radius: 6px;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.diff-line {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
line-height: 46px;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-left: 14px;
|
||||
}
|
||||
.diff-sign {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
color: #41506220;
|
||||
}
|
||||
.diff-del {
|
||||
background: rgba(248, 81, 73, 0.12);
|
||||
box-shadow: inset 3px 0 #f85149;
|
||||
}
|
||||
.diff-add {
|
||||
background: rgba(63, 185, 80, 0.12);
|
||||
box-shadow: inset 3px 0 #3fb950;
|
||||
}
|
||||
.diff-del .diff-sign {
|
||||
color: #f85149;
|
||||
}
|
||||
.diff-add .diff-sign {
|
||||
color: #3fb950;
|
||||
}
|
||||
.flight-block {
|
||||
position: absolute;
|
||||
white-space: pre;
|
||||
font-size: 27px;
|
||||
line-height: 40px;
|
||||
padding: 11px 22px;
|
||||
border-radius: 11px;
|
||||
background: #0f1722;
|
||||
border: 1px solid #233044;
|
||||
box-shadow: 0 16px 44px rgba(0, 0, 0, 0.5);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.flight-target {
|
||||
position: absolute;
|
||||
border: 1.5px dashed #2a3a4f;
|
||||
border-radius: 14px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="code-diff"
|
||||
data-start="0"
|
||||
data-duration="6"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="bg-field" data-layout-ignore></div>
|
||||
<div class="bg-grid" data-layout-ignore></div>
|
||||
<div class="bg-glow a" data-layout-ignore></div>
|
||||
<div class="bg-glow b" data-layout-ignore></div>
|
||||
<div class="editor" id="editor" data-layout-ignore>
|
||||
<div class="titlebar">
|
||||
<div class="dots">
|
||||
<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
|
||||
</div>
|
||||
<span class="filename"><span class="accent">greet.js</span> — Code Diff</span>
|
||||
</div>
|
||||
<div class="code-surface" id="surface"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.__TOKENS = {
|
||||
diff: {
|
||||
lang: "js",
|
||||
theme: "github-dark",
|
||||
bg: "#24292e",
|
||||
fg: "#e1e4e8",
|
||||
states: [
|
||||
{
|
||||
code: 'function greet(name) {\n console.log("hi " + name)\n}',
|
||||
tokens: [
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-0",
|
||||
content: "function",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-2",
|
||||
content: "greet",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-3",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-4",
|
||||
content: "name",
|
||||
color: "#FFAB70",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-5",
|
||||
content: ") {",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-6",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-7",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-8",
|
||||
content: "console.",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-9",
|
||||
content: "log",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-10",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-11",
|
||||
content: '"hi "',
|
||||
color: "#9ECBFF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-12",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-13",
|
||||
content: "+",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-14",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-15",
|
||||
content: "name)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-16",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-17",
|
||||
content: "}",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-18",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: 'function greet(name, lang) {\n const msg = translate("hi", lang)\n console.log(`${msg} ${name}`)\n}',
|
||||
tokens: [
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-0",
|
||||
content: "function",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-2",
|
||||
content: "greet",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-3",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-4",
|
||||
content: "name",
|
||||
color: "#FFAB70",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-5",
|
||||
content: ",",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-6",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-7",
|
||||
content: "lang",
|
||||
color: "#FFAB70",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-5",
|
||||
content: ") {",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-6",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-7",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-11",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-12",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-13",
|
||||
content: "msg",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-14",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-15",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-16",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-17",
|
||||
content: "translate",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-10",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-19",
|
||||
content: '"hi"',
|
||||
color: "#9ECBFF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-20",
|
||||
content: ", lang)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-21",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-22",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-23",
|
||||
content: "console.",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-9",
|
||||
content: "log",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-25",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-26",
|
||||
content: "`${",
|
||||
color: "#9ECBFF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-27",
|
||||
content: "msg",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-28",
|
||||
content: "} ${",
|
||||
color: "#9ECBFF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-29",
|
||||
content: "name",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-30",
|
||||
content: "}`",
|
||||
color: "#9ECBFF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-31",
|
||||
content: ")",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-16",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "97wperY-uI8_OA5W5UX94Q5keKEoIobaKXQ6V8Tcd5I-17",
|
||||
content: "}",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "w2rmoAdPXgC70S4pDXgHIIwz8Jqq0QKYf1B6jvTMUJ8-34",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
window.__BLOCK = { id: "code-diff", effect: "diff", seq: "diff", duration: 6 };
|
||||
</script>
|
||||
<script>
|
||||
// hf-code-anim.js — self-contained code-animation engine for HyperFrames registry blocks.
|
||||
//
|
||||
// Reuses Shiki Magic Move's baked output (window.__TOKENS, baked at author time) and
|
||||
// drives the animation as a PAUSED GSAP timeline the HyperFrames engine seeks per frame —
|
||||
// NOT CSS transitions (those break under non-sequential frame seeking). FLIP measurement
|
||||
// runs once inside document.fonts.ready (glyph metrics must be final). Each block sets
|
||||
// window.__BLOCK = { id, effect, seq, line?, duration } and includes this engine; the
|
||||
// engine builds one scene that fills the composition and registers window.__timelines[id].
|
||||
(function () {
|
||||
"use strict";
|
||||
var T = window.__TOKENS || {};
|
||||
|
||||
function lineDiv() {
|
||||
var d = document.createElement("div");
|
||||
d.className = "line";
|
||||
return d;
|
||||
}
|
||||
|
||||
// Render a code state's keyed tokens into codeEl as lines of inline-block .tok spans.
|
||||
function renderState(codeEl, state) {
|
||||
codeEl.textContent = "";
|
||||
var keyToEl = new Map();
|
||||
var line = lineDiv();
|
||||
codeEl.appendChild(line);
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
line = lineDiv();
|
||||
codeEl.appendChild(line);
|
||||
continue;
|
||||
}
|
||||
var span = document.createElement("span");
|
||||
span.className = "tok";
|
||||
span.textContent = t.content;
|
||||
span.style.color = t.color;
|
||||
if (t.fontStyle & 1) span.style.fontStyle = "italic";
|
||||
if (t.fontStyle & 2) span.style.fontWeight = "700";
|
||||
line.appendChild(span);
|
||||
keyToEl.set(t.key, span);
|
||||
}
|
||||
var last = codeEl.lastChild;
|
||||
if (last && last.className === "line" && last.children.length === 0)
|
||||
codeEl.removeChild(last);
|
||||
return keyToEl;
|
||||
}
|
||||
|
||||
function rectsOf(keyToEl) {
|
||||
var m = new Map();
|
||||
keyToEl.forEach(function (el, k) {
|
||||
var r = el.getBoundingClientRect();
|
||||
m.set(k, { left: r.left, top: r.top });
|
||||
});
|
||||
return m;
|
||||
}
|
||||
|
||||
function makeScene(surface, withGutter) {
|
||||
var scene = document.createElement("div");
|
||||
scene.className = "scene";
|
||||
if (withGutter) {
|
||||
var g = document.createElement("div");
|
||||
g.className = "gutter";
|
||||
scene.appendChild(g);
|
||||
}
|
||||
var code = document.createElement("div");
|
||||
code.className = "code";
|
||||
scene.appendChild(code);
|
||||
surface.appendChild(scene);
|
||||
return { scene: scene, code: code, gutter: scene.querySelector(".gutter") };
|
||||
}
|
||||
|
||||
function fillGutter(g, n) {
|
||||
var html = "";
|
||||
for (var i = 1; i <= n; i++) html += '<span class="ln">' + i + "</span>";
|
||||
g.innerHTML = html;
|
||||
}
|
||||
|
||||
function splitLines(state) {
|
||||
var lines = [{ text: "", tokens: [] }];
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
lines.push({ text: "", tokens: [] });
|
||||
continue;
|
||||
}
|
||||
var cur = lines[lines.length - 1];
|
||||
cur.tokens.push(t);
|
||||
cur.text += t.content;
|
||||
}
|
||||
if (lines.length && lines[lines.length - 1].tokens.length === 0) lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
function lineDiff(a, b) {
|
||||
var n = a.length;
|
||||
var m = b.length;
|
||||
var dp = [];
|
||||
for (var i = 0; i <= n; i++) dp.push(new Array(m + 1).fill(0));
|
||||
for (var i = n - 1; i >= 0; i--) {
|
||||
for (var j = m - 1; j >= 0; j--) {
|
||||
dp[i][j] =
|
||||
a[i].text === b[j].text
|
||||
? dp[i + 1][j + 1] + 1
|
||||
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||
}
|
||||
}
|
||||
var ops = [];
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
while (i < n && j < m) {
|
||||
if (a[i].text === b[j].text) {
|
||||
ops.push({ type: "same", line: b[j] });
|
||||
i++;
|
||||
j++;
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
ops.push({ type: "del", line: a[i] });
|
||||
i++;
|
||||
} else {
|
||||
ops.push({ type: "add", line: b[j] });
|
||||
j++;
|
||||
}
|
||||
}
|
||||
while (i < n) ops.push({ type: "del", line: a[i++] });
|
||||
while (j < m) ops.push({ type: "add", line: b[j++] });
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ---- single-scene effect builders (fade in, play, hold to end) -------------
|
||||
var FADE = 0.45;
|
||||
|
||||
function diff(tl, surface, states, start) {
|
||||
var DEL = 0.55;
|
||||
var ADD = 0.6;
|
||||
var ops = lineDiff(splitLines(states[0]), splitLines(states[1]));
|
||||
var parts = makeScene(surface, false);
|
||||
var code = parts.code;
|
||||
var delEls = [];
|
||||
var addEls = [];
|
||||
ops.forEach(function (op) {
|
||||
var ln = document.createElement("div");
|
||||
ln.className =
|
||||
"diff-line" +
|
||||
(op.type === "del" ? " diff-del" : op.type === "add" ? " diff-add" : "");
|
||||
var sign = document.createElement("span");
|
||||
sign.className = "diff-sign";
|
||||
sign.textContent = op.type === "del" ? "- " : op.type === "add" ? "+ " : " ";
|
||||
ln.appendChild(sign);
|
||||
op.line.tokens.forEach(function (t) {
|
||||
var s = document.createElement("span");
|
||||
s.className = "tok";
|
||||
s.textContent = t.content;
|
||||
s.style.color = t.color;
|
||||
ln.appendChild(s);
|
||||
});
|
||||
code.appendChild(ln);
|
||||
if (op.type === "del") delEls.push(ln);
|
||||
if (op.type === "add") addEls.push(ln);
|
||||
});
|
||||
tl.set(parts.scene, { opacity: 0 }, 0);
|
||||
tl.to(parts.scene, { opacity: 1, duration: FADE, ease: "power1.out" }, start);
|
||||
var atDel = start + FADE + 0.3;
|
||||
delEls.forEach(function (el, k) {
|
||||
tl.fromTo(
|
||||
el,
|
||||
{ height: 46, opacity: 1 },
|
||||
{ height: 0, opacity: 0, duration: DEL, ease: "power2.inOut" },
|
||||
atDel + k * 0.08,
|
||||
);
|
||||
});
|
||||
var atAdd = atDel + DEL + 0.15;
|
||||
addEls.forEach(function (el, k) {
|
||||
tl.set(el, { height: 0, opacity: 0 }, 0);
|
||||
tl.fromTo(
|
||||
el,
|
||||
{ height: 0, opacity: 0 },
|
||||
{ height: 46, opacity: 1, duration: ADD, ease: "power2.out" },
|
||||
atAdd + k * 0.12,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function buildEffect(tl, surface, spec) {
|
||||
var start = 0.45;
|
||||
var seq = T[spec.seq];
|
||||
if (spec.effect === "diff") diff(tl, surface, seq.states, start);
|
||||
}
|
||||
|
||||
function go() {
|
||||
var spec = window.__BLOCK;
|
||||
var surface = document.getElementById("surface");
|
||||
var root = document.getElementById("root");
|
||||
window.__timelines = window.__timelines || {};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
// Build inside fonts.ready (glyph metrics must be final), then register.
|
||||
// Register ONLY after the timeline is fully built: the runtime's
|
||||
// sub-composition readiness gate treats "key present" as "ready" and
|
||||
// nests the child once. An empty timeline registered before this build
|
||||
// would be nested empty and never re-nested → blank render when used as
|
||||
// a sub-composition. See the contract in engine frameCapture.ts.
|
||||
document.fonts.ready.then(function () {
|
||||
tl.from("#editor", { opacity: 0, scale: 0.985, duration: 0.5, ease: "power2.out" }, 0);
|
||||
buildEffect(tl, surface, spec);
|
||||
var dur = parseFloat(root.dataset.duration) || tl.duration();
|
||||
tl.to({}, { duration: dur }, 0); // pad to full composition length
|
||||
window.__timelines["code-diff"] = tl; // register AFTER setup completes
|
||||
if (typeof window.__hfForceTimelineRebind === "function") {
|
||||
window.__hfForceTimelineRebind(); // re-nest now that we are populated
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go);
|
||||
else go();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-diff",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Diff",
|
||||
"description": "An edit shown as a colored diff — removed lines collapse in red, added lines expand in green.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 6,
|
||||
"tags": ["code", "code-animation", "diff", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-diff.html",
|
||||
"target": "compositions/code-diff.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-diff.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-diff.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,745 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Code Highlight Sweep</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #05070b;
|
||||
}
|
||||
#root {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
color: #e6edf3;
|
||||
}
|
||||
.bg-field {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(1200px 700px at 50% 18%, #0e1726 0%, #070b12 55%, #05070b 100%);
|
||||
}
|
||||
.bg-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(88, 166, 255, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(88, 166, 255, 0.05) 1px, transparent 1px);
|
||||
background-size: 64px 64px;
|
||||
mask-image: radial-gradient(1100px 620px at 50% 42%, #000 0%, transparent 78%);
|
||||
}
|
||||
.bg-glow {
|
||||
position: absolute;
|
||||
width: 900px;
|
||||
height: 900px;
|
||||
border-radius: 50%;
|
||||
filter: blur(120px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bg-glow.a {
|
||||
background: #1f6feb55;
|
||||
left: -180px;
|
||||
top: -200px;
|
||||
}
|
||||
.bg-glow.b {
|
||||
background: #2ea04355;
|
||||
right: -200px;
|
||||
bottom: -260px;
|
||||
}
|
||||
.editor {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1380px;
|
||||
height: 800px;
|
||||
background: #0b0f17;
|
||||
border: 1px solid #1d2733;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 40px 120px rgba(0, 0, 0, 0.6),
|
||||
0 2px 0 rgba(255, 255, 255, 0.03) inset;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.titlebar {
|
||||
height: 56px;
|
||||
flex: 0 0 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 0 22px;
|
||||
background: linear-gradient(#11161f, #0c111a);
|
||||
border-bottom: 1px solid #1b2430;
|
||||
}
|
||||
.dots {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.r {
|
||||
background: #ff5f57;
|
||||
}
|
||||
.dot.y {
|
||||
background: #febc2e;
|
||||
}
|
||||
.dot.g {
|
||||
background: #28c840;
|
||||
}
|
||||
.filename {
|
||||
font-size: 19px;
|
||||
color: #8b98a9;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.filename .accent {
|
||||
color: #d6e2f0;
|
||||
}
|
||||
.code-surface {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.scene {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
.code {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0 44px 0 104px;
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
font-variant-ligatures: none;
|
||||
tab-size: 2;
|
||||
}
|
||||
.gutter {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
width: 84px;
|
||||
padding-right: 18px;
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
text-align: right;
|
||||
color: #828c9b;
|
||||
user-select: none;
|
||||
}
|
||||
.ln {
|
||||
display: block;
|
||||
height: 46px;
|
||||
}
|
||||
.line {
|
||||
display: block;
|
||||
min-height: 46px;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.tok {
|
||||
display: inline-block;
|
||||
white-space: pre;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.caret {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
height: 32px;
|
||||
margin-top: 6px;
|
||||
background: #58a6ff;
|
||||
border-radius: 1px;
|
||||
z-index: 3;
|
||||
}
|
||||
.hl-box {
|
||||
position: absolute;
|
||||
background: rgba(88, 166, 255, 0.16);
|
||||
border-left: 3px solid #58a6ff;
|
||||
border-radius: 6px;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.diff-line {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
line-height: 46px;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-left: 14px;
|
||||
}
|
||||
.diff-sign {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
color: #41506220;
|
||||
}
|
||||
.diff-del {
|
||||
background: rgba(248, 81, 73, 0.12);
|
||||
box-shadow: inset 3px 0 #f85149;
|
||||
}
|
||||
.diff-add {
|
||||
background: rgba(63, 185, 80, 0.12);
|
||||
box-shadow: inset 3px 0 #3fb950;
|
||||
}
|
||||
.diff-del .diff-sign {
|
||||
color: #f85149;
|
||||
}
|
||||
.diff-add .diff-sign {
|
||||
color: #3fb950;
|
||||
}
|
||||
.flight-block {
|
||||
position: absolute;
|
||||
white-space: pre;
|
||||
font-size: 27px;
|
||||
line-height: 40px;
|
||||
padding: 11px 22px;
|
||||
border-radius: 11px;
|
||||
background: #0f1722;
|
||||
border: 1px solid #233044;
|
||||
box-shadow: 0 16px 44px rgba(0, 0, 0, 0.5);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.flight-target {
|
||||
position: absolute;
|
||||
border: 1.5px dashed #2a3a4f;
|
||||
border-radius: 14px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="code-highlight"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="bg-field" data-layout-ignore></div>
|
||||
<div class="bg-grid" data-layout-ignore></div>
|
||||
<div class="bg-glow a" data-layout-ignore></div>
|
||||
<div class="bg-glow b" data-layout-ignore></div>
|
||||
<div class="editor" id="editor" data-layout-ignore>
|
||||
<div class="titlebar">
|
||||
<div class="dots">
|
||||
<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
|
||||
</div>
|
||||
<span class="filename"
|
||||
><span class="accent">loadConfig.js</span> — Code Highlight Sweep</span
|
||||
>
|
||||
</div>
|
||||
<div class="code-surface" id="surface"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.__TOKENS = {
|
||||
feature: {
|
||||
lang: "js",
|
||||
theme: "github-dark",
|
||||
bg: "#24292e",
|
||||
fg: "#e1e4e8",
|
||||
states: [
|
||||
{
|
||||
code: 'async function loadConfig(path) {\n const raw = await readFile(path, "utf8")\n const config = JSON.parse(raw)\n return validate(config)\n}',
|
||||
tokens: [
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-0",
|
||||
content: "async",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-2",
|
||||
content: "function",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-3",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-4",
|
||||
content: "loadConfig",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-5",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-6",
|
||||
content: "path",
|
||||
color: "#FFAB70",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-7",
|
||||
content: ") {",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-8",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-9",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-10",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-11",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-12",
|
||||
content: "raw",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-13",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-14",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-15",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-16",
|
||||
content: "await",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-17",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-18",
|
||||
content: "readFile",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-19",
|
||||
content: "(path,",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-20",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-21",
|
||||
content: '"utf8"',
|
||||
color: "#9ECBFF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-22",
|
||||
content: ")",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-23",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-24",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-25",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-26",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-27",
|
||||
content: "config",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-28",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-29",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-30",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-31",
|
||||
content: "JSON",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-32",
|
||||
content: ".",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-33",
|
||||
content: "parse",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-34",
|
||||
content: "(raw)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-35",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-36",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-37",
|
||||
content: "return",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-38",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-39",
|
||||
content: "validate",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-40",
|
||||
content: "(config)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-41",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-42",
|
||||
content: "}",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-43",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
window.__BLOCK = {
|
||||
id: "code-highlight",
|
||||
effect: "highlight",
|
||||
seq: "feature",
|
||||
line: 1,
|
||||
duration: 5,
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
// hf-code-anim.js — self-contained code-animation engine for HyperFrames registry blocks.
|
||||
//
|
||||
// Reuses Shiki Magic Move's baked output (window.__TOKENS, baked at author time) and
|
||||
// drives the animation as a PAUSED GSAP timeline the HyperFrames engine seeks per frame —
|
||||
// NOT CSS transitions (those break under non-sequential frame seeking). FLIP measurement
|
||||
// runs once inside document.fonts.ready (glyph metrics must be final). Each block sets
|
||||
// window.__BLOCK = { id, effect, seq, line?, duration } and includes this engine; the
|
||||
// engine builds one scene that fills the composition and registers window.__timelines[id].
|
||||
(function () {
|
||||
"use strict";
|
||||
var T = window.__TOKENS || {};
|
||||
|
||||
function lineDiv() {
|
||||
var d = document.createElement("div");
|
||||
d.className = "line";
|
||||
return d;
|
||||
}
|
||||
|
||||
// Render a code state's keyed tokens into codeEl as lines of inline-block .tok spans.
|
||||
function renderState(codeEl, state) {
|
||||
codeEl.textContent = "";
|
||||
var keyToEl = new Map();
|
||||
var line = lineDiv();
|
||||
codeEl.appendChild(line);
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
line = lineDiv();
|
||||
codeEl.appendChild(line);
|
||||
continue;
|
||||
}
|
||||
var span = document.createElement("span");
|
||||
span.className = "tok";
|
||||
span.textContent = t.content;
|
||||
span.style.color = t.color;
|
||||
if (t.fontStyle & 1) span.style.fontStyle = "italic";
|
||||
if (t.fontStyle & 2) span.style.fontWeight = "700";
|
||||
line.appendChild(span);
|
||||
keyToEl.set(t.key, span);
|
||||
}
|
||||
var last = codeEl.lastChild;
|
||||
if (last && last.className === "line" && last.children.length === 0)
|
||||
codeEl.removeChild(last);
|
||||
return keyToEl;
|
||||
}
|
||||
|
||||
function rectsOf(keyToEl) {
|
||||
var m = new Map();
|
||||
keyToEl.forEach(function (el, k) {
|
||||
var r = el.getBoundingClientRect();
|
||||
m.set(k, { left: r.left, top: r.top });
|
||||
});
|
||||
return m;
|
||||
}
|
||||
|
||||
function makeScene(surface, withGutter) {
|
||||
var scene = document.createElement("div");
|
||||
scene.className = "scene";
|
||||
if (withGutter) {
|
||||
var g = document.createElement("div");
|
||||
g.className = "gutter";
|
||||
scene.appendChild(g);
|
||||
}
|
||||
var code = document.createElement("div");
|
||||
code.className = "code";
|
||||
scene.appendChild(code);
|
||||
surface.appendChild(scene);
|
||||
return { scene: scene, code: code, gutter: scene.querySelector(".gutter") };
|
||||
}
|
||||
|
||||
function fillGutter(g, n) {
|
||||
var html = "";
|
||||
for (var i = 1; i <= n; i++) html += '<span class="ln">' + i + "</span>";
|
||||
g.innerHTML = html;
|
||||
}
|
||||
|
||||
function splitLines(state) {
|
||||
var lines = [{ text: "", tokens: [] }];
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
lines.push({ text: "", tokens: [] });
|
||||
continue;
|
||||
}
|
||||
var cur = lines[lines.length - 1];
|
||||
cur.tokens.push(t);
|
||||
cur.text += t.content;
|
||||
}
|
||||
if (lines.length && lines[lines.length - 1].tokens.length === 0) lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
function lineDiff(a, b) {
|
||||
var n = a.length;
|
||||
var m = b.length;
|
||||
var dp = [];
|
||||
for (var i = 0; i <= n; i++) dp.push(new Array(m + 1).fill(0));
|
||||
for (var i = n - 1; i >= 0; i--) {
|
||||
for (var j = m - 1; j >= 0; j--) {
|
||||
dp[i][j] =
|
||||
a[i].text === b[j].text
|
||||
? dp[i + 1][j + 1] + 1
|
||||
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||
}
|
||||
}
|
||||
var ops = [];
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
while (i < n && j < m) {
|
||||
if (a[i].text === b[j].text) {
|
||||
ops.push({ type: "same", line: b[j] });
|
||||
i++;
|
||||
j++;
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
ops.push({ type: "del", line: a[i] });
|
||||
i++;
|
||||
} else {
|
||||
ops.push({ type: "add", line: b[j] });
|
||||
j++;
|
||||
}
|
||||
}
|
||||
while (i < n) ops.push({ type: "del", line: a[i++] });
|
||||
while (j < m) ops.push({ type: "add", line: b[j++] });
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ---- single-scene effect builders (fade in, play, hold to end) -------------
|
||||
var FADE = 0.45;
|
||||
|
||||
function highlight(tl, surface, state, start, targetIdx) {
|
||||
var SWEEP = 0.9;
|
||||
var parts = makeScene(surface, true);
|
||||
var code = parts.code;
|
||||
renderState(code, state);
|
||||
var lines = code.querySelectorAll(".line");
|
||||
fillGutter(parts.gutter, lines.length);
|
||||
var base = code.getBoundingClientRect();
|
||||
var idx = Math.max(0, Math.min(lines.length - 1, targetIdx));
|
||||
var lr = lines[idx].getBoundingClientRect();
|
||||
var box = document.createElement("div");
|
||||
box.className = "hl-box";
|
||||
box.style.left = lr.left - base.left - 8 + "px";
|
||||
box.style.top = lr.top - base.top - 1 + "px";
|
||||
box.style.height = lr.height + 2 + "px";
|
||||
code.insertBefore(box, code.firstChild);
|
||||
tl.set(parts.scene, { opacity: 0 }, 0);
|
||||
tl.to(parts.scene, { opacity: 1, duration: FADE, ease: "power1.out" }, start);
|
||||
var at = start + FADE + 0.2;
|
||||
tl.fromTo(
|
||||
box,
|
||||
{ width: 0, opacity: 0 },
|
||||
{ width: lr.width + 18, opacity: 1, duration: SWEEP, ease: "power2.inOut" },
|
||||
at,
|
||||
);
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (i === idx) continue;
|
||||
tl.fromTo(
|
||||
lines[i],
|
||||
{ opacity: 1 },
|
||||
{ opacity: 0.45, duration: SWEEP, ease: "power2.inOut" },
|
||||
at,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function buildEffect(tl, surface, spec) {
|
||||
var start = 0.45;
|
||||
var seq = T[spec.seq];
|
||||
if (spec.effect === "highlight")
|
||||
highlight(tl, surface, seq.states[0], start, spec.line || 0);
|
||||
}
|
||||
|
||||
function go() {
|
||||
var spec = window.__BLOCK;
|
||||
var surface = document.getElementById("surface");
|
||||
var root = document.getElementById("root");
|
||||
window.__timelines = window.__timelines || {};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
// Build inside fonts.ready (glyph metrics must be final), then register.
|
||||
// Register ONLY after the timeline is fully built: the runtime's
|
||||
// sub-composition readiness gate treats "key present" as "ready" and
|
||||
// nests the child once. An empty timeline registered before this build
|
||||
// would be nested empty and never re-nested → blank render when used as
|
||||
// a sub-composition. See the contract in engine frameCapture.ts.
|
||||
document.fonts.ready.then(function () {
|
||||
tl.from("#editor", { opacity: 0, scale: 0.985, duration: 0.5, ease: "power2.out" }, 0);
|
||||
buildEffect(tl, surface, spec);
|
||||
var dur = parseFloat(root.dataset.duration) || tl.duration();
|
||||
tl.to({}, { duration: dur }, 0); // pad to full composition length
|
||||
window.__timelines["code-highlight"] = tl; // register AFTER setup completes
|
||||
if (typeof window.__hfForceTimelineRebind === "function") {
|
||||
window.__hfForceTimelineRebind(); // re-nest now that we are populated
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go);
|
||||
else go();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-highlight",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Highlight Sweep",
|
||||
"description": "A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 5,
|
||||
"tags": ["code", "code-animation", "highlight", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-highlight.html",
|
||||
"target": "compositions/code-highlight.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-highlight.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-highlight.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-morph",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Morph",
|
||||
"description": "One snippet transforms into another — tokens glide between positions, leavers fade out, enterers fade in. Shiki Magic Move re-driven as a paused GSAP timeline.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 7,
|
||||
"tags": ["code", "code-animation", "morph", "refactor", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-morph.html",
|
||||
"target": "compositions/code-morph.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-morph.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-morph.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Code Particle Assemble</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #05070b;
|
||||
}
|
||||
#root {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
}
|
||||
#gl {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="code-particle-assemble"
|
||||
data-start="0"
|
||||
data-duration="8"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<canvas id="gl" width="1920" height="1080"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
window.__TOKENS = {
|
||||
assemble: {
|
||||
lang: "js",
|
||||
theme: "github-dark",
|
||||
bg: "#24292e",
|
||||
fg: "#e1e4e8",
|
||||
states: [
|
||||
{
|
||||
code: "const app = pipe(\n parse,\n optimize,\n emit,\n)",
|
||||
tokens: [
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-0",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-2",
|
||||
content: "app",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-3",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-4",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-5",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-6",
|
||||
content: "pipe",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-7",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-8",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-9",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-10",
|
||||
content: "parse,",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-11",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-12",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-13",
|
||||
content: "optimize,",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-14",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-15",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-16",
|
||||
content: "emit,",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-17",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-18",
|
||||
content: ")",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "AFffx9qQBGKnMZ-vDjoaRwC68xQloGJNUSICY1myCK4-19",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
window.__BLOCK = {
|
||||
id: "code-particle-assemble",
|
||||
effect: "particles",
|
||||
seq: "assemble",
|
||||
duration: 8,
|
||||
seed: 23,
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
// hf-code-anim-gl.js — WebGL code-animation engine for the "crazy" Code Animations blocks.
|
||||
//
|
||||
// Bridges the baked Shiki tokens (window.__TOKENS) into Three.js: the tokens are drawn once
|
||||
// to an offscreen 2D canvas (a CanvasTexture), which then drives a lit 3D slab, a dissolve
|
||||
// shader, or a GPU particle system. Determinism per the project contract:
|
||||
// - render only via tl.eventCallback("onUpdate", …) + one initial render() — never rAF
|
||||
// - all randomness from a seeded mulberry32 — never Math.random / Date.now
|
||||
// - canvas drawn once after document.fonts.ready (final glyph metrics)
|
||||
// - paused GSAP timeline registered on window.__timelines[id]; HF seeks it per frame
|
||||
//
|
||||
// Each block sets window.__BLOCK = { id, effect, seq, duration, seed } and includes this engine.
|
||||
(function () {
|
||||
"use strict";
|
||||
var T = window.__TOKENS || {};
|
||||
|
||||
// ---- seeded PRNG (no Math.random) -----------------------------------------
|
||||
function mulberry32(a) {
|
||||
return function () {
|
||||
a |= 0;
|
||||
a = (a + 0x6d2b79f5) | 0;
|
||||
var t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
// ---- split a token state into lines of {content,color} --------------------
|
||||
function splitLines(state) {
|
||||
var lines = [[]];
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
lines.push([]);
|
||||
continue;
|
||||
}
|
||||
lines[lines.length - 1].push(t);
|
||||
}
|
||||
while (lines.length && lines[lines.length - 1].length === 0) lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
// ---- THE BRIDGE: draw baked tokens to an offscreen 2D canvas ---------------
|
||||
// Returns { canvas, W, H, lines, lineH, padY, fontPx } so effects can sample it.
|
||||
function drawCodeToCanvas(state, opts) {
|
||||
opts = opts || {};
|
||||
var fontPx = opts.fontPx || 36;
|
||||
var lineH = opts.lineH || 54;
|
||||
var padX = opts.padX || 70;
|
||||
var padY = opts.padY || 60;
|
||||
var weight = opts.weight || 500;
|
||||
var font = weight + " " + fontPx + 'px "JetBrains Mono", monospace';
|
||||
|
||||
var lines = splitLines(state);
|
||||
var measure = document.createElement("canvas").getContext("2d");
|
||||
measure.font = font;
|
||||
var maxW = 0;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var w = 0;
|
||||
for (var j = 0; j < lines[i].length; j++)
|
||||
w += measure.measureText(lines[i][j].content).width;
|
||||
if (w > maxW) maxW = w;
|
||||
}
|
||||
var W = Math.max(2, Math.ceil(maxW + padX * 2));
|
||||
var H = Math.max(2, Math.ceil(lines.length * lineH + padY * 2));
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
var ctx = canvas.getContext("2d");
|
||||
if (opts.transparent) {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
} else {
|
||||
ctx.fillStyle = opts.bg || "#0b0f17";
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
}
|
||||
ctx.textBaseline = "top";
|
||||
ctx.font = font;
|
||||
for (var li = 0; li < lines.length; li++) {
|
||||
var x = padX;
|
||||
var y = padY + li * lineH;
|
||||
for (var k = 0; k < lines[li].length; k++) {
|
||||
var tok = lines[li][k];
|
||||
ctx.fillStyle = tok.color;
|
||||
ctx.fillText(tok.content, x, y);
|
||||
x += ctx.measureText(tok.content).width;
|
||||
}
|
||||
}
|
||||
return {
|
||||
canvas: canvas,
|
||||
W: W,
|
||||
H: H,
|
||||
lines: lines.length,
|
||||
lineH: lineH,
|
||||
padY: padY,
|
||||
fontPx: fontPx,
|
||||
};
|
||||
}
|
||||
|
||||
function fxParticles(THREE, scene, camera, renderer, state, st, tl, dur, seed) {
|
||||
scene.background = new THREE.Color(0x05070b);
|
||||
var code = drawCodeToCanvas(state, {
|
||||
fontPx: 46,
|
||||
lineH: 70,
|
||||
padX: 70,
|
||||
padY: 64,
|
||||
weight: 700,
|
||||
});
|
||||
var cw = code.W;
|
||||
var ch = code.H;
|
||||
var data = code.canvas.getContext("2d").getImageData(0, 0, cw, ch).data;
|
||||
var rng = mulberry32(((seed || 1) * 2654435761) >>> 0);
|
||||
|
||||
var worldW = 13.0;
|
||||
var worldH = worldW * (ch / cw);
|
||||
|
||||
// sample glyph pixels (brighter than the dark bg) on a fixed grid
|
||||
var step = 2;
|
||||
var tx = [];
|
||||
var col = [];
|
||||
for (var y = 0; y < ch; y += step) {
|
||||
for (var x = 0; x < cw; x += step) {
|
||||
var idx = (y * cw + x) * 4;
|
||||
var r = data[idx];
|
||||
var g = data[idx + 1];
|
||||
var b = data[idx + 2];
|
||||
if (r + g + b < 120) continue; // skip background
|
||||
tx.push((x / cw - 0.5) * worldW, -(y / ch - 0.5) * worldH, 0);
|
||||
col.push(r / 255, g / 255, b / 255);
|
||||
}
|
||||
}
|
||||
var n = tx.length / 3;
|
||||
|
||||
var positions = new Float32Array(n * 3);
|
||||
var aOrigin = new Float32Array(n * 3);
|
||||
var aTarget = new Float32Array(tx);
|
||||
var aColor = new Float32Array(col);
|
||||
var aDelay = new Float32Array(n);
|
||||
for (var i = 0; i < n; i++) {
|
||||
var sx = (rng() * 2 - 1) * worldW * 1.15;
|
||||
var sy = (rng() * 2 - 1) * worldH * 1.7;
|
||||
var sz = (rng() * 2 - 1) * 9.0 - 2.0;
|
||||
aOrigin[i * 3] = sx;
|
||||
aOrigin[i * 3 + 1] = sy;
|
||||
aOrigin[i * 3 + 2] = sz;
|
||||
positions[i * 3] = sx;
|
||||
positions[i * 3 + 1] = sy;
|
||||
positions[i * 3 + 2] = sz;
|
||||
aDelay[i] = rng();
|
||||
}
|
||||
|
||||
var geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
||||
geo.setAttribute("aOrigin", new THREE.BufferAttribute(aOrigin, 3));
|
||||
geo.setAttribute("aTarget", new THREE.BufferAttribute(aTarget, 3));
|
||||
geo.setAttribute("aColor", new THREE.BufferAttribute(aColor, 3));
|
||||
geo.setAttribute("aDelay", new THREE.BufferAttribute(aDelay, 1));
|
||||
|
||||
var uniforms = { uProgress: { value: 0 }, uSize: { value: 3.4 } };
|
||||
var mat = new THREE.ShaderMaterial({
|
||||
uniforms: uniforms,
|
||||
transparent: true,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
blending: THREE.NormalBlending,
|
||||
vertexShader: [
|
||||
"attribute vec3 aOrigin; attribute vec3 aTarget; attribute vec3 aColor; attribute float aDelay;",
|
||||
"uniform float uProgress; uniform float uSize;",
|
||||
"varying vec3 vColor; varying float vP;",
|
||||
"void main(){",
|
||||
" float span = 0.62;",
|
||||
" float p = clamp((uProgress - aDelay*(1.0-span)) / span, 0.0, 1.0);",
|
||||
" p = p*p*(3.0-2.0*p);",
|
||||
" vec3 pos = mix(aOrigin, aTarget, p);",
|
||||
" vec4 mv = modelViewMatrix * vec4(pos, 1.0);",
|
||||
" gl_Position = projectionMatrix * mv;",
|
||||
" gl_PointSize = uSize * (60.0 / -mv.z) * (0.45 + 0.55*p);",
|
||||
" vColor = aColor; vP = p;",
|
||||
"}",
|
||||
].join("\n"),
|
||||
fragmentShader: [
|
||||
"precision highp float;",
|
||||
"varying vec3 vColor; varying float vP;",
|
||||
"void main(){",
|
||||
" vec2 d = gl_PointCoord - 0.5;",
|
||||
" float r2 = dot(d, d);",
|
||||
" if (r2 > 0.25) discard;",
|
||||
" float a = smoothstep(0.25, 0.02, r2);",
|
||||
// brighter & whiter in flight, resolves to true color as it lands
|
||||
" vec3 c = mix(vColor + vec3(0.32)*(1.0-vP), vColor, vP);",
|
||||
" gl_FragColor = vec4(c, a * (0.7 + 0.3*vP));",
|
||||
"}",
|
||||
].join("\n"),
|
||||
});
|
||||
|
||||
var pts = new THREE.Points(geo, mat);
|
||||
scene.add(pts);
|
||||
|
||||
camera.position.set(0, 0, 10);
|
||||
var vfov = (camera.fov * Math.PI) / 180;
|
||||
var sa = 1920 / 1080;
|
||||
var fitH = worldH / 2 / Math.tan(vfov / 2) / 0.78;
|
||||
var fitW = worldW / 2 / (Math.tan(vfov / 2) * sa) / 0.78;
|
||||
camera.position.z = Math.max(fitH, fitW);
|
||||
camera.lookAt(0, 0, 0);
|
||||
|
||||
tl.to(uniforms.uProgress, { value: 1.0, duration: dur * 0.72, ease: "power2.out" }, 0);
|
||||
|
||||
// uProgress is tweened by GSAP; the boot loop's render() draws once per frame.
|
||||
return function () {};
|
||||
}
|
||||
|
||||
// ---- dispatch + boot ------------------------------------------------------
|
||||
function buildEffect(THREE, scene, camera, renderer, spec, st, tl) {
|
||||
var state = T[spec.seq].states[0];
|
||||
var dur = spec.duration;
|
||||
if (spec.effect === "particles")
|
||||
return fxParticles(THREE, scene, camera, renderer, state, st, tl, dur, spec.seed);
|
||||
return function () {};
|
||||
}
|
||||
|
||||
// Free GPU resources (renderer, geometries, materials, textures) on page teardown.
|
||||
// One-shot renders exit per job so nothing leaks there; this guards long-lived
|
||||
// Studio/player sessions that reload. Fires only on unload — never during capture.
|
||||
function disposeGL(renderer, scene) {
|
||||
scene.traverse(function (o) {
|
||||
if (o.geometry && o.geometry.dispose) o.geometry.dispose();
|
||||
var mats = o.material ? (Array.isArray(o.material) ? o.material : [o.material]) : [];
|
||||
mats.forEach(function (m) {
|
||||
if (m.map && m.map.dispose) m.map.dispose();
|
||||
if (
|
||||
m.uniforms &&
|
||||
m.uniforms.uTex &&
|
||||
m.uniforms.uTex.value &&
|
||||
m.uniforms.uTex.value.dispose
|
||||
) {
|
||||
m.uniforms.uTex.value.dispose();
|
||||
}
|
||||
if (m.dispose) m.dispose();
|
||||
});
|
||||
});
|
||||
if (renderer.dispose) renderer.dispose();
|
||||
}
|
||||
|
||||
function go() {
|
||||
var spec = window.__BLOCK;
|
||||
var root = document.getElementById("root");
|
||||
var glcanvas = document.getElementById("gl");
|
||||
var W = 1920;
|
||||
var H = 1080;
|
||||
window.__timelines = window.__timelines || {};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.__timelines[spec.id] = tl; // register synchronously
|
||||
|
||||
var THREE = window.THREE;
|
||||
var renderer = new THREE.WebGLRenderer({
|
||||
canvas: glcanvas,
|
||||
antialias: true,
|
||||
alpha: false,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
renderer.setPixelRatio(1);
|
||||
renderer.setSize(W, H, false);
|
||||
var scene = new THREE.Scene();
|
||||
var camera = new THREE.PerspectiveCamera(45, W / H, 0.1, 200);
|
||||
var st = {};
|
||||
|
||||
document.fonts.ready.then(function () {
|
||||
var renderFn = buildEffect(THREE, scene, camera, renderer, spec, st, tl);
|
||||
function render() {
|
||||
renderFn();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
tl.eventCallback("onUpdate", render);
|
||||
var dur = parseFloat(root.dataset.duration) || tl.duration();
|
||||
tl.to({}, { duration: dur }, 0); // pad to full composition length
|
||||
render(); // paint frame 0
|
||||
window.addEventListener("beforeunload", function () {
|
||||
disposeGL(renderer, scene);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go);
|
||||
else go();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-particle-assemble",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Particle Assemble",
|
||||
"description": "Thousands of GPU points scatter through space and fly to the exact glyph pixels of the code, resolving into readable syntax-highlighted text — a particle system, not a token tween.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 8,
|
||||
"tags": ["code", "code-animation", "particles", "gpu", "webgl", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-particle-assemble.html",
|
||||
"target": "compositions/code-particle-assemble.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-particle-assemble.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-particle-assemble.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-scroll",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Scroll To Line",
|
||||
"description": "The camera scrolls a long file to bring a target line to center and spotlights it — for walking through real modules.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 6,
|
||||
"tags": ["code", "code-animation", "scroll", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-scroll.html",
|
||||
"target": "compositions/code-scroll.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-scroll.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-scroll.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Code Shader Dissolve</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.147.0/build/three.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #05070b;
|
||||
}
|
||||
#root {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
}
|
||||
#gl {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="code-shader-dissolve"
|
||||
data-start="0"
|
||||
data-duration="7"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<canvas id="gl" width="1920" height="1080"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
window.__TOKENS = {
|
||||
compile: {
|
||||
lang: "js",
|
||||
theme: "github-dark",
|
||||
bg: "#24292e",
|
||||
fg: "#e1e4e8",
|
||||
states: [
|
||||
{
|
||||
code: "function compile(src) {\n const ast = parse(src)\n const ir = optimize(ast)\n return emit(ir)\n}",
|
||||
tokens: [
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-0",
|
||||
content: "function",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-2",
|
||||
content: "compile",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-3",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-4",
|
||||
content: "src",
|
||||
color: "#FFAB70",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-5",
|
||||
content: ") {",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-6",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-7",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-8",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-9",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-10",
|
||||
content: "ast",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-11",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-12",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-13",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-14",
|
||||
content: "parse",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-15",
|
||||
content: "(src)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-16",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-17",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-18",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-19",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-20",
|
||||
content: "ir",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-21",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-22",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-23",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-24",
|
||||
content: "optimize",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-25",
|
||||
content: "(ast)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-26",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-27",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-28",
|
||||
content: "return",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-29",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-30",
|
||||
content: "emit",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-31",
|
||||
content: "(ir)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-32",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-33",
|
||||
content: "}",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "zGtBjd8ajYlOewpKFJD53Oc7W94UowxIfV0186O1l8A-34",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
window.__BLOCK = {
|
||||
id: "code-shader-dissolve",
|
||||
effect: "dissolve",
|
||||
seq: "compile",
|
||||
duration: 7,
|
||||
seed: 11,
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
// hf-code-anim-gl.js — WebGL code-animation engine for the "crazy" Code Animations blocks.
|
||||
//
|
||||
// Bridges the baked Shiki tokens (window.__TOKENS) into Three.js: the tokens are drawn once
|
||||
// to an offscreen 2D canvas (a CanvasTexture), which then drives a lit 3D slab, a dissolve
|
||||
// shader, or a GPU particle system. Determinism per the project contract:
|
||||
// - render only via tl.eventCallback("onUpdate", …) + one initial render() — never rAF
|
||||
// - all randomness from a seeded mulberry32 — never Math.random / Date.now
|
||||
// - canvas drawn once after document.fonts.ready (final glyph metrics)
|
||||
// - paused GSAP timeline registered on window.__timelines[id]; HF seeks it per frame
|
||||
//
|
||||
// Each block sets window.__BLOCK = { id, effect, seq, duration, seed } and includes this engine.
|
||||
(function () {
|
||||
"use strict";
|
||||
var T = window.__TOKENS || {};
|
||||
|
||||
// ---- seeded PRNG (no Math.random) -----------------------------------------
|
||||
function mulberry32(a) {
|
||||
return function () {
|
||||
a |= 0;
|
||||
a = (a + 0x6d2b79f5) | 0;
|
||||
var t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
// ---- split a token state into lines of {content,color} --------------------
|
||||
function splitLines(state) {
|
||||
var lines = [[]];
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
lines.push([]);
|
||||
continue;
|
||||
}
|
||||
lines[lines.length - 1].push(t);
|
||||
}
|
||||
while (lines.length && lines[lines.length - 1].length === 0) lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
// ---- THE BRIDGE: draw baked tokens to an offscreen 2D canvas ---------------
|
||||
// Returns { canvas, W, H, lines, lineH, padY, fontPx } so effects can sample it.
|
||||
function drawCodeToCanvas(state, opts) {
|
||||
opts = opts || {};
|
||||
var fontPx = opts.fontPx || 36;
|
||||
var lineH = opts.lineH || 54;
|
||||
var padX = opts.padX || 70;
|
||||
var padY = opts.padY || 60;
|
||||
var weight = opts.weight || 500;
|
||||
var font = weight + " " + fontPx + 'px "JetBrains Mono", monospace';
|
||||
|
||||
var lines = splitLines(state);
|
||||
var measure = document.createElement("canvas").getContext("2d");
|
||||
measure.font = font;
|
||||
var maxW = 0;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var w = 0;
|
||||
for (var j = 0; j < lines[i].length; j++)
|
||||
w += measure.measureText(lines[i][j].content).width;
|
||||
if (w > maxW) maxW = w;
|
||||
}
|
||||
var W = Math.max(2, Math.ceil(maxW + padX * 2));
|
||||
var H = Math.max(2, Math.ceil(lines.length * lineH + padY * 2));
|
||||
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
var ctx = canvas.getContext("2d");
|
||||
if (opts.transparent) {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
} else {
|
||||
ctx.fillStyle = opts.bg || "#0b0f17";
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
}
|
||||
ctx.textBaseline = "top";
|
||||
ctx.font = font;
|
||||
for (var li = 0; li < lines.length; li++) {
|
||||
var x = padX;
|
||||
var y = padY + li * lineH;
|
||||
for (var k = 0; k < lines[li].length; k++) {
|
||||
var tok = lines[li][k];
|
||||
ctx.fillStyle = tok.color;
|
||||
ctx.fillText(tok.content, x, y);
|
||||
x += ctx.measureText(tok.content).width;
|
||||
}
|
||||
}
|
||||
return {
|
||||
canvas: canvas,
|
||||
W: W,
|
||||
H: H,
|
||||
lines: lines.length,
|
||||
lineH: lineH,
|
||||
padY: padY,
|
||||
fontPx: fontPx,
|
||||
};
|
||||
}
|
||||
|
||||
function fxDissolve(THREE, scene, camera, renderer, state, st, tl, dur, seed) {
|
||||
scene.background = new THREE.Color(0x05070b);
|
||||
var code = drawCodeToCanvas(state, {
|
||||
fontPx: 40,
|
||||
lineH: 60,
|
||||
padX: 90,
|
||||
padY: 80,
|
||||
weight: 600,
|
||||
});
|
||||
var tex = new THREE.CanvasTexture(code.canvas);
|
||||
tex.minFilter = THREE.LinearFilter;
|
||||
tex.magFilter = THREE.LinearFilter;
|
||||
tex.generateMipmaps = false;
|
||||
|
||||
var aspect = code.W / code.H;
|
||||
var planeH = 6.6;
|
||||
var planeW = planeH * aspect;
|
||||
|
||||
var uniforms = {
|
||||
uTex: { value: tex },
|
||||
uProgress: { value: 0 },
|
||||
uSeed: { value: (seed || 1) * 13.37 },
|
||||
};
|
||||
var mat = new THREE.ShaderMaterial({
|
||||
uniforms: uniforms,
|
||||
transparent: true,
|
||||
vertexShader: [
|
||||
"varying vec2 vUv;",
|
||||
"void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }",
|
||||
].join("\n"),
|
||||
fragmentShader: [
|
||||
"precision highp float;",
|
||||
"uniform sampler2D uTex;",
|
||||
"uniform float uProgress;",
|
||||
"uniform float uSeed;",
|
||||
"varying vec2 vUv;",
|
||||
"float hash(vec2 p){ p=fract(p*vec2(123.34,456.21)); p+=dot(p,p+45.32); return fract(p.x*p.y); }",
|
||||
"float noise(vec2 p){ vec2 i=floor(p),f=fract(p); float a=hash(i),b=hash(i+vec2(1.,0.)),c=hash(i+vec2(0.,1.)),d=hash(i+vec2(1.,1.)); vec2 u=f*f*(3.-2.*f); return mix(mix(a,b,u.x),mix(c,d,u.x),u.y); }",
|
||||
"void main(){",
|
||||
" vec2 uv = vUv;",
|
||||
// multi-octave seeded noise field
|
||||
" float n = noise(uv*16.0+uSeed)*0.6 + noise(uv*48.0+uSeed*1.7)*0.3 + noise(uv*120.0+uSeed*2.3)*0.1;",
|
||||
" float edge = 0.12;",
|
||||
" float reveal = smoothstep(n-edge, n+edge, uProgress);", // 0 hidden -> 1 shown
|
||||
// chromatic fringe strongest at the dissolving edge
|
||||
" float band = reveal*(1.0-reveal)*4.0;",
|
||||
" float off = band*0.010;",
|
||||
" float r = texture2D(uTex, uv+vec2(off,0.0)).r;",
|
||||
" float g = texture2D(uTex, uv).g;",
|
||||
" float b = texture2D(uTex, uv-vec2(off,0.0)).b;",
|
||||
" vec3 codeCol = vec3(r,g,b);",
|
||||
// additive cyan edge glow along the dissolve front
|
||||
" vec3 glow = vec3(0.30,0.72,1.0) * band * 0.9;",
|
||||
" vec3 outc = codeCol*reveal + glow;",
|
||||
" float a = max(reveal, band*0.8);",
|
||||
" gl_FragColor = vec4(outc, a);",
|
||||
"}",
|
||||
].join("\n"),
|
||||
});
|
||||
|
||||
var plane = new THREE.Mesh(new THREE.PlaneGeometry(planeW, planeH), mat);
|
||||
scene.add(plane);
|
||||
|
||||
camera.position.set(0, 0, 10);
|
||||
var vfov = (camera.fov * Math.PI) / 180;
|
||||
var screenAspect = 1920 / 1080;
|
||||
var fitH = planeH / 2 / Math.tan(vfov / 2) / 0.82;
|
||||
var fitW = planeW / 2 / (Math.tan(vfov / 2) * screenAspect) / 0.82;
|
||||
camera.position.z = Math.max(fitH, fitW);
|
||||
camera.lookAt(0, 0, 0);
|
||||
|
||||
// compile in, then hold crisp. Overshoot to 1.15 so even max-noise pixels
|
||||
// (n up to ~1.0, edge 0.12) fully resolve — no leftover specks at rest.
|
||||
tl.to(uniforms.uProgress, { value: 1.15, duration: dur * 0.62, ease: "power1.inOut" }, 0);
|
||||
|
||||
// uProgress is tweened by GSAP; the boot loop's render() draws once per frame.
|
||||
return function () {};
|
||||
}
|
||||
|
||||
// ---- dispatch + boot ------------------------------------------------------
|
||||
function buildEffect(THREE, scene, camera, renderer, spec, st, tl) {
|
||||
var state = T[spec.seq].states[0];
|
||||
var dur = spec.duration;
|
||||
if (spec.effect === "dissolve")
|
||||
return fxDissolve(THREE, scene, camera, renderer, state, st, tl, dur, spec.seed);
|
||||
return function () {};
|
||||
}
|
||||
|
||||
// Free GPU resources (renderer, geometries, materials, textures) on page teardown.
|
||||
// One-shot renders exit per job so nothing leaks there; this guards long-lived
|
||||
// Studio/player sessions that reload. Fires only on unload — never during capture.
|
||||
function disposeGL(renderer, scene) {
|
||||
scene.traverse(function (o) {
|
||||
if (o.geometry && o.geometry.dispose) o.geometry.dispose();
|
||||
var mats = o.material ? (Array.isArray(o.material) ? o.material : [o.material]) : [];
|
||||
mats.forEach(function (m) {
|
||||
if (m.map && m.map.dispose) m.map.dispose();
|
||||
if (
|
||||
m.uniforms &&
|
||||
m.uniforms.uTex &&
|
||||
m.uniforms.uTex.value &&
|
||||
m.uniforms.uTex.value.dispose
|
||||
) {
|
||||
m.uniforms.uTex.value.dispose();
|
||||
}
|
||||
if (m.dispose) m.dispose();
|
||||
});
|
||||
});
|
||||
if (renderer.dispose) renderer.dispose();
|
||||
}
|
||||
|
||||
function go() {
|
||||
var spec = window.__BLOCK;
|
||||
var root = document.getElementById("root");
|
||||
var glcanvas = document.getElementById("gl");
|
||||
var W = 1920;
|
||||
var H = 1080;
|
||||
window.__timelines = window.__timelines || {};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.__timelines[spec.id] = tl; // register synchronously
|
||||
|
||||
var THREE = window.THREE;
|
||||
var renderer = new THREE.WebGLRenderer({
|
||||
canvas: glcanvas,
|
||||
antialias: true,
|
||||
alpha: false,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
renderer.setPixelRatio(1);
|
||||
renderer.setSize(W, H, false);
|
||||
var scene = new THREE.Scene();
|
||||
var camera = new THREE.PerspectiveCamera(45, W / H, 0.1, 200);
|
||||
var st = {};
|
||||
|
||||
document.fonts.ready.then(function () {
|
||||
var renderFn = buildEffect(THREE, scene, camera, renderer, spec, st, tl);
|
||||
function render() {
|
||||
renderFn();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
tl.eventCallback("onUpdate", render);
|
||||
var dur = parseFloat(root.dataset.duration) || tl.duration();
|
||||
tl.to({}, { duration: dur }, 0); // pad to full composition length
|
||||
render(); // paint frame 0
|
||||
window.addEventListener("beforeunload", function () {
|
||||
disposeGL(renderer, scene);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go);
|
||||
else go();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-shader-dissolve",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Shader Dissolve",
|
||||
"description": "The code compiles into existence: a GPU fragment shader resolves it out of seeded noise with a chromatic dissolve front and edge glow, then holds crisp.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 7,
|
||||
"tags": ["code", "code-animation", "shader", "webgl", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-shader-dissolve.html",
|
||||
"target": "compositions/code-shader-dissolve.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-shader-dissolve.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-shader-dissolve.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Basic</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #ececec;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #e8e8e8 0%, #d8d8d8 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.25),
|
||||
0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #ececec;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
gap: 0;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #d0d0d0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #333333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #ffffff;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #000000;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-basic"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line">Last login: Mon Jun 2 09:14:22 on ttys001</span>
|
||||
<span class="output-line">total 48</span>
|
||||
<span class="output-line bold">drwxr-xr-x 8 user staff 256 Jun 2 09:10 .</span>
|
||||
<span class="output-line">drwxr-xr-x 42 user staff 1344 Jun 1 18:22 ..</span>
|
||||
<span class="output-line">-rw-r--r-- 1 user staff 872 May 30 14:05 README.md</span>
|
||||
<span class="output-line bold">drwxr-xr-x 5 user staff 160 Jun 2 09:10 dist</span>
|
||||
<span class="output-line bold"
|
||||
>drwxr-xr-x 12 user staff 384 Jun 2 09:10 node_modules</span
|
||||
>
|
||||
<span class="output-line">-rw-r--r-- 1 user staff 1204 Jun 2 09:08 package.json</span>
|
||||
<span class="output-line bold">drwxr-xr-x 6 user staff 192 Jun 2 09:10 src</span>
|
||||
<span class="output-line">-rw-r--r-- 1 user staff 348 May 29 11:42 tsconfig.json</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "ls -la";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Phase 1 (0–0.5s): hold on empty prompt
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
// Phase 2 (0.5–2s): type command char by char
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
// Phase 3 (2.2s): hide cursor, show first output line (login message)
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
// Phase 4 (2.5s): clear command, show ls output line by line
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return; // already shown
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.08);
|
||||
});
|
||||
|
||||
// Phase 5 (4s): new prompt with blinking cursor
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.0);
|
||||
|
||||
// Phase 6 (4.2–10s): blink cursor 3 times then stop
|
||||
const blinkTimes = [4.2, 4.6, 5.0, 5.4, 5.8, 6.2];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.6);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-basic"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-basic",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Basic",
|
||||
"description": "Apple Terminal Basic profile: white background, black text, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-basic.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-basic.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-basic.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-basic.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Clear Dark</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #1a1a1a;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #111111 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.7),
|
||||
0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.dim {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #888888;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #888888;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-clear-dark"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line dim">Last login: Mon Jun 2 09:14:22 on ttys002</span>
|
||||
<span class="output-line">Scanning dependencies...</span>
|
||||
<span class="output-line">Found 3 vulnerabilities (1 moderate, 2 low)</span>
|
||||
<span class="output-line dim"> package: lodash@4.17.20</span>
|
||||
<span class="output-line dim"> severity: moderate</span>
|
||||
<span class="output-line dim"> fix: lodash@4.17.21</span>
|
||||
<span class="output-line dim"> package: minimist@1.2.5</span>
|
||||
<span class="output-line dim"> severity: low</span>
|
||||
<span class="output-line dim"> fix: minimist@1.2.6</span>
|
||||
<span class="output-line bold">Run `npm audit fix` to fix them.</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "npm audit";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.2);
|
||||
|
||||
const blinkTimes = [4.4, 4.8, 5.2, 5.6, 6.0, 6.4];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.8);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-clear-dark"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-clear-dark",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Clear Dark",
|
||||
"description": "Apple Terminal Clear Dark profile: semi-transparent dark background, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-clear-dark.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-clear-dark.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-clear-dark.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-clear-dark.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Clear Light</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.18),
|
||||
0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
position: relative;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #333333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #000000;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.output-line.muted {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #888888;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #888888;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-clear-light"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line muted">Last login: Mon Jun 2 09:14:22 on ttys003</span>
|
||||
<span class="output-line bold">git log --oneline -8</span>
|
||||
<span class="output-line">a3f2c19 fix: correct off-by-one in pagination</span>
|
||||
<span class="output-line">b7e8d04 feat: add dark mode toggle</span>
|
||||
<span class="output-line">c1a9f55 refactor: extract auth helpers</span>
|
||||
<span class="output-line">d4e2b31 test: add unit tests for parser</span>
|
||||
<span class="output-line">e6c8a72 docs: update API reference</span>
|
||||
<span class="output-line">f9b1d44 chore: bump dependencies</span>
|
||||
<span class="output-line">2a7e890 feat: implement search endpoint</span>
|
||||
<span class="output-line">3c5f012 init: project bootstrap</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac myproject % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "git log --oneline -8";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.09);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac myproject % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.0);
|
||||
|
||||
const blinkTimes = [4.2, 4.6, 5.0, 5.4, 5.8, 6.2];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.6);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-clear-light"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-clear-light",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Clear Light",
|
||||
"description": "Apple Terminal Clear Light profile: semi-transparent light background, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-clear-light.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-clear-light.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-clear-light.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-clear-light.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Grass</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #000000;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(ellipse at center, #0a1a0a 0%, #000000 70%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 193, 0, 0.15),
|
||||
0 10px 30px rgba(0, 0, 0, 0.8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #1c1c1c;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #00c100;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #00c100;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #000000;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #00c100;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #00c100;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #00c100;
|
||||
}
|
||||
|
||||
.output-line.dim {
|
||||
color: #006600;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #00c100;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #00c100;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #00c100;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-grass"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line dim">Last login: Mon Jun 2 09:14:22 on ttys004</span>
|
||||
<span class="output-line">Running test suite...</span>
|
||||
<span class="output-line dim"> ✓ should initialize with defaults (2ms)</span>
|
||||
<span class="output-line dim"> ✓ should parse config correctly (4ms)</span>
|
||||
<span class="output-line dim"> ✓ should handle empty input (1ms)</span>
|
||||
<span class="output-line dim"> ✓ should reject invalid tokens (3ms)</span>
|
||||
<span class="output-line dim"> ✓ should emit events on change (5ms)</span>
|
||||
<span class="output-line dim"> ✓ should clean up on destroy (2ms)</span>
|
||||
<span class="output-line bold"> 6 passing (18ms)</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "npm test";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.12);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.0);
|
||||
|
||||
const blinkTimes = [4.2, 4.6, 5.0, 5.4, 5.8, 6.2];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.6);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-grass"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-grass",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Grass",
|
||||
"description": "Apple Terminal Grass profile: black background with green text, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-grass.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-grass.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-grass.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-grass.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Homebrew</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #000000;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(ellipse at center, #0d1a0d 0%, #000000 70%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 207, 0, 0.18),
|
||||
0 10px 30px rgba(0, 0, 0, 0.85);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #1c1c1c;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #006900;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #00cf00;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #000000;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #00cf00;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #00cf00;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #00de00;
|
||||
}
|
||||
|
||||
.output-line.dim {
|
||||
color: #006600;
|
||||
}
|
||||
|
||||
.output-line.bright {
|
||||
color: #48f800;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #48f800;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #00cf00;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #48f800;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-homebrew"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line dim">Last login: Mon Jun 2 09:14:22 on ttys005</span>
|
||||
<span class="output-line">==> Updating Homebrew...</span>
|
||||
<span class="output-line dim">Already up-to-date.</span>
|
||||
<span class="output-line">==> Outdated formulae</span>
|
||||
<span class="output-line">node 20.11.0 -> 22.3.0</span>
|
||||
<span class="output-line">python 3.11.6 -> 3.12.4</span>
|
||||
<span class="output-line">git 2.43.0 -> 2.45.2</span>
|
||||
<span class="output-line">ffmpeg 6.1.1 -> 7.0.0</span>
|
||||
<span class="output-line bright">==> Run `brew upgrade` to update 4 formulae.</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "brew outdated";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.2);
|
||||
|
||||
const blinkTimes = [4.4, 4.8, 5.2, 5.6, 6.0, 6.4];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.8);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-homebrew"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-homebrew",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Homebrew",
|
||||
"description": "Apple Terminal Homebrew profile: black background with bright green text and lime cursor, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-homebrew.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-homebrew.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-homebrew.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-homebrew.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Man Page</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #ede87a 0%, #ddd566 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.2),
|
||||
0 10px 30px rgba(0, 0, 0, 0.12);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #f0e680;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #c8be40;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #333333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #fef49c;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.output-line.indent {
|
||||
padding-left: 24px;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.output-line.section {
|
||||
font-weight: bold;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #000000;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-man-page"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">man — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line section">CURL(1) General Commands Manual CURL(1)</span>
|
||||
<span class="output-line"></span>
|
||||
<span class="output-line section">NAME</span>
|
||||
<span class="output-line indent">curl -- transfer a URL</span>
|
||||
<span class="output-line"></span>
|
||||
<span class="output-line section">SYNOPSIS</span>
|
||||
<span class="output-line indent">curl [options / URLs]</span>
|
||||
<span class="output-line"></span>
|
||||
<span class="output-line section">DESCRIPTION</span>
|
||||
<span class="output-line indent"
|
||||
>curl is a tool for transferring data from or to a server using URLs.</span
|
||||
>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "man curl";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.4);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
tl.set(line, { opacity: 1 }, 2.5 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.0);
|
||||
|
||||
const blinkTimes = [4.2, 4.6, 5.0, 5.4, 5.8, 6.2];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.6);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-man-page"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-man-page",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Man Page",
|
||||
"description": "Apple Terminal Man Page profile: pale yellow background with black text, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-man-page.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-man-page.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-man-page.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-man-page.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Novel</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #c9a86c 0%, #b8955a 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(62, 41, 0, 0.3),
|
||||
0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #c9a86c;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #a88040;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #3e2900;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #dfc18a;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #3e2900;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #3e2900;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #3e2900;
|
||||
}
|
||||
|
||||
.output-line.muted {
|
||||
color: #7a5a20;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #3e2900;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #3e2900;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #3e2900;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-novel"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line muted">Last login: Mon Jun 2 09:14:22 on ttys006</span>
|
||||
<span class="output-line muted">Python 3.12.4 (main, Jun 6 2024, 18:26:44)</span>
|
||||
<span class="output-line muted"
|
||||
>Type "help", "copyright", "credits" or "license" for more information.</span
|
||||
>
|
||||
<span class="output-line bold">>>> import hyperframes</span>
|
||||
<span class="output-line bold">>>> hf = hyperframes.Composition("intro.html")</span>
|
||||
<span class="output-line">>>> hf.duration</span>
|
||||
<span class="output-line">12.0</span>
|
||||
<span class="output-line">>>> hf.dimensions</span>
|
||||
<span class="output-line">Dimensions(width=1920, height=1080)</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "python3";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.2);
|
||||
|
||||
const blinkTimes = [4.4, 4.8, 5.2, 5.6, 6.0, 6.4];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.8);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-novel"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-novel",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Novel",
|
||||
"description": "Apple Terminal Novel profile: warm parchment background with dark brown text, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-novel.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-novel.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-novel.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-novel.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Ocean</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #1a3f9e;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(ellipse at center, #224fbe 0%, #0e2878 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.6),
|
||||
0 10px 30px rgba(34, 79, 190, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #1a3f9e;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #224fbe;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.dim {
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
.output-line.success {
|
||||
color: #7fffb3;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #ffffff;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-ocean"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line dim">Last login: Mon Jun 2 09:14:22 on ttys007</span>
|
||||
<span class="output-line bold">docker ps</span>
|
||||
<span class="output-line dim">CONTAINER ID IMAGE COMMAND CREATED</span>
|
||||
<span class="output-line"
|
||||
>a1b2c3d4e5f6 nginx:latest "/docker-entrypoint.…" 2 hours ago</span
|
||||
>
|
||||
<span class="output-line"
|
||||
>b2c3d4e5f6a7 postgres:15 "docker-entrypoint.s…" 2 hours ago</span
|
||||
>
|
||||
<span class="output-line"
|
||||
>c3d4e5f6a7b8 redis:alpine "docker-entrypoint.s…" 2 hours ago</span
|
||||
>
|
||||
<span class="output-line success">3 containers running</span>
|
||||
<span class="output-line dim">Use 'docker logs CONTAINER' to view output</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "docker ps";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.2);
|
||||
|
||||
const blinkTimes = [4.4, 4.8, 5.2, 5.6, 6.0, 6.4];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.8);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-ocean"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-ocean",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Ocean",
|
||||
"description": "Apple Terminal Ocean profile: deep blue background with white text, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-ocean.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-ocean.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-ocean.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-ocean.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Pro</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #000000;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(ellipse at center, #141414 0%, #000000 70%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(136, 255, 103, 0.08),
|
||||
0 10px 30px rgba(0, 0, 0, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #1c1c1c;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #333333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #bbbbbb;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #000000;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #bbbbbb;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #bbbbbb;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.accent {
|
||||
color: #88ff67;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.output-line.dim {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #88ff67;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #88ff67;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-pro"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line dim">Last login: Mon Jun 2 09:14:22 on ttys008</span>
|
||||
<span class="output-line dim">Building production bundle...</span>
|
||||
<span class="output-line"> vite v5.3.1 building for production...</span>
|
||||
<span class="output-line"> ✓ 142 modules transformed.</span>
|
||||
<span class="output-line"> dist/index.html 0.48 kB</span>
|
||||
<span class="output-line"> dist/assets/index-Cv3rN.js 284.12 kB</span>
|
||||
<span class="output-line"> dist/assets/index-Du4mP.css 42.08 kB</span>
|
||||
<span class="output-line accent"> ✓ built in 2.31s</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac myapp % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "npm run build";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac myapp % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.2);
|
||||
|
||||
const blinkTimes = [4.4, 4.8, 5.2, 5.6, 6.0, 6.4];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.8);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-pro"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-pro",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Pro",
|
||||
"description": "Apple Terminal Pro profile: black background with grey text and lime green cursor, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-pro.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-pro.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-pro.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-pro.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Red Sands</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #7b140e;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(ellipse at center, #8a1a12 0%, #4a0a06 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.7),
|
||||
0 10px 30px rgba(123, 20, 14, 0.4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #5e0f0a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #8b2018;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #ffe1b9;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #7b140e;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #ffe1b9;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #ffe1b9;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.dim {
|
||||
color: #c4916a;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #ffe1b9;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #ffe1b9;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #ffffff;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-red-sands"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line dim">Last login: Mon Jun 2 09:14:22 on ttys009</span>
|
||||
<span class="output-line">Fetching https://api.example.com/status</span>
|
||||
<span class="output-line dim"> HTTP/2 200</span>
|
||||
<span class="output-line dim"> content-type: application/json</span>
|
||||
<span class="output-line dim"> x-request-id: a3f9c21e-04b2-4d78</span>
|
||||
<span class="output-line">{</span>
|
||||
<span class="output-line"> "status": "healthy",</span>
|
||||
<span class="output-line"> "version": "2.4.1",</span>
|
||||
<span class="output-line bold"> "uptime": "14d 6h 32m"</span>
|
||||
<span class="output-line">}</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "curl -s api.example.com/status | jq";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.4);
|
||||
|
||||
const blinkTimes = [4.6, 5.0, 5.4, 5.8, 6.2, 6.6];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 7.0);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-red-sands"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-red-sands",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Red Sands",
|
||||
"description": "Apple Terminal Red Sands profile: deep red background with sandy text, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-red-sands.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-red-sands.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-red-sands.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-red-sands.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Silver Aerogel</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #2a2a2a;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(ellipse at center, #3a3a3a 0%, #1c1c1c 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.6),
|
||||
0 10px 30px rgba(232, 232, 232, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #2a2a2a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #444444;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #3d3d3d;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.dim {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
|
||||
.output-line.success {
|
||||
color: #7fffb3;
|
||||
}
|
||||
|
||||
.output-line.warn {
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #e8e8e8;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #e8e8e8;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-silver-aerogel"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line dim">Last login: Mon Jun 2 09:14:22 on ttys010</span>
|
||||
<span class="output-line">Checking system resources...</span>
|
||||
<span class="output-line">Filesystem Size Used Avail Use%</span>
|
||||
<span class="output-line">/dev/disk3s1s1 460G 142G 302G 32%</span>
|
||||
<span class="output-line dim">/dev/disk3s6 460G 5.0G 302G 2%</span>
|
||||
<span class="output-line">/dev/disk3s2 460G 11G 302G 4%</span>
|
||||
<span class="output-line warn">Memory: 14.2 GB / 16.0 GB used (89%)</span>
|
||||
<span class="output-line success">CPU: 8-core, 12% load average</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "df -h && vm_stat";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.2);
|
||||
|
||||
const blinkTimes = [4.4, 4.8, 5.2, 5.6, 6.0, 6.4];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.8);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-silver-aerogel"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-silver-aerogel",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Silver Aerogel",
|
||||
"description": "Apple Terminal Silver Aerogel profile: dark grey background with white text, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-silver-aerogel.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-silver-aerogel.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-silver-aerogel.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-silver-aerogel.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Apple Terminal Solid Colors</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #4c0099;
|
||||
font-family: "SF Mono", Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
#scene {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: radial-gradient(ellipse at center, #5a00b3 0%, #2e0060 100%);
|
||||
}
|
||||
|
||||
.terminal-window {
|
||||
width: 1400px;
|
||||
height: 700px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 30px 80px rgba(0, 0, 0, 0.7),
|
||||
0 10px 30px rgba(76, 0, 153, 0.4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
height: 38px;
|
||||
background: #3a0077;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid #6600bb;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.traffic-lights {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traffic-lights span {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.traffic-lights .close {
|
||||
background: #ff5f57;
|
||||
border: 1px solid #e0443e;
|
||||
}
|
||||
|
||||
.traffic-lights .minimize {
|
||||
background: #ffbd2e;
|
||||
border: 1px solid #dfa123;
|
||||
}
|
||||
|
||||
.traffic-lights .fullscreen {
|
||||
background: #28c840;
|
||||
border: 1px solid #1aab29;
|
||||
}
|
||||
|
||||
.window-title {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 13px;
|
||||
color: #ffffff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
|
||||
.terminal-canvas {
|
||||
flex: 1;
|
||||
background: #4c0099;
|
||||
padding: 18px 20px;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-lines {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.output-line {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
opacity: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.bold {
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.output-line.dim {
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
.output-line.accent {
|
||||
color: #cc99ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #cc99ff;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.typed-command {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 16px;
|
||||
background: #ffffff;
|
||||
vertical-align: text-bottom;
|
||||
margin-left: 1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="scene"
|
||||
data-composition-id="code-snippet-apple-terminal-solid-colors"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="terminal-window">
|
||||
<div class="titlebar">
|
||||
<div class="traffic-lights">
|
||||
<span class="close"></span>
|
||||
<span class="minimize"></span>
|
||||
<span class="fullscreen"></span>
|
||||
</div>
|
||||
<span class="window-title">bash — 80×24</span>
|
||||
</div>
|
||||
<div class="terminal-canvas">
|
||||
<div class="output-lines">
|
||||
<span class="output-line dim">Last login: Mon Jun 2 09:14:22 on ttys011</span>
|
||||
<span class="output-line">Initializing HyperFrames project...</span>
|
||||
<span class="output-line dim"> Creating project structure</span>
|
||||
<span class="output-line dim"> Copying template files</span>
|
||||
<span class="output-line dim"> Installing dependencies</span>
|
||||
<span class="output-line"> added 142 packages in 3.4s</span>
|
||||
<span class="output-line accent"> ✓ Project ready at ./my-video</span>
|
||||
<span class="output-line bold"> Run: cd my-video && npx hyperframes preview</span>
|
||||
</div>
|
||||
<div class="input-line">
|
||||
<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span
|
||||
><span class="cursor-block"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
const command = "npx hyperframes init my-video";
|
||||
const typedEl = document.querySelector(".typed-command");
|
||||
const cursorEl = document.querySelector(".cursor-block");
|
||||
const outputLines = document.querySelectorAll(".output-line");
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
tl.set(typedEl, { text: "" }, 0);
|
||||
|
||||
const charDuration = 1.5 / command.length;
|
||||
command.split("").forEach((char, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
typedEl.textContent = command.slice(0, i + 1);
|
||||
},
|
||||
0.5 + i * charDuration,
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(cursorEl, { opacity: 0 }, 2.2);
|
||||
tl.set(outputLines[0], { opacity: 1 }, 2.3);
|
||||
|
||||
tl.add(() => {
|
||||
typedEl.textContent = "";
|
||||
}, 2.5);
|
||||
|
||||
outputLines.forEach((line, i) => {
|
||||
if (i === 0) return;
|
||||
tl.set(line, { opacity: 1 }, 2.8 + i * 0.1);
|
||||
});
|
||||
|
||||
tl.add(() => {
|
||||
const inputLine = document.querySelector(".input-line");
|
||||
const newPrompt = document.createElement("div");
|
||||
newPrompt.className = "input-line";
|
||||
newPrompt.innerHTML =
|
||||
'<span class="prompt">user@Mac ~ % </span><span class="typed-command"></span><span class="cursor-block" id="final-cursor"></span>';
|
||||
inputLine.style.display = "none";
|
||||
inputLine.parentNode.appendChild(newPrompt);
|
||||
}, 4.2);
|
||||
|
||||
const blinkTimes = [4.4, 4.8, 5.2, 5.6, 6.0, 6.4];
|
||||
blinkTimes.forEach((t, i) => {
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = i % 2 === 0 ? "0" : "1";
|
||||
}, t);
|
||||
});
|
||||
tl.add(() => {
|
||||
const fc = document.getElementById("final-cursor");
|
||||
if (fc) fc.style.opacity = "1";
|
||||
}, 6.8);
|
||||
|
||||
window.__timelines["code-snippet-apple-terminal-solid-colors"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-apple-terminal-solid-colors",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Apple Terminal Solid Colors",
|
||||
"description": "Apple Terminal Solid Colors profile: deep purple background with white text, per-character typing animation of a shell session.",
|
||||
"tags": ["code", "developer", "showcase", "terminal", "apple", "apple-terminal"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 12,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-apple-terminal-solid-colors.html",
|
||||
"target": "compositions/code-snippet-apple-terminal-solid-colors.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-solid-colors.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-apple-terminal-solid-colors.png"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-dark-2026",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Dark 2026",
|
||||
"description": "VS Code workbench with per-character typing animation in the Dark 2026 theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-dark-2026.html",
|
||||
"target": "compositions/code-snippet-dark-2026.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-dark-modern",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Dark Modern",
|
||||
"description": "VS Code workbench with per-character typing animation in the Dark Modern theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-dark-modern.html",
|
||||
"target": "compositions/code-snippet-dark-modern.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-dark-plus",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Dark+",
|
||||
"description": "VS Code workbench with per-character typing animation in the Dark+ theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-dark-plus.html",
|
||||
"target": "compositions/code-snippet-dark-plus.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,665 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Code Snippet Flight</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #05070b;
|
||||
}
|
||||
#root {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
color: #e6edf3;
|
||||
}
|
||||
.bg-field {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(1200px 700px at 50% 18%, #0e1726 0%, #070b12 55%, #05070b 100%);
|
||||
}
|
||||
.bg-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(88, 166, 255, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(88, 166, 255, 0.05) 1px, transparent 1px);
|
||||
background-size: 64px 64px;
|
||||
mask-image: radial-gradient(1100px 620px at 50% 42%, #000 0%, transparent 78%);
|
||||
}
|
||||
.bg-glow {
|
||||
position: absolute;
|
||||
width: 900px;
|
||||
height: 900px;
|
||||
border-radius: 50%;
|
||||
filter: blur(120px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bg-glow.a {
|
||||
background: #1f6feb55;
|
||||
left: -180px;
|
||||
top: -200px;
|
||||
}
|
||||
.bg-glow.b {
|
||||
background: #2ea04355;
|
||||
right: -200px;
|
||||
bottom: -260px;
|
||||
}
|
||||
.editor {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1380px;
|
||||
height: 800px;
|
||||
background: #0b0f17;
|
||||
border: 1px solid #1d2733;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 40px 120px rgba(0, 0, 0, 0.6),
|
||||
0 2px 0 rgba(255, 255, 255, 0.03) inset;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.titlebar {
|
||||
height: 56px;
|
||||
flex: 0 0 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 0 22px;
|
||||
background: linear-gradient(#11161f, #0c111a);
|
||||
border-bottom: 1px solid #1b2430;
|
||||
}
|
||||
.dots {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.r {
|
||||
background: #ff5f57;
|
||||
}
|
||||
.dot.y {
|
||||
background: #febc2e;
|
||||
}
|
||||
.dot.g {
|
||||
background: #28c840;
|
||||
}
|
||||
.filename {
|
||||
font-size: 19px;
|
||||
color: #8b98a9;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.filename .accent {
|
||||
color: #d6e2f0;
|
||||
}
|
||||
.code-surface {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.scene {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
.code {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0 44px 0 104px;
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
font-variant-ligatures: none;
|
||||
tab-size: 2;
|
||||
}
|
||||
.gutter {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
width: 84px;
|
||||
padding-right: 18px;
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
text-align: right;
|
||||
color: #828c9b;
|
||||
user-select: none;
|
||||
}
|
||||
.ln {
|
||||
display: block;
|
||||
height: 46px;
|
||||
}
|
||||
.line {
|
||||
display: block;
|
||||
min-height: 46px;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.tok {
|
||||
display: inline-block;
|
||||
white-space: pre;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.caret {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
height: 32px;
|
||||
margin-top: 6px;
|
||||
background: #58a6ff;
|
||||
border-radius: 1px;
|
||||
z-index: 3;
|
||||
}
|
||||
.hl-box {
|
||||
position: absolute;
|
||||
background: rgba(88, 166, 255, 0.16);
|
||||
border-left: 3px solid #58a6ff;
|
||||
border-radius: 6px;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.diff-line {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
line-height: 46px;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-left: 14px;
|
||||
}
|
||||
.diff-sign {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
color: #41506220;
|
||||
}
|
||||
.diff-del {
|
||||
background: rgba(248, 81, 73, 0.12);
|
||||
box-shadow: inset 3px 0 #f85149;
|
||||
}
|
||||
.diff-add {
|
||||
background: rgba(63, 185, 80, 0.12);
|
||||
box-shadow: inset 3px 0 #3fb950;
|
||||
}
|
||||
.diff-del .diff-sign {
|
||||
color: #f85149;
|
||||
}
|
||||
.diff-add .diff-sign {
|
||||
color: #3fb950;
|
||||
}
|
||||
.flight-block {
|
||||
position: absolute;
|
||||
white-space: pre;
|
||||
font-size: 27px;
|
||||
line-height: 40px;
|
||||
padding: 11px 22px;
|
||||
border-radius: 11px;
|
||||
background: #0f1722;
|
||||
border: 1px solid #233044;
|
||||
box-shadow: 0 16px 44px rgba(0, 0, 0, 0.5);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.flight-target {
|
||||
position: absolute;
|
||||
border: 1.5px dashed #2a3a4f;
|
||||
border-radius: 14px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="code-snippet-flight"
|
||||
data-start="0"
|
||||
data-duration="6"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="bg-field" data-layout-ignore></div>
|
||||
<div class="bg-grid" data-layout-ignore></div>
|
||||
<div class="bg-glow a" data-layout-ignore></div>
|
||||
<div class="bg-glow b" data-layout-ignore></div>
|
||||
<div class="editor" id="editor" data-layout-ignore>
|
||||
<div class="titlebar">
|
||||
<div class="dots">
|
||||
<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
|
||||
</div>
|
||||
<span class="filename"
|
||||
><span class="accent">pipeline.js</span> — Code Snippet Flight</span
|
||||
>
|
||||
</div>
|
||||
<div class="code-surface" id="surface"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.__TOKENS = {
|
||||
flight: {
|
||||
lang: "js",
|
||||
theme: "github-dark",
|
||||
bg: "#24292e",
|
||||
fg: "#e1e4e8",
|
||||
states: [
|
||||
{
|
||||
code: "const x = parse(input)",
|
||||
tokens: [
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-0",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-2",
|
||||
content: "x",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-3",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-4",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-5",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-6",
|
||||
content: "parse",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-7",
|
||||
content: "(input)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-8",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "const y = transform(x)",
|
||||
tokens: [
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-0",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "ulDeJnFQa2Vy-61y5zTTA7sNfq7d4aUawNMAxQ6sVgg-2",
|
||||
content: "y",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-3",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-4",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-5",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "ulDeJnFQa2Vy-61y5zTTA7sNfq7d4aUawNMAxQ6sVgg-6",
|
||||
content: "transform",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "ulDeJnFQa2Vy-61y5zTTA7sNfq7d4aUawNMAxQ6sVgg-7",
|
||||
content: "(x)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "ulDeJnFQa2Vy-61y5zTTA7sNfq7d4aUawNMAxQ6sVgg-8",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "const z = render(y)",
|
||||
tokens: [
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-0",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "wFuhzZNydG-3inU2s1h1gU8Kivya8CAP9gl9S4MBQj4-2",
|
||||
content: "z",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-3",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-4",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "qABRRXThYBzvlFJqemsh1aauYJqWMIPHhxkj-uoxex4-5",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "wFuhzZNydG-3inU2s1h1gU8Kivya8CAP9gl9S4MBQj4-6",
|
||||
content: "render",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "wFuhzZNydG-3inU2s1h1gU8Kivya8CAP9gl9S4MBQj4-7",
|
||||
content: "(y)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "wFuhzZNydG-3inU2s1h1gU8Kivya8CAP9gl9S4MBQj4-8",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
window.__BLOCK = { id: "code-snippet-flight", effect: "flight", seq: "flight", duration: 6 };
|
||||
</script>
|
||||
<script>
|
||||
// hf-code-anim.js — self-contained code-animation engine for HyperFrames registry blocks.
|
||||
//
|
||||
// Reuses Shiki Magic Move's baked output (window.__TOKENS, baked at author time) and
|
||||
// drives the animation as a PAUSED GSAP timeline the HyperFrames engine seeks per frame —
|
||||
// NOT CSS transitions (those break under non-sequential frame seeking). FLIP measurement
|
||||
// runs once inside document.fonts.ready (glyph metrics must be final). Each block sets
|
||||
// window.__BLOCK = { id, effect, seq, line?, duration } and includes this engine; the
|
||||
// engine builds one scene that fills the composition and registers window.__timelines[id].
|
||||
(function () {
|
||||
"use strict";
|
||||
var T = window.__TOKENS || {};
|
||||
|
||||
function lineDiv() {
|
||||
var d = document.createElement("div");
|
||||
d.className = "line";
|
||||
return d;
|
||||
}
|
||||
|
||||
// Render a code state's keyed tokens into codeEl as lines of inline-block .tok spans.
|
||||
function renderState(codeEl, state) {
|
||||
codeEl.textContent = "";
|
||||
var keyToEl = new Map();
|
||||
var line = lineDiv();
|
||||
codeEl.appendChild(line);
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
line = lineDiv();
|
||||
codeEl.appendChild(line);
|
||||
continue;
|
||||
}
|
||||
var span = document.createElement("span");
|
||||
span.className = "tok";
|
||||
span.textContent = t.content;
|
||||
span.style.color = t.color;
|
||||
if (t.fontStyle & 1) span.style.fontStyle = "italic";
|
||||
if (t.fontStyle & 2) span.style.fontWeight = "700";
|
||||
line.appendChild(span);
|
||||
keyToEl.set(t.key, span);
|
||||
}
|
||||
var last = codeEl.lastChild;
|
||||
if (last && last.className === "line" && last.children.length === 0)
|
||||
codeEl.removeChild(last);
|
||||
return keyToEl;
|
||||
}
|
||||
|
||||
function rectsOf(keyToEl) {
|
||||
var m = new Map();
|
||||
keyToEl.forEach(function (el, k) {
|
||||
var r = el.getBoundingClientRect();
|
||||
m.set(k, { left: r.left, top: r.top });
|
||||
});
|
||||
return m;
|
||||
}
|
||||
|
||||
function makeScene(surface, withGutter) {
|
||||
var scene = document.createElement("div");
|
||||
scene.className = "scene";
|
||||
if (withGutter) {
|
||||
var g = document.createElement("div");
|
||||
g.className = "gutter";
|
||||
scene.appendChild(g);
|
||||
}
|
||||
var code = document.createElement("div");
|
||||
code.className = "code";
|
||||
scene.appendChild(code);
|
||||
surface.appendChild(scene);
|
||||
return { scene: scene, code: code, gutter: scene.querySelector(".gutter") };
|
||||
}
|
||||
|
||||
function fillGutter(g, n) {
|
||||
var html = "";
|
||||
for (var i = 1; i <= n; i++) html += '<span class="ln">' + i + "</span>";
|
||||
g.innerHTML = html;
|
||||
}
|
||||
|
||||
function splitLines(state) {
|
||||
var lines = [{ text: "", tokens: [] }];
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
lines.push({ text: "", tokens: [] });
|
||||
continue;
|
||||
}
|
||||
var cur = lines[lines.length - 1];
|
||||
cur.tokens.push(t);
|
||||
cur.text += t.content;
|
||||
}
|
||||
if (lines.length && lines[lines.length - 1].tokens.length === 0) lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
function lineDiff(a, b) {
|
||||
var n = a.length;
|
||||
var m = b.length;
|
||||
var dp = [];
|
||||
for (var i = 0; i <= n; i++) dp.push(new Array(m + 1).fill(0));
|
||||
for (var i = n - 1; i >= 0; i--) {
|
||||
for (var j = m - 1; j >= 0; j--) {
|
||||
dp[i][j] =
|
||||
a[i].text === b[j].text
|
||||
? dp[i + 1][j + 1] + 1
|
||||
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||
}
|
||||
}
|
||||
var ops = [];
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
while (i < n && j < m) {
|
||||
if (a[i].text === b[j].text) {
|
||||
ops.push({ type: "same", line: b[j] });
|
||||
i++;
|
||||
j++;
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
ops.push({ type: "del", line: a[i] });
|
||||
i++;
|
||||
} else {
|
||||
ops.push({ type: "add", line: b[j] });
|
||||
j++;
|
||||
}
|
||||
}
|
||||
while (i < n) ops.push({ type: "del", line: a[i++] });
|
||||
while (j < m) ops.push({ type: "add", line: b[j++] });
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ---- single-scene effect builders (fade in, play, hold to end) -------------
|
||||
var FADE = 0.45;
|
||||
|
||||
function flight(tl, surface, states, start) {
|
||||
var FLY = 0.95;
|
||||
var STAGGER = 0.26;
|
||||
var GAP = 20;
|
||||
var parts = makeScene(surface, false);
|
||||
parts.code.remove();
|
||||
var scene = parts.scene;
|
||||
var target = document.createElement("div");
|
||||
target.className = "flight-target";
|
||||
scene.appendChild(target);
|
||||
var W = surface.clientWidth;
|
||||
var H = surface.clientHeight;
|
||||
|
||||
var blocks = states.map(function (st) {
|
||||
var blk = document.createElement("div");
|
||||
blk.className = "flight-block";
|
||||
renderState(blk, st);
|
||||
scene.appendChild(blk);
|
||||
return blk;
|
||||
});
|
||||
var n = blocks.length;
|
||||
|
||||
// Pass 1: measure natural size, then make every chip the SAME width (symmetry).
|
||||
var maxW = 0;
|
||||
var chipH = 0;
|
||||
blocks.forEach(function (blk) {
|
||||
var r = blk.getBoundingClientRect();
|
||||
if (r.width > maxW) maxW = r.width;
|
||||
if (r.height > chipH) chipH = r.height;
|
||||
});
|
||||
blocks.forEach(function (blk) {
|
||||
blk.style.width = maxW + "px";
|
||||
});
|
||||
|
||||
// Pass 2: evenly-stacked, left-aligned, group centered in the surface.
|
||||
var stackH = n * chipH + (n - 1) * GAP;
|
||||
var destLeft = Math.round((W - maxW) / 2);
|
||||
var topY = Math.round((H - stackH) / 2);
|
||||
blocks.forEach(function (blk, i) {
|
||||
var destY = topY + i * (chipH + GAP);
|
||||
blk.style.left = destLeft + "px";
|
||||
blk.style.top = destY + "px";
|
||||
// fly straight in from the left to the shared left edge — clean, no skew
|
||||
var srcX = destLeft - W * 0.32;
|
||||
tl.fromTo(
|
||||
blk,
|
||||
{ x: srcX - destLeft, opacity: 0, scale: 0.96 },
|
||||
{ x: 0, opacity: 1, scale: 1, duration: FLY, ease: "power3.out" },
|
||||
start + FADE + i * STAGGER,
|
||||
);
|
||||
});
|
||||
|
||||
// Even-padded panel tightly wrapping the uniform stack.
|
||||
var padX = 30;
|
||||
var padY = 24;
|
||||
target.style.left = destLeft - padX + "px";
|
||||
target.style.top = topY - padY + "px";
|
||||
target.style.width = maxW + padX * 2 + "px";
|
||||
target.style.height = stackH + padY * 2 + "px";
|
||||
var landAt = start + FADE + (n - 1) * STAGGER + FLY;
|
||||
tl.set(scene, { opacity: 1 }, 0);
|
||||
tl.to(target, { opacity: 1, duration: 0.5, ease: "power1.out" }, landAt - 0.5);
|
||||
}
|
||||
|
||||
function buildEffect(tl, surface, spec) {
|
||||
var start = 0.45;
|
||||
var seq = T[spec.seq];
|
||||
if (spec.effect === "flight") flight(tl, surface, seq.states, start);
|
||||
}
|
||||
|
||||
function go() {
|
||||
var spec = window.__BLOCK;
|
||||
var surface = document.getElementById("surface");
|
||||
var root = document.getElementById("root");
|
||||
window.__timelines = window.__timelines || {};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.__timelines["code-snippet-flight"] = tl; // register synchronously
|
||||
document.fonts.ready.then(function () {
|
||||
tl.from("#editor", { opacity: 0, scale: 0.985, duration: 0.5, ease: "power2.out" }, 0);
|
||||
buildEffect(tl, surface, spec);
|
||||
var dur = parseFloat(root.dataset.duration) || tl.duration();
|
||||
tl.to({}, { duration: dur }, 0); // pad to full composition length
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go);
|
||||
else go();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-flight",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet Flight",
|
||||
"description": "Discrete code snippets fly in from the side and assemble into a stacked program, staggered. Block-level FLIP.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 6,
|
||||
"tags": ["code", "code-animation", "flight", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-flight.html",
|
||||
"target": "compositions/code-snippet-flight.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-flight.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-snippet-flight.png"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-high-contrast-light",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - High Contrast Light",
|
||||
"description": "VS Code workbench with per-character typing animation in the High Contrast Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-high-contrast-light.html",
|
||||
"target": "compositions/code-snippet-high-contrast-light.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-high-contrast",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - High Contrast",
|
||||
"description": "VS Code workbench with per-character typing animation in the High Contrast theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-high-contrast.html",
|
||||
"target": "compositions/code-snippet-high-contrast.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-light-2026",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Light 2026",
|
||||
"description": "VS Code workbench with per-character typing animation in the Light 2026 theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-light-2026.html",
|
||||
"target": "compositions/code-snippet-light-2026.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-light-modern",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Light Modern",
|
||||
"description": "VS Code workbench with per-character typing animation in the Light Modern theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-light-modern.html",
|
||||
"target": "compositions/code-snippet-light-modern.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-light-plus",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Light+",
|
||||
"description": "VS Code workbench with per-character typing animation in the Light+ theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-light-plus.html",
|
||||
"target": "compositions/code-snippet-light-plus.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-monokai",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Monokai",
|
||||
"description": "VS Code workbench with per-character typing animation in the Monokai theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-monokai.html",
|
||||
"target": "compositions/code-snippet-monokai.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-solarized-light",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Solarized Light",
|
||||
"description": "VS Code workbench with per-character typing animation in the Solarized Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-solarized-light.html",
|
||||
"target": "compositions/code-snippet-solarized-light.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-visual-studio-dark",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Visual Studio Dark",
|
||||
"description": "VS Code workbench with per-character typing animation in the Visual Studio Dark theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-visual-studio-dark.html",
|
||||
"target": "compositions/code-snippet-visual-studio-dark.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 427 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-snippet-visual-studio-light",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Snippet - Visual Studio Light",
|
||||
"description": "VS Code workbench with per-character typing animation in the Visual Studio Light theme. Full editor chrome with activity bar, sidebar, tabs, terminal, and status bar.",
|
||||
"tags": ["code", "developer", "showcase", "vscode"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 11,
|
||||
"files": [
|
||||
{
|
||||
"path": "code-snippet-visual-studio-light.html",
|
||||
"target": "compositions/code-snippet-visual-studio-light.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "background.jpeg",
|
||||
"target": "assets/background.jpeg",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Code Typing</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: #05070b;
|
||||
}
|
||||
#root {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
color: #e6edf3;
|
||||
}
|
||||
.bg-field {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(1200px 700px at 50% 18%, #0e1726 0%, #070b12 55%, #05070b 100%);
|
||||
}
|
||||
.bg-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(88, 166, 255, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(88, 166, 255, 0.05) 1px, transparent 1px);
|
||||
background-size: 64px 64px;
|
||||
mask-image: radial-gradient(1100px 620px at 50% 42%, #000 0%, transparent 78%);
|
||||
}
|
||||
.bg-glow {
|
||||
position: absolute;
|
||||
width: 900px;
|
||||
height: 900px;
|
||||
border-radius: 50%;
|
||||
filter: blur(120px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bg-glow.a {
|
||||
background: #1f6feb55;
|
||||
left: -180px;
|
||||
top: -200px;
|
||||
}
|
||||
.bg-glow.b {
|
||||
background: #2ea04355;
|
||||
right: -200px;
|
||||
bottom: -260px;
|
||||
}
|
||||
.editor {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1380px;
|
||||
height: 800px;
|
||||
background: #0b0f17;
|
||||
border: 1px solid #1d2733;
|
||||
border-radius: 16px;
|
||||
box-shadow:
|
||||
0 40px 120px rgba(0, 0, 0, 0.6),
|
||||
0 2px 0 rgba(255, 255, 255, 0.03) inset;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.titlebar {
|
||||
height: 56px;
|
||||
flex: 0 0 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 0 22px;
|
||||
background: linear-gradient(#11161f, #0c111a);
|
||||
border-bottom: 1px solid #1b2430;
|
||||
}
|
||||
.dots {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.r {
|
||||
background: #ff5f57;
|
||||
}
|
||||
.dot.y {
|
||||
background: #febc2e;
|
||||
}
|
||||
.dot.g {
|
||||
background: #28c840;
|
||||
}
|
||||
.filename {
|
||||
font-size: 19px;
|
||||
color: #8b98a9;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
.filename .accent {
|
||||
color: #d6e2f0;
|
||||
}
|
||||
.code-surface {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.scene {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
will-change: opacity;
|
||||
}
|
||||
.code {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0 44px 0 104px;
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
font-variant-ligatures: none;
|
||||
tab-size: 2;
|
||||
}
|
||||
.gutter {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
left: 0;
|
||||
width: 84px;
|
||||
padding-right: 18px;
|
||||
font-size: 30px;
|
||||
line-height: 46px;
|
||||
text-align: right;
|
||||
color: #828c9b;
|
||||
user-select: none;
|
||||
}
|
||||
.ln {
|
||||
display: block;
|
||||
height: 46px;
|
||||
}
|
||||
.line {
|
||||
display: block;
|
||||
min-height: 46px;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.tok {
|
||||
display: inline-block;
|
||||
white-space: pre;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.caret {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
height: 32px;
|
||||
margin-top: 6px;
|
||||
background: #58a6ff;
|
||||
border-radius: 1px;
|
||||
z-index: 3;
|
||||
}
|
||||
.hl-box {
|
||||
position: absolute;
|
||||
background: rgba(88, 166, 255, 0.16);
|
||||
border-left: 3px solid #58a6ff;
|
||||
border-radius: 6px;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.diff-line {
|
||||
display: block;
|
||||
min-height: 0;
|
||||
line-height: 46px;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-left: 14px;
|
||||
}
|
||||
.diff-sign {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
color: #41506220;
|
||||
}
|
||||
.diff-del {
|
||||
background: rgba(248, 81, 73, 0.12);
|
||||
box-shadow: inset 3px 0 #f85149;
|
||||
}
|
||||
.diff-add {
|
||||
background: rgba(63, 185, 80, 0.12);
|
||||
box-shadow: inset 3px 0 #3fb950;
|
||||
}
|
||||
.diff-del .diff-sign {
|
||||
color: #f85149;
|
||||
}
|
||||
.diff-add .diff-sign {
|
||||
color: #3fb950;
|
||||
}
|
||||
.flight-block {
|
||||
position: absolute;
|
||||
white-space: pre;
|
||||
font-size: 27px;
|
||||
line-height: 40px;
|
||||
padding: 11px 22px;
|
||||
border-radius: 11px;
|
||||
background: #0f1722;
|
||||
border: 1px solid #233044;
|
||||
box-shadow: 0 16px 44px rgba(0, 0, 0, 0.5);
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.flight-target {
|
||||
position: absolute;
|
||||
border: 1.5px dashed #2a3a4f;
|
||||
border-radius: 14px;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="code-typing"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div class="bg-field" data-layout-ignore></div>
|
||||
<div class="bg-grid" data-layout-ignore></div>
|
||||
<div class="bg-glow a" data-layout-ignore></div>
|
||||
<div class="bg-glow b" data-layout-ignore></div>
|
||||
<div class="editor" id="editor" data-layout-ignore>
|
||||
<div class="titlebar">
|
||||
<div class="dots">
|
||||
<span class="dot r"></span><span class="dot y"></span><span class="dot g"></span>
|
||||
</div>
|
||||
<span class="filename"><span class="accent">loadConfig.js</span> — Code Typing</span>
|
||||
</div>
|
||||
<div class="code-surface" id="surface"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.__TOKENS = {
|
||||
feature: {
|
||||
lang: "js",
|
||||
theme: "github-dark",
|
||||
bg: "#24292e",
|
||||
fg: "#e1e4e8",
|
||||
states: [
|
||||
{
|
||||
code: 'async function loadConfig(path) {\n const raw = await readFile(path, "utf8")\n const config = JSON.parse(raw)\n return validate(config)\n}',
|
||||
tokens: [
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-0",
|
||||
content: "async",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-1",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-2",
|
||||
content: "function",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-3",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-4",
|
||||
content: "loadConfig",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-5",
|
||||
content: "(",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-6",
|
||||
content: "path",
|
||||
color: "#FFAB70",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-7",
|
||||
content: ") {",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-8",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-9",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-10",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-11",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-12",
|
||||
content: "raw",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-13",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-14",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-15",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-16",
|
||||
content: "await",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-17",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-18",
|
||||
content: "readFile",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-19",
|
||||
content: "(path,",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-20",
|
||||
content: " ",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-21",
|
||||
content: '"utf8"',
|
||||
color: "#9ECBFF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-22",
|
||||
content: ")",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-23",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-24",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-25",
|
||||
content: "const",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-26",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-27",
|
||||
content: "config",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-28",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-29",
|
||||
content: "=",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-30",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-31",
|
||||
content: "JSON",
|
||||
color: "#79B8FF",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-32",
|
||||
content: ".",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-33",
|
||||
content: "parse",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-34",
|
||||
content: "(raw)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-35",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-36",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-37",
|
||||
content: "return",
|
||||
color: "#F97583",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-38",
|
||||
content: " ",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-39",
|
||||
content: "validate",
|
||||
color: "#B392F0",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-40",
|
||||
content: "(config)",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-41",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-42",
|
||||
content: "}",
|
||||
color: "#E1E4E8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
{
|
||||
key: "SPx9u4TyzfMqSHpbjPHnWFQU8lPVEPieF6W5Vtu-bNA-43",
|
||||
content: "\n",
|
||||
color: "#e1e4e8",
|
||||
fontStyle: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
window.__BLOCK = { id: "code-typing", effect: "typing", seq: "feature", duration: 5 };
|
||||
</script>
|
||||
<script>
|
||||
// hf-code-anim.js — self-contained code-animation engine for HyperFrames registry blocks.
|
||||
//
|
||||
// Reuses Shiki Magic Move's baked output (window.__TOKENS, baked at author time) and
|
||||
// drives the animation as a PAUSED GSAP timeline the HyperFrames engine seeks per frame —
|
||||
// NOT CSS transitions (those break under non-sequential frame seeking). FLIP measurement
|
||||
// runs once inside document.fonts.ready (glyph metrics must be final). Each block sets
|
||||
// window.__BLOCK = { id, effect, seq, line?, duration } and includes this engine; the
|
||||
// engine builds one scene that fills the composition and registers window.__timelines[id].
|
||||
(function () {
|
||||
"use strict";
|
||||
var T = window.__TOKENS || {};
|
||||
|
||||
function lineDiv() {
|
||||
var d = document.createElement("div");
|
||||
d.className = "line";
|
||||
return d;
|
||||
}
|
||||
|
||||
// Render a code state's keyed tokens into codeEl as lines of inline-block .tok spans.
|
||||
function renderState(codeEl, state) {
|
||||
codeEl.textContent = "";
|
||||
var keyToEl = new Map();
|
||||
var line = lineDiv();
|
||||
codeEl.appendChild(line);
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
line = lineDiv();
|
||||
codeEl.appendChild(line);
|
||||
continue;
|
||||
}
|
||||
var span = document.createElement("span");
|
||||
span.className = "tok";
|
||||
span.textContent = t.content;
|
||||
span.style.color = t.color;
|
||||
if (t.fontStyle & 1) span.style.fontStyle = "italic";
|
||||
if (t.fontStyle & 2) span.style.fontWeight = "700";
|
||||
line.appendChild(span);
|
||||
keyToEl.set(t.key, span);
|
||||
}
|
||||
var last = codeEl.lastChild;
|
||||
if (last && last.className === "line" && last.children.length === 0)
|
||||
codeEl.removeChild(last);
|
||||
return keyToEl;
|
||||
}
|
||||
|
||||
function rectsOf(keyToEl) {
|
||||
var m = new Map();
|
||||
keyToEl.forEach(function (el, k) {
|
||||
var r = el.getBoundingClientRect();
|
||||
m.set(k, { left: r.left, top: r.top });
|
||||
});
|
||||
return m;
|
||||
}
|
||||
|
||||
function makeScene(surface, withGutter) {
|
||||
var scene = document.createElement("div");
|
||||
scene.className = "scene";
|
||||
if (withGutter) {
|
||||
var g = document.createElement("div");
|
||||
g.className = "gutter";
|
||||
scene.appendChild(g);
|
||||
}
|
||||
var code = document.createElement("div");
|
||||
code.className = "code";
|
||||
scene.appendChild(code);
|
||||
surface.appendChild(scene);
|
||||
return { scene: scene, code: code, gutter: scene.querySelector(".gutter") };
|
||||
}
|
||||
|
||||
function fillGutter(g, n) {
|
||||
var html = "";
|
||||
for (var i = 1; i <= n; i++) html += '<span class="ln">' + i + "</span>";
|
||||
g.innerHTML = html;
|
||||
}
|
||||
|
||||
function splitLines(state) {
|
||||
var lines = [{ text: "", tokens: [] }];
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
lines.push({ text: "", tokens: [] });
|
||||
continue;
|
||||
}
|
||||
var cur = lines[lines.length - 1];
|
||||
cur.tokens.push(t);
|
||||
cur.text += t.content;
|
||||
}
|
||||
if (lines.length && lines[lines.length - 1].tokens.length === 0) lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
function lineDiff(a, b) {
|
||||
var n = a.length;
|
||||
var m = b.length;
|
||||
var dp = [];
|
||||
for (var i = 0; i <= n; i++) dp.push(new Array(m + 1).fill(0));
|
||||
for (var i = n - 1; i >= 0; i--) {
|
||||
for (var j = m - 1; j >= 0; j--) {
|
||||
dp[i][j] =
|
||||
a[i].text === b[j].text
|
||||
? dp[i + 1][j + 1] + 1
|
||||
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
||||
}
|
||||
}
|
||||
var ops = [];
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
while (i < n && j < m) {
|
||||
if (a[i].text === b[j].text) {
|
||||
ops.push({ type: "same", line: b[j] });
|
||||
i++;
|
||||
j++;
|
||||
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
||||
ops.push({ type: "del", line: a[i] });
|
||||
i++;
|
||||
} else {
|
||||
ops.push({ type: "add", line: b[j] });
|
||||
j++;
|
||||
}
|
||||
}
|
||||
while (i < n) ops.push({ type: "del", line: a[i++] });
|
||||
while (j < m) ops.push({ type: "add", line: b[j++] });
|
||||
return ops;
|
||||
}
|
||||
|
||||
// ---- single-scene effect builders (fade in, play, hold to end) -------------
|
||||
var FADE = 0.45;
|
||||
|
||||
function typing(tl, surface, state, start) {
|
||||
var PER = 0.028; // seconds per character — smooth, fast typewriter
|
||||
var parts = makeScene(surface, true);
|
||||
var code = parts.code;
|
||||
|
||||
// Per-CHARACTER render (granular reveal reads smoother than per-token pops).
|
||||
code.textContent = "";
|
||||
var line = document.createElement("div");
|
||||
line.className = "line";
|
||||
code.appendChild(line);
|
||||
var chars = [];
|
||||
for (var i = 0; i < state.tokens.length; i++) {
|
||||
var t = state.tokens[i];
|
||||
if (t.content === "\n") {
|
||||
line = document.createElement("div");
|
||||
line.className = "line";
|
||||
code.appendChild(line);
|
||||
continue;
|
||||
}
|
||||
for (var c = 0; c < t.content.length; c++) {
|
||||
var span = document.createElement("span");
|
||||
span.className = "tok";
|
||||
span.textContent = t.content[c];
|
||||
span.style.color = t.color;
|
||||
if (t.fontStyle & 1) span.style.fontStyle = "italic";
|
||||
if (t.fontStyle & 2) span.style.fontWeight = "700";
|
||||
line.appendChild(span);
|
||||
chars.push({ el: span, ws: t.content[c] === " " });
|
||||
}
|
||||
}
|
||||
var last = code.lastChild;
|
||||
if (last && last.className === "line" && last.children.length === 0)
|
||||
code.removeChild(last);
|
||||
|
||||
fillGutter(parts.gutter, code.querySelectorAll(".line").length);
|
||||
var base = code.getBoundingClientRect();
|
||||
var caret = document.createElement("div");
|
||||
caret.className = "caret";
|
||||
code.appendChild(caret);
|
||||
|
||||
tl.set(parts.scene, { opacity: 0 }, 0);
|
||||
tl.to(parts.scene, { opacity: 1, duration: FADE, ease: "power1.out" }, start);
|
||||
|
||||
var t0 = start + FADE;
|
||||
if (chars.length) {
|
||||
var r0 = chars[0].el.getBoundingClientRect();
|
||||
tl.set(caret, { x: r0.left - base.left, y: r0.top - base.top, opacity: 1 }, 0);
|
||||
}
|
||||
chars.forEach(function (c, i) {
|
||||
var at = t0 + i * PER;
|
||||
tl.set(c.el, { opacity: 0 }, 0);
|
||||
// soft per-char fade (overlaps neighbors → smooth wave); whitespace appears instantly
|
||||
tl.to(c.el, { opacity: 1, duration: c.ws ? 0.01 : 0.12, ease: "power1.out" }, at);
|
||||
// caret glides at constant speed to each char's right edge (linear = no stop-start jitter)
|
||||
var r = c.el.getBoundingClientRect();
|
||||
tl.to(
|
||||
caret,
|
||||
{ x: r.right - base.left, y: r.top - base.top, duration: PER, ease: "none" },
|
||||
at,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function buildEffect(tl, surface, spec) {
|
||||
var start = 0.45;
|
||||
var seq = T[spec.seq];
|
||||
if (spec.effect === "typing") typing(tl, surface, seq.states[0], start);
|
||||
}
|
||||
|
||||
function go() {
|
||||
var spec = window.__BLOCK;
|
||||
var surface = document.getElementById("surface");
|
||||
var root = document.getElementById("root");
|
||||
window.__timelines = window.__timelines || {};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
// Build inside fonts.ready (glyph metrics must be final), then register.
|
||||
// Register ONLY after the timeline is fully built: the runtime's
|
||||
// sub-composition readiness gate treats "key present" as "ready" and
|
||||
// nests the child once. An empty timeline registered before this build
|
||||
// would be nested empty and never re-nested → blank render when used as
|
||||
// a sub-composition. See the contract in engine frameCapture.ts.
|
||||
document.fonts.ready.then(function () {
|
||||
tl.from("#editor", { opacity: 0, scale: 0.985, duration: 0.5, ease: "power2.out" }, 0);
|
||||
buildEffect(tl, surface, spec);
|
||||
var dur = parseFloat(root.dataset.duration) || tl.duration();
|
||||
tl.to({}, { duration: dur }, 0); // pad to full composition length
|
||||
window.__timelines["code-typing"] = tl; // register AFTER setup completes
|
||||
if (typeof window.__hfForceTimelineRebind === "function") {
|
||||
window.__hfForceTimelineRebind(); // re-nest now that we are populated
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", go);
|
||||
else go();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "code-typing",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Code Typing",
|
||||
"description": "Token-streamed typing reveal with a caret that tracks the frontier — deterministic, no CSS animation.",
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 5,
|
||||
"tags": ["code", "code-animation", "typing", "developer"],
|
||||
"files": [
|
||||
{
|
||||
"path": "code-typing.html",
|
||||
"target": "compositions/code-typing.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-typing.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/code-typing.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Bebas+Neue&family=JetBrains+Mono:wght@400;700&family=Lato:wght@400;700&display=block");
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="main"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<style>
|
||||
[data-composition-id="main"] {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #283618;
|
||||
}
|
||||
|
||||
/* Blueprint grid */
|
||||
.bp-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.bp-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.bp-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 60px 60px 60px 40px;
|
||||
}
|
||||
.bp-divider {
|
||||
position: absolute;
|
||||
left: 1200px;
|
||||
top: 60px;
|
||||
bottom: 60px;
|
||||
width: 1px;
|
||||
background: #a7c957;
|
||||
opacity: 0.15;
|
||||
}
|
||||
.bp-number {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4em;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 60px;
|
||||
color: #a7c957;
|
||||
}
|
||||
|
||||
/* Right panel typography */
|
||||
.bp-name {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 38px;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 20px;
|
||||
color: #a7c957;
|
||||
}
|
||||
.bp-plabel {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
color: #a7c957;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bp-prompt {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
padding: 16px 20px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
color: #a7c957;
|
||||
background: rgba(167, 201, 87, 0.06);
|
||||
border: 1px solid rgba(167, 201, 87, 0.12);
|
||||
}
|
||||
.bp-desc {
|
||||
font-family: "Lato", sans-serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
color: #8aaa9a;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* GL scenes (hidden, just for texture capture) */
|
||||
.scene {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.scene-label {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 140px;
|
||||
letter-spacing: 0.06em;
|
||||
opacity: 0.12;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bp-grid"></div>
|
||||
<div class="bp-divider"></div>
|
||||
<div class="bp-number">13 / 14</div>
|
||||
|
||||
<!-- GL demo scenes (captured as textures, then hidden) -->
|
||||
<div id="s1" class="scene" style="background: #283618">
|
||||
<div class="scene-label" style="color: #a7c957">SCENE A</div>
|
||||
</div>
|
||||
<div id="s2" class="scene" style="background: #a7c957; opacity: 0">
|
||||
<div class="scene-label" style="color: #283618">SCENE B</div>
|
||||
</div>
|
||||
|
||||
<!-- GL canvas (left panel only) -->
|
||||
<canvas
|
||||
id="gl-canvas"
|
||||
width="1200"
|
||||
height="1080"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
"
|
||||
>
|
||||
</canvas>
|
||||
|
||||
<!-- Right panel metadata -->
|
||||
<div class="bp-right">
|
||||
<div class="bp-name">Cross-Warp Morph</div>
|
||||
<div class="bp-plabel">Prompt</div>
|
||||
<div class="bp-prompt">"use cross-warp morph shader transition"</div>
|
||||
<div class="bp-desc">
|
||||
Both scenes displace along a shared FBM noise field in opposite directions. Noise-driven
|
||||
blend boundary.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="driver"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
var glCanvas = document.getElementById("gl-canvas");
|
||||
var gl = glCanvas.getContext("webgl", { preserveDrawingBuffer: true });
|
||||
if (!gl) {
|
||||
console.warn("WebGL not available");
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
} else {
|
||||
gl.viewport(0, 0, 1200, 1080);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
var sceneTextures = {};
|
||||
|
||||
function captureScene(sceneId) {
|
||||
var scene = document.getElementById(sceneId);
|
||||
var origOpacity = scene.style.opacity;
|
||||
var origZ = scene.style.zIndex;
|
||||
scene.style.opacity = "1";
|
||||
scene.style.zIndex = "999";
|
||||
scene.offsetHeight;
|
||||
var c = document.createElement("canvas");
|
||||
c.width = 1200;
|
||||
c.height = 1080;
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.fillStyle = window.getComputedStyle(scene).backgroundColor;
|
||||
ctx.fillRect(0, 0, 1200, 1080);
|
||||
var sr = scene.getBoundingClientRect();
|
||||
var els = scene.querySelectorAll("*");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i],
|
||||
cs = window.getComputedStyle(el);
|
||||
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
||||
var r = el.getBoundingClientRect();
|
||||
if (r.width < 1 || r.height < 1) continue;
|
||||
var x = r.left - sr.left,
|
||||
y = r.top - sr.top,
|
||||
w = r.width,
|
||||
h = r.height;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = parseFloat(cs.opacity) || 1;
|
||||
var bg = cs.backgroundColor;
|
||||
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(x, y, w, h);
|
||||
}
|
||||
var hasChildEls = el.querySelector("div,span,img,video");
|
||||
var text = "";
|
||||
for (var j = 0; j < el.childNodes.length; j++)
|
||||
if (el.childNodes[j].nodeType === 3) text += el.childNodes[j].textContent;
|
||||
text = text.trim();
|
||||
if (text && !hasChildEls) {
|
||||
ctx.font = cs.fontWeight + " " + cs.fontSize + " " + cs.fontFamily;
|
||||
ctx.fillStyle = cs.color;
|
||||
if (cs.letterSpacing && cs.letterSpacing !== "normal")
|
||||
ctx.letterSpacing = cs.letterSpacing;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(text, x + w / 2, y + h / 2);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
scene.style.opacity = origOpacity;
|
||||
scene.style.zIndex = origZ;
|
||||
var tex = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
||||
sceneTextures[sceneId] = tex;
|
||||
}
|
||||
|
||||
var vertSrc =
|
||||
"attribute vec2 a_pos; varying vec2 v_uv; void main(){" +
|
||||
"v_uv=a_pos*0.5+0.5; v_uv.y=1.0-v_uv.y; gl_Position=vec4(a_pos,0,1);}";
|
||||
var quadBuf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
||||
gl.STATIC_DRAW,
|
||||
);
|
||||
function compileShader(src, type) {
|
||||
var s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
|
||||
console.error("Shader:", gl.getShaderInfoLog(s));
|
||||
return s;
|
||||
}
|
||||
function mkProg(fragSrc) {
|
||||
var p = gl.createProgram();
|
||||
gl.attachShader(p, compileShader(vertSrc, gl.VERTEX_SHADER));
|
||||
gl.attachShader(p, compileShader(fragSrc, gl.FRAGMENT_SHADER));
|
||||
gl.linkProgram(p);
|
||||
if (!gl.getProgramParameter(p, gl.LINK_STATUS))
|
||||
console.error("Link:", gl.getProgramInfoLog(p));
|
||||
return p;
|
||||
}
|
||||
|
||||
var H =
|
||||
"precision mediump float;" +
|
||||
"varying vec2 v_uv;" +
|
||||
"uniform sampler2D u_from, u_to;" +
|
||||
"uniform float u_progress;" +
|
||||
"uniform vec2 u_resolution;\n";
|
||||
|
||||
var NQ =
|
||||
"float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}" +
|
||||
"float vnoise(vec2 p){vec2 i=floor(p),f=fract(p);" +
|
||||
"f=f*f*f*(f*(f*6.-15.)+10.);" +
|
||||
"return mix(mix(hash(i),hash(i+vec2(1,0)),f.x)," +
|
||||
"mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x),f.y);}" +
|
||||
"float fbm(vec2 p){float v=0.,a=.5;" +
|
||||
"mat2 R=mat2(.8,.6,-.6,.8);" +
|
||||
"for(int i=0;i<5;i++){v+=a*vnoise(p);p=R*p*2.02;a*=.5;}return v;}";
|
||||
|
||||
var progPass = mkProg(H + "void main(){gl_FragColor=texture2D(u_from,v_uv);}");
|
||||
var progTrans = mkProg(
|
||||
H +
|
||||
NQ +
|
||||
"void main(){vec2 disp=vec2(fbm(v_uv*3.),fbm(v_uv*3.+vec2(7.3,3.7)))-.5;vec2 fromUv=clamp(v_uv+disp*u_progress*.5,0.,1.);vec2 toUv=clamp(v_uv-disp*(1.-u_progress)*.5,0.,1.);vec4 A=texture2D(u_from,fromUv);vec4 B=texture2D(u_to,toUv);float n=fbm(v_uv*4.+vec2(3.1,1.7));float blend=smoothstep(.4,.6,n+u_progress*1.2-.6);gl_FragColor=mix(A,B,blend);}",
|
||||
);
|
||||
|
||||
function renderShader(prog, texFrom, texTo, progress) {
|
||||
gl.useProgram(prog);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texFrom);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_from"), 0);
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texTo);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_to"), 1);
|
||||
gl.uniform1f(gl.getUniformLocation(prog, "u_progress"), progress);
|
||||
gl.uniform2f(gl.getUniformLocation(prog, "u_resolution"), 1200, 1080);
|
||||
var pos = gl.getAttribLocation(prog, "a_pos");
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.enableVertexAttribArray(pos);
|
||||
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
function easeInOut(p) {
|
||||
return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
|
||||
}
|
||||
|
||||
captureScene("s1");
|
||||
captureScene("s2");
|
||||
glCanvas.style.display = "block";
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
document.querySelectorAll(".scene").forEach(function (s) {
|
||||
s.style.opacity = "0";
|
||||
});
|
||||
|
||||
var tl = gsap.timeline({
|
||||
paused: true,
|
||||
onUpdate: function () {
|
||||
var t = tl.time();
|
||||
if (t >= 1.0 && t < 3.0) {
|
||||
renderShader(
|
||||
progTrans,
|
||||
sceneTextures["s1"],
|
||||
sceneTextures["s2"],
|
||||
easeInOut((t - 1.0) / 2.0),
|
||||
);
|
||||
} else if (t >= 3.0) {
|
||||
renderShader(progPass, sceneTextures["s2"], sceneTextures["s2"], 0);
|
||||
} else {
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
tl.to({ v: 0 }, { v: 1, duration: 4, ease: "none" }, 0);
|
||||
window.__timelines["main"] = tl;
|
||||
} // end if (gl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "cross-warp-morph",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Cross Warp Morph",
|
||||
"description": "Shader transition with cross-warped morphing",
|
||||
"tags": ["transition", "shader"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 4,
|
||||
"files": [
|
||||
{
|
||||
"path": "cross-warp-morph.html",
|
||||
"target": "compositions/cross-warp-morph.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/cross-warp-morph.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/cross-warp-morph.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Data Chart</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:wght@400;700&family=Libre+Franklin:wght@300;400;600&display=block"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background-color: #faf9f6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="data-chart"
|
||||
data-composition-id="data-chart"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
data-start="0"
|
||||
data-duration="15"
|
||||
>
|
||||
<div class="chart-container">
|
||||
<!-- Header Section -->
|
||||
<div class="header">
|
||||
<h1 class="headline">Monthly Revenue vs. Conversion Rate</h1>
|
||||
<p class="subtitle">Jan–Jun 2024, in thousands</p>
|
||||
<div class="key">
|
||||
<div class="key-item">
|
||||
<div class="key-box revenue-box"></div>
|
||||
<span class="key-text">Revenue</span>
|
||||
</div>
|
||||
<div class="key-item">
|
||||
<div class="key-line conversion-line"></div>
|
||||
<span class="key-text">Conversion Rate</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SVG Chart Area -->
|
||||
<svg class="chart-svg" viewBox="0 0 1600 700" preserveAspectRatio="xMidYMid meet">
|
||||
<!-- Gridlines Group -->
|
||||
<g class="gridlines"></g>
|
||||
|
||||
<!-- X-Axis Labels -->
|
||||
<g class="x-axis-labels"></g>
|
||||
|
||||
<!-- Bars Group (Revenue) -->
|
||||
<g class="bars-group"></g>
|
||||
|
||||
<!-- Bar Labels Group -->
|
||||
<g class="bar-labels-group"></g>
|
||||
|
||||
<!-- Line Group (Conversion Rate) -->
|
||||
<g class="line-group">
|
||||
<path class="conversion-path" fill="none" stroke="#326FA8" stroke-width="1.5" />
|
||||
<circle class="line-dot" r="5" fill="#326FA8" style="opacity: 0" />
|
||||
</g>
|
||||
|
||||
<!-- Line Labels Group -->
|
||||
<g class="line-labels-group"></g>
|
||||
|
||||
<!-- Baseline -->
|
||||
<line x1="100" y1="600" x2="1500" y2="600" stroke="black" stroke-width="1" />
|
||||
</svg>
|
||||
|
||||
<!-- Source Line -->
|
||||
<div class="source">Source: Internal analytics</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="data-chart"] {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
background-color: #faf9f6;
|
||||
font-family: "Libre Franklin", sans-serif;
|
||||
color: #333;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .chart-container {
|
||||
width: 1600px;
|
||||
height: 900px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
transform: scale(1.15);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .header {
|
||||
margin-bottom: 40px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .headline {
|
||||
font-family: "Libre Baskerville", serif;
|
||||
font-size: 42px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
clip-path: inset(0 100% 0 0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .subtitle {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
margin: 8px 0 16px 0;
|
||||
clip-path: inset(0 100% 0 0);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .key {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .key-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .key-box {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #5c5c5c;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .key-line {
|
||||
width: 16px;
|
||||
height: 1.5px;
|
||||
background-color: #326fa8;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .key-text {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .chart-svg {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .source {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .grid-line {
|
||||
stroke: #e8e8e8;
|
||||
stroke-width: 0.5px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .axis-label {
|
||||
font-size: 14px;
|
||||
fill: #666;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .bar {
|
||||
fill: #5c5c5c;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .bar-label {
|
||||
font-size: 14px;
|
||||
fill: #333;
|
||||
text-anchor: middle;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="data-chart"] .line-label {
|
||||
font-size: 14px;
|
||||
fill: #326fa8;
|
||||
font-weight: 600;
|
||||
text-anchor: middle;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const svg = document.querySelector('[data-composition-id="data-chart"] .chart-svg');
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Data
|
||||
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
|
||||
const revenueData = [8, 12, 15, 11, 18, 22];
|
||||
const conversionData = [2.1, 2.8, 3.2, 2.9, 3.8, 4.2];
|
||||
|
||||
// Chart Dimensions
|
||||
const chartWidth = 1400;
|
||||
const chartHeight = 500;
|
||||
const startX = 100;
|
||||
const startY = 600;
|
||||
const barWidth = 80;
|
||||
const spacing = (chartWidth - barWidth * months.length) / (months.length + 1);
|
||||
|
||||
// Scales
|
||||
const maxRevenue = 25;
|
||||
const maxConversion = 5;
|
||||
|
||||
const getBarY = (val) => startY - (val / maxRevenue) * chartHeight;
|
||||
const getLineY = (val) => startY - (val / maxConversion) * chartHeight;
|
||||
const getX = (i) => startX + spacing + i * (barWidth + spacing) + barWidth / 2;
|
||||
|
||||
// Setup Elements
|
||||
const gridGroup = svg.querySelector(".gridlines");
|
||||
const xAxisGroup = svg.querySelector(".x-axis-labels");
|
||||
const barsGroup = svg.querySelector(".bars-group");
|
||||
const barLabelsGroup = svg.querySelector(".bar-labels-group");
|
||||
const lineLabelsGroup = svg.querySelector(".line-labels-group");
|
||||
const conversionPath = svg.querySelector(".conversion-path");
|
||||
const lineDot = svg.querySelector(".line-dot");
|
||||
|
||||
// Create Gridlines
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const y = startY - i * (chartHeight / 5);
|
||||
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
||||
line.setAttribute("x1", startX);
|
||||
line.setAttribute("y1", y);
|
||||
line.setAttribute("x2", startX + chartWidth);
|
||||
line.setAttribute("y2", y);
|
||||
line.setAttribute("class", "grid-line");
|
||||
gridGroup.appendChild(line);
|
||||
}
|
||||
|
||||
// Create X-Axis Labels & Bars & Bar Labels
|
||||
const linePoints = [];
|
||||
months.forEach((month, i) => {
|
||||
const x = getX(i);
|
||||
|
||||
const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
||||
label.setAttribute("x", x);
|
||||
label.setAttribute("y", startY + 30);
|
||||
label.setAttribute("class", "axis-label");
|
||||
label.textContent = month;
|
||||
xAxisGroup.appendChild(label);
|
||||
|
||||
const barHeight = (revenueData[i] / maxRevenue) * chartHeight;
|
||||
const bar = document.createElementNS("http://www.w3.org/2000/svg", "rect");
|
||||
bar.setAttribute("x", x - barWidth / 2);
|
||||
bar.setAttribute("y", startY);
|
||||
bar.setAttribute("width", barWidth);
|
||||
bar.setAttribute("height", 0);
|
||||
bar.setAttribute("class", "bar");
|
||||
barsGroup.appendChild(bar);
|
||||
|
||||
const barLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
||||
barLabel.setAttribute("x", x);
|
||||
barLabel.setAttribute("y", getBarY(revenueData[i]) - 10);
|
||||
barLabel.setAttribute("class", "bar-label");
|
||||
barLabel.textContent = `$${revenueData[i]}K`;
|
||||
barLabelsGroup.appendChild(barLabel);
|
||||
|
||||
linePoints.push(`${x},${getLineY(conversionData[i])}`);
|
||||
|
||||
const lineLabel = document.createElementNS("http://www.w3.org/2000/svg", "text");
|
||||
lineLabel.setAttribute("x", x);
|
||||
lineLabel.setAttribute("y", getLineY(conversionData[i]) - 15);
|
||||
lineLabel.setAttribute("class", "line-label");
|
||||
lineLabel.textContent = `${conversionData[i]}%`;
|
||||
lineLabelsGroup.appendChild(lineLabel);
|
||||
});
|
||||
|
||||
// Set Path Data
|
||||
conversionPath.setAttribute("d", `M ${linePoints.join(" L ")}`);
|
||||
const pathLength = conversionPath.getTotalLength();
|
||||
conversionPath.style.strokeDasharray = pathLength;
|
||||
conversionPath.style.strokeDashoffset = pathLength;
|
||||
|
||||
// --- ANIMATION TIMELINE ---
|
||||
|
||||
tl.to(
|
||||
'[data-composition-id="data-chart"] .headline',
|
||||
{
|
||||
clipPath: "inset(0 0% 0 0)",
|
||||
duration: 1.2,
|
||||
ease: "power1.inOut",
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
tl.to(
|
||||
'[data-composition-id="data-chart"] .subtitle',
|
||||
{
|
||||
clipPath: "inset(0 0% 0 0)",
|
||||
duration: 1.2,
|
||||
ease: "power1.inOut",
|
||||
},
|
||||
0.2,
|
||||
);
|
||||
|
||||
tl.to(
|
||||
'[data-composition-id="data-chart"] .key',
|
||||
{
|
||||
opacity: 1,
|
||||
duration: 0.4,
|
||||
ease: "power2.out",
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
tl.to(
|
||||
'[data-composition-id="data-chart"] .grid-line',
|
||||
{
|
||||
opacity: 1,
|
||||
duration: 0.5,
|
||||
stagger: 0.25,
|
||||
ease: "power2.out",
|
||||
},
|
||||
0.5,
|
||||
);
|
||||
|
||||
// Bars
|
||||
const bars = barsGroup.querySelectorAll(".bar");
|
||||
const barLabels = barLabelsGroup.querySelectorAll(".bar-label");
|
||||
|
||||
bars.forEach((bar, i) => {
|
||||
const barHeight = (revenueData[i] / maxRevenue) * chartHeight;
|
||||
const startTime = 1.5 + i * 0.5;
|
||||
|
||||
tl.to(
|
||||
bar,
|
||||
{
|
||||
attr: { height: barHeight, y: startY - barHeight },
|
||||
duration: 0.8,
|
||||
ease: "power2.out",
|
||||
},
|
||||
startTime,
|
||||
);
|
||||
|
||||
tl.to(
|
||||
barLabels[i],
|
||||
{
|
||||
opacity: 1,
|
||||
duration: 0.3,
|
||||
ease: "power2.out",
|
||||
},
|
||||
startTime + 0.8,
|
||||
);
|
||||
});
|
||||
|
||||
// Line
|
||||
const lastBarStartTime = 1.5 + (months.length - 1) * 0.5;
|
||||
const lineStartTime = lastBarStartTime + 0.5;
|
||||
|
||||
tl.to(
|
||||
conversionPath,
|
||||
{
|
||||
strokeDashoffset: 0,
|
||||
duration: 3,
|
||||
ease: "none",
|
||||
},
|
||||
lineStartTime,
|
||||
);
|
||||
|
||||
// Line Dot & Labels
|
||||
tl.set(
|
||||
lineDot,
|
||||
{
|
||||
opacity: 1,
|
||||
attr: { cx: getX(0), cy: getLineY(conversionData[0]) },
|
||||
},
|
||||
lineStartTime,
|
||||
);
|
||||
|
||||
const lineLabels = lineLabelsGroup.querySelectorAll(".line-label");
|
||||
months.forEach((_, i) => {
|
||||
if (i > 0) {
|
||||
const x = getX(i);
|
||||
const y = getLineY(conversionData[i]);
|
||||
tl.to(
|
||||
lineDot,
|
||||
{
|
||||
attr: { cx: x, cy: y },
|
||||
duration: 3 / (months.length - 1),
|
||||
ease: "none",
|
||||
},
|
||||
lineStartTime + ((i - 1) / (months.length - 1)) * 3,
|
||||
);
|
||||
}
|
||||
|
||||
const labelTime = lineStartTime + (i / (months.length - 1)) * 3 + 0.2;
|
||||
|
||||
lineLabels[i].textContent = "0.0%";
|
||||
|
||||
tl.to(
|
||||
lineLabels[i],
|
||||
{
|
||||
opacity: 1,
|
||||
duration: 0.1,
|
||||
ease: "power2.out",
|
||||
},
|
||||
labelTime,
|
||||
);
|
||||
|
||||
const dummy = { val: 0 };
|
||||
tl.to(
|
||||
dummy,
|
||||
{
|
||||
val: conversionData[i],
|
||||
duration: 0.8,
|
||||
ease: "power2.out",
|
||||
onUpdate: () => {
|
||||
lineLabels[i].textContent = dummy.val.toFixed(1) + "%";
|
||||
},
|
||||
},
|
||||
labelTime,
|
||||
);
|
||||
});
|
||||
|
||||
// Source Line
|
||||
tl.to(
|
||||
'[data-composition-id="data-chart"] .source',
|
||||
{
|
||||
opacity: 1,
|
||||
duration: 0.5,
|
||||
ease: "power2.out",
|
||||
},
|
||||
">+0.5",
|
||||
);
|
||||
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["data-chart"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "data-chart",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Data Chart",
|
||||
"description": "Animated bar + line chart with staggered reveal, NYT-style typography, and value labels",
|
||||
"tags": ["data", "chart", "statistics"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 15,
|
||||
"files": [
|
||||
{
|
||||
"path": "data-chart.html",
|
||||
"target": "compositions/data-chart.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/data-chart.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/data-chart.png"
|
||||
},
|
||||
"params": [
|
||||
{
|
||||
"key": "--bg-color",
|
||||
"label": "Background",
|
||||
"type": "color",
|
||||
"default": "#faf9f6"
|
||||
},
|
||||
{
|
||||
"key": "--text-color",
|
||||
"label": "Text color",
|
||||
"type": "color",
|
||||
"default": "#333333"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Bebas+Neue&family=JetBrains+Mono:wght@400;700&family=Lato:wght@400;700&display=block");
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="main"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<style>
|
||||
[data-composition-id="main"] {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #0d1b2a;
|
||||
}
|
||||
|
||||
/* Blueprint grid */
|
||||
.bp-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.bp-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.bp-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 60px 60px 60px 40px;
|
||||
}
|
||||
.bp-divider {
|
||||
position: absolute;
|
||||
left: 1200px;
|
||||
top: 60px;
|
||||
bottom: 60px;
|
||||
width: 1px;
|
||||
background: #00f5d4;
|
||||
opacity: 0.15;
|
||||
}
|
||||
.bp-number {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4em;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 60px;
|
||||
color: #00f5d4;
|
||||
}
|
||||
|
||||
/* Right panel typography */
|
||||
.bp-name {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 38px;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 20px;
|
||||
color: #00f5d4;
|
||||
}
|
||||
.bp-plabel {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
color: #00f5d4;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bp-prompt {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
padding: 16px 20px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
color: #00f5d4;
|
||||
background: rgba(0, 245, 212, 0.06);
|
||||
border: 1px solid rgba(0, 245, 212, 0.12);
|
||||
}
|
||||
.bp-desc {
|
||||
font-family: "Lato", sans-serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
color: #8aa8c0;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* GL scenes (hidden, just for texture capture) */
|
||||
.scene {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.scene-label {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 140px;
|
||||
letter-spacing: 0.06em;
|
||||
opacity: 0.12;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bp-grid"></div>
|
||||
<div class="bp-divider"></div>
|
||||
<div class="bp-number">01 / 14</div>
|
||||
|
||||
<!-- GL demo scenes (captured as textures, then hidden) -->
|
||||
<div id="s1" class="scene" style="background: #0d1b2a">
|
||||
<div class="scene-label" style="color: #00f5d4">SCENE A</div>
|
||||
</div>
|
||||
<div id="s2" class="scene" style="background: #00f5d4; opacity: 0">
|
||||
<div class="scene-label" style="color: #0d1b2a">SCENE B</div>
|
||||
</div>
|
||||
|
||||
<!-- GL canvas (left panel only) -->
|
||||
<canvas
|
||||
id="gl-canvas"
|
||||
width="1200"
|
||||
height="1080"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
"
|
||||
>
|
||||
</canvas>
|
||||
|
||||
<!-- Right panel metadata -->
|
||||
<div class="bp-right">
|
||||
<div class="bp-name">Domain Warp Dissolve</div>
|
||||
<div class="bp-plabel">Prompt</div>
|
||||
<div class="bp-prompt">"use domain warp dissolve shader transition"</div>
|
||||
<div class="bp-desc">
|
||||
Cascaded fbm(p + fbm(p)) displaces both scenes along a warp field. Iridescent
|
||||
cosine-palette edge glow.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="driver"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
var glCanvas = document.getElementById("gl-canvas");
|
||||
var gl = glCanvas.getContext("webgl", { preserveDrawingBuffer: true });
|
||||
if (!gl) {
|
||||
console.warn("WebGL not available");
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
} else {
|
||||
gl.viewport(0, 0, 1200, 1080);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
var sceneTextures = {};
|
||||
|
||||
function captureScene(sceneId) {
|
||||
var scene = document.getElementById(sceneId);
|
||||
var origOpacity = scene.style.opacity;
|
||||
var origZ = scene.style.zIndex;
|
||||
scene.style.opacity = "1";
|
||||
scene.style.zIndex = "999";
|
||||
scene.offsetHeight;
|
||||
var c = document.createElement("canvas");
|
||||
c.width = 1200;
|
||||
c.height = 1080;
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.fillStyle = window.getComputedStyle(scene).backgroundColor;
|
||||
ctx.fillRect(0, 0, 1200, 1080);
|
||||
var sr = scene.getBoundingClientRect();
|
||||
var els = scene.querySelectorAll("*");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i],
|
||||
cs = window.getComputedStyle(el);
|
||||
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
||||
var r = el.getBoundingClientRect();
|
||||
if (r.width < 1 || r.height < 1) continue;
|
||||
var x = r.left - sr.left,
|
||||
y = r.top - sr.top,
|
||||
w = r.width,
|
||||
h = r.height;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = parseFloat(cs.opacity) || 1;
|
||||
var bg = cs.backgroundColor;
|
||||
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(x, y, w, h);
|
||||
}
|
||||
var hasChildEls = el.querySelector("div,span,img,video");
|
||||
var text = "";
|
||||
for (var j = 0; j < el.childNodes.length; j++)
|
||||
if (el.childNodes[j].nodeType === 3) text += el.childNodes[j].textContent;
|
||||
text = text.trim();
|
||||
if (text && !hasChildEls) {
|
||||
ctx.font = cs.fontWeight + " " + cs.fontSize + " " + cs.fontFamily;
|
||||
ctx.fillStyle = cs.color;
|
||||
if (cs.letterSpacing && cs.letterSpacing !== "normal")
|
||||
ctx.letterSpacing = cs.letterSpacing;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(text, x + w / 2, y + h / 2);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
scene.style.opacity = origOpacity;
|
||||
scene.style.zIndex = origZ;
|
||||
var tex = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
||||
sceneTextures[sceneId] = tex;
|
||||
}
|
||||
|
||||
var vertSrc =
|
||||
"attribute vec2 a_pos; varying vec2 v_uv; void main(){" +
|
||||
"v_uv=a_pos*0.5+0.5; v_uv.y=1.0-v_uv.y; gl_Position=vec4(a_pos,0,1);}";
|
||||
var quadBuf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
||||
gl.STATIC_DRAW,
|
||||
);
|
||||
function compileShader(src, type) {
|
||||
var s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
|
||||
console.error("Shader:", gl.getShaderInfoLog(s));
|
||||
return s;
|
||||
}
|
||||
function mkProg(fragSrc) {
|
||||
var p = gl.createProgram();
|
||||
gl.attachShader(p, compileShader(vertSrc, gl.VERTEX_SHADER));
|
||||
gl.attachShader(p, compileShader(fragSrc, gl.FRAGMENT_SHADER));
|
||||
gl.linkProgram(p);
|
||||
if (!gl.getProgramParameter(p, gl.LINK_STATUS))
|
||||
console.error("Link:", gl.getProgramInfoLog(p));
|
||||
return p;
|
||||
}
|
||||
|
||||
var H =
|
||||
"precision mediump float;" +
|
||||
"varying vec2 v_uv;" +
|
||||
"uniform sampler2D u_from, u_to;" +
|
||||
"uniform float u_progress;" +
|
||||
"uniform vec2 u_resolution;\n";
|
||||
|
||||
var NQ =
|
||||
"float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}" +
|
||||
"float vnoise(vec2 p){vec2 i=floor(p),f=fract(p);" +
|
||||
"f=f*f*f*(f*(f*6.-15.)+10.);" +
|
||||
"return mix(mix(hash(i),hash(i+vec2(1,0)),f.x)," +
|
||||
"mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x),f.y);}" +
|
||||
"float fbm(vec2 p){float v=0.,a=.5;" +
|
||||
"mat2 R=mat2(.8,.6,-.6,.8);" +
|
||||
"for(int i=0;i<5;i++){v+=a*vnoise(p);p=R*p*2.02;a*=.5;}return v;}";
|
||||
var CP =
|
||||
"vec3 palette(float t,vec3 a,vec3 b,vec3 c,vec3 d){" + "return a+b*cos(6.2832*(c*t+d));}";
|
||||
|
||||
var progPass = mkProg(H + "void main(){gl_FragColor=texture2D(u_from,v_uv);}");
|
||||
var progTrans = mkProg(
|
||||
H +
|
||||
NQ +
|
||||
CP +
|
||||
"void main(){vec2 q=vec2(fbm(v_uv*3.),fbm(v_uv*3.+vec2(5.2,1.3)));vec2 r=vec2(fbm(v_uv*3.+q*4.+vec2(1.7,9.2)),fbm(v_uv*3.+q*4.+vec2(8.3,2.8)));float n=fbm(v_uv*3.+r*2.);vec2 warpDir=(q-.5)*.4;vec4 A=texture2D(u_from,clamp(v_uv+warpDir*u_progress,0.,1.));vec4 B=texture2D(u_to,clamp(v_uv-warpDir*(1.-u_progress),0.,1.));float e=smoothstep(u_progress-.08,u_progress+.08,n);float ed=abs(n-u_progress);float em=smoothstep(.1,0.,ed)*(1.-step(1.,u_progress));vec3 ec=palette(ed*8.,vec3(.5),vec3(.5),vec3(1.),vec3(0.,.33,.67));gl_FragColor=vec4(mix(B,A,e).rgb+ec*em*2.,1.);}",
|
||||
);
|
||||
|
||||
function renderShader(prog, texFrom, texTo, progress) {
|
||||
gl.useProgram(prog);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texFrom);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_from"), 0);
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texTo);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_to"), 1);
|
||||
gl.uniform1f(gl.getUniformLocation(prog, "u_progress"), progress);
|
||||
gl.uniform2f(gl.getUniformLocation(prog, "u_resolution"), 1200, 1080);
|
||||
var pos = gl.getAttribLocation(prog, "a_pos");
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.enableVertexAttribArray(pos);
|
||||
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
function easeInOut(p) {
|
||||
return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
|
||||
}
|
||||
|
||||
captureScene("s1");
|
||||
captureScene("s2");
|
||||
glCanvas.style.display = "block";
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
document.querySelectorAll(".scene").forEach(function (s) {
|
||||
s.style.opacity = "0";
|
||||
});
|
||||
|
||||
var tl = gsap.timeline({
|
||||
paused: true,
|
||||
onUpdate: function () {
|
||||
var t = tl.time();
|
||||
if (t >= 1.0 && t < 3.0) {
|
||||
renderShader(
|
||||
progTrans,
|
||||
sceneTextures["s1"],
|
||||
sceneTextures["s2"],
|
||||
easeInOut((t - 1.0) / 2.0),
|
||||
);
|
||||
} else if (t >= 3.0) {
|
||||
renderShader(progPass, sceneTextures["s2"], sceneTextures["s2"], 0);
|
||||
} else {
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
tl.to({ v: 0 }, { v: 1, duration: 4, ease: "none" }, 0);
|
||||
window.__timelines["main"] = tl;
|
||||
} // end if (gl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "domain-warp-dissolve",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Domain Warp Dissolve",
|
||||
"description": "Shader transition with fractal noise domain warping",
|
||||
"tags": ["transition", "shader"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 4,
|
||||
"files": [
|
||||
{
|
||||
"path": "domain-warp-dissolve.html",
|
||||
"target": "compositions/domain-warp-dissolve.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/domain-warp-dissolve.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/domain-warp-dissolve.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Bebas+Neue&family=JetBrains+Mono:wght@400;700&family=Lato:wght@400;700&display=block");
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="root"
|
||||
data-composition-id="main"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<style>
|
||||
[data-composition-id="main"] {
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: #03071e;
|
||||
}
|
||||
|
||||
/* Blueprint grid */
|
||||
.bp-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.025) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
.bp-left {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.bp-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 720px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 60px 60px 60px 40px;
|
||||
}
|
||||
.bp-divider {
|
||||
position: absolute;
|
||||
left: 1200px;
|
||||
top: 60px;
|
||||
bottom: 60px;
|
||||
width: 1px;
|
||||
background: #ffba08;
|
||||
opacity: 0.15;
|
||||
}
|
||||
.bp-number {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4em;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 60px;
|
||||
color: #ffba08;
|
||||
}
|
||||
|
||||
/* Right panel typography */
|
||||
.bp-name {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 38px;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 20px;
|
||||
color: #ffba08;
|
||||
}
|
||||
.bp-plabel {
|
||||
font-family: "Space Mono", monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.3em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
color: #ffba08;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.bp-prompt {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
padding: 16px 20px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
color: #ffba08;
|
||||
background: rgba(255, 186, 8, 0.06);
|
||||
border: 1px solid rgba(255, 186, 8, 0.12);
|
||||
}
|
||||
.bp-desc {
|
||||
font-family: "Lato", sans-serif;
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
color: #888;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* GL scenes (hidden, just for texture capture) */
|
||||
.scene {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.scene-label {
|
||||
font-family: "Bebas Neue", sans-serif;
|
||||
font-size: 140px;
|
||||
letter-spacing: 0.06em;
|
||||
opacity: 0.12;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bp-grid"></div>
|
||||
<div class="bp-divider"></div>
|
||||
<div class="bp-number">12 / 14</div>
|
||||
|
||||
<!-- GL demo scenes (captured as textures, then hidden) -->
|
||||
<div id="s1" class="scene" style="background: #03071e">
|
||||
<div class="scene-label" style="color: #ffba08">SCENE A</div>
|
||||
</div>
|
||||
<div id="s2" class="scene" style="background: #ffba08; opacity: 0">
|
||||
<div class="scene-label" style="color: #03071e">SCENE B</div>
|
||||
</div>
|
||||
|
||||
<!-- GL canvas (left panel only) -->
|
||||
<canvas
|
||||
id="gl-canvas"
|
||||
width="1200"
|
||||
height="1080"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1200px;
|
||||
height: 1080px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
"
|
||||
>
|
||||
</canvas>
|
||||
|
||||
<!-- Right panel metadata -->
|
||||
<div class="bp-right">
|
||||
<div class="bp-name">Flash Through White</div>
|
||||
<div class="bp-plabel">Prompt</div>
|
||||
<div class="bp-prompt">"use flash through white shader transition"</div>
|
||||
<div class="bp-desc">
|
||||
Both scenes brighten to a white midpoint. Works on dark backgrounds where
|
||||
color-dip-to-black is invisible.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="driver"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
var glCanvas = document.getElementById("gl-canvas");
|
||||
var gl = glCanvas.getContext("webgl", { preserveDrawingBuffer: true });
|
||||
if (!gl) {
|
||||
console.warn("WebGL not available");
|
||||
window.__timelines["main"] = gsap.timeline({ paused: true });
|
||||
} else {
|
||||
gl.viewport(0, 0, 1200, 1080);
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
var sceneTextures = {};
|
||||
|
||||
function captureScene(sceneId) {
|
||||
var scene = document.getElementById(sceneId);
|
||||
var origOpacity = scene.style.opacity;
|
||||
var origZ = scene.style.zIndex;
|
||||
scene.style.opacity = "1";
|
||||
scene.style.zIndex = "999";
|
||||
scene.offsetHeight;
|
||||
var c = document.createElement("canvas");
|
||||
c.width = 1200;
|
||||
c.height = 1080;
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.fillStyle = window.getComputedStyle(scene).backgroundColor;
|
||||
ctx.fillRect(0, 0, 1200, 1080);
|
||||
var sr = scene.getBoundingClientRect();
|
||||
var els = scene.querySelectorAll("*");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i],
|
||||
cs = window.getComputedStyle(el);
|
||||
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
||||
var r = el.getBoundingClientRect();
|
||||
if (r.width < 1 || r.height < 1) continue;
|
||||
var x = r.left - sr.left,
|
||||
y = r.top - sr.top,
|
||||
w = r.width,
|
||||
h = r.height;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = parseFloat(cs.opacity) || 1;
|
||||
var bg = cs.backgroundColor;
|
||||
if (bg && bg !== "rgba(0, 0, 0, 0)" && bg !== "transparent") {
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(x, y, w, h);
|
||||
}
|
||||
var hasChildEls = el.querySelector("div,span,img,video");
|
||||
var text = "";
|
||||
for (var j = 0; j < el.childNodes.length; j++)
|
||||
if (el.childNodes[j].nodeType === 3) text += el.childNodes[j].textContent;
|
||||
text = text.trim();
|
||||
if (text && !hasChildEls) {
|
||||
ctx.font = cs.fontWeight + " " + cs.fontSize + " " + cs.fontFamily;
|
||||
ctx.fillStyle = cs.color;
|
||||
if (cs.letterSpacing && cs.letterSpacing !== "normal")
|
||||
ctx.letterSpacing = cs.letterSpacing;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(text, x + w / 2, y + h / 2);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
scene.style.opacity = origOpacity;
|
||||
scene.style.zIndex = origZ;
|
||||
var tex = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
||||
sceneTextures[sceneId] = tex;
|
||||
}
|
||||
|
||||
var vertSrc =
|
||||
"attribute vec2 a_pos; varying vec2 v_uv; void main(){" +
|
||||
"v_uv=a_pos*0.5+0.5; v_uv.y=1.0-v_uv.y; gl_Position=vec4(a_pos,0,1);}";
|
||||
var quadBuf = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
|
||||
gl.STATIC_DRAW,
|
||||
);
|
||||
function compileShader(src, type) {
|
||||
var s = gl.createShader(type);
|
||||
gl.shaderSource(s, src);
|
||||
gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
|
||||
console.error("Shader:", gl.getShaderInfoLog(s));
|
||||
return s;
|
||||
}
|
||||
function mkProg(fragSrc) {
|
||||
var p = gl.createProgram();
|
||||
gl.attachShader(p, compileShader(vertSrc, gl.VERTEX_SHADER));
|
||||
gl.attachShader(p, compileShader(fragSrc, gl.FRAGMENT_SHADER));
|
||||
gl.linkProgram(p);
|
||||
if (!gl.getProgramParameter(p, gl.LINK_STATUS))
|
||||
console.error("Link:", gl.getProgramInfoLog(p));
|
||||
return p;
|
||||
}
|
||||
|
||||
var H =
|
||||
"precision mediump float;" +
|
||||
"varying vec2 v_uv;" +
|
||||
"uniform sampler2D u_from, u_to;" +
|
||||
"uniform float u_progress;" +
|
||||
"uniform vec2 u_resolution;\n";
|
||||
|
||||
var progPass = mkProg(H + "void main(){gl_FragColor=texture2D(u_from,v_uv);}");
|
||||
var progTrans = mkProg(
|
||||
H +
|
||||
"void main(){vec4 A=texture2D(u_from,v_uv),B=texture2D(u_to,v_uv);float toWhite=smoothstep(0.,.45,u_progress);vec3 fromC=mix(A.rgb,vec3(1.),toWhite);float fromWhite=1.-smoothstep(.5,1.,u_progress);vec3 toC=mix(B.rgb,vec3(1.),fromWhite);gl_FragColor=vec4(mix(fromC,toC,smoothstep(.35,.65,u_progress)),1.);}",
|
||||
);
|
||||
|
||||
function renderShader(prog, texFrom, texTo, progress) {
|
||||
gl.useProgram(prog);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texFrom);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_from"), 0);
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texTo);
|
||||
gl.uniform1i(gl.getUniformLocation(prog, "u_to"), 1);
|
||||
gl.uniform1f(gl.getUniformLocation(prog, "u_progress"), progress);
|
||||
gl.uniform2f(gl.getUniformLocation(prog, "u_resolution"), 1200, 1080);
|
||||
var pos = gl.getAttribLocation(prog, "a_pos");
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
|
||||
gl.enableVertexAttribArray(pos);
|
||||
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
function easeInOut(p) {
|
||||
return p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
|
||||
}
|
||||
|
||||
captureScene("s1");
|
||||
captureScene("s2");
|
||||
glCanvas.style.display = "block";
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
document.querySelectorAll(".scene").forEach(function (s) {
|
||||
s.style.opacity = "0";
|
||||
});
|
||||
|
||||
var tl = gsap.timeline({
|
||||
paused: true,
|
||||
onUpdate: function () {
|
||||
var t = tl.time();
|
||||
if (t >= 1.0 && t < 3.0) {
|
||||
renderShader(
|
||||
progTrans,
|
||||
sceneTextures["s1"],
|
||||
sceneTextures["s2"],
|
||||
easeInOut((t - 1.0) / 2.0),
|
||||
);
|
||||
} else if (t >= 3.0) {
|
||||
renderShader(progPass, sceneTextures["s2"], sceneTextures["s2"], 0);
|
||||
} else {
|
||||
renderShader(progPass, sceneTextures["s1"], sceneTextures["s1"], 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
tl.to({ v: 0 }, { v: 1, duration: 4, ease: "none" }, 0);
|
||||
window.__timelines["main"] = tl;
|
||||
} // end if (gl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "flash-through-white",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Flash Through White",
|
||||
"description": "Shader transition with white flash crossfade",
|
||||
"tags": ["transition", "shader"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 4,
|
||||
"files": [
|
||||
{
|
||||
"path": "flash-through-white.html",
|
||||
"target": "compositions/flash-through-white.html",
|
||||
"type": "hyperframes:composition"
|
||||
}
|
||||
],
|
||||
"preview": {
|
||||
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/flash-through-white.mp4",
|
||||
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/flash-through-white.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1440, height=2560" />
|
||||
<title>Flowchart Vertical</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=block"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 1440px;
|
||||
height: 2560px;
|
||||
overflow: hidden;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="flowchart-vertical"
|
||||
data-composition-id="flowchart-vertical"
|
||||
data-width="1440"
|
||||
data-height="2560"
|
||||
data-start="0"
|
||||
data-duration="12"
|
||||
>
|
||||
<div class="canvas">
|
||||
<svg class="connectors" width="1440" height="2560" viewBox="0 0 1440 2560">
|
||||
<!-- Level 1 to 2 -->
|
||||
<path
|
||||
id="path-1-L"
|
||||
class="connector"
|
||||
d="M 720 210 L 720 360 L 440 360 L 440 530"
|
||||
fill="none"
|
||||
stroke="#333"
|
||||
stroke-width="4"
|
||||
/>
|
||||
<path
|
||||
id="path-1-R"
|
||||
class="connector"
|
||||
d="M 720 210 L 720 360 L 1000 360 L 1000 530"
|
||||
fill="none"
|
||||
stroke="#333"
|
||||
stroke-width="4"
|
||||
/>
|
||||
|
||||
<!-- Level 2 to 3 (Left side) -->
|
||||
<path
|
||||
id="path-2-LL"
|
||||
class="connector"
|
||||
d="M 440 590 L 440 760 L 280 760 L 280 930"
|
||||
fill="none"
|
||||
stroke="#333"
|
||||
stroke-width="4"
|
||||
/>
|
||||
<path
|
||||
id="path-2-LR"
|
||||
class="connector"
|
||||
d="M 440 590 L 440 760 L 600 760 L 600 930"
|
||||
fill="none"
|
||||
stroke="#333"
|
||||
stroke-width="4"
|
||||
/>
|
||||
|
||||
<!-- Level 2 to 3 (Right side) -->
|
||||
<path
|
||||
id="path-2-RL"
|
||||
class="connector"
|
||||
d="M 1000 590 L 1000 760 L 840 760 L 840 930"
|
||||
fill="none"
|
||||
stroke="#333"
|
||||
stroke-width="4"
|
||||
/>
|
||||
<path
|
||||
id="path-2-RR"
|
||||
class="connector"
|
||||
d="M 1000 590 L 1000 760 L 1160 760 L 1160 930"
|
||||
fill="none"
|
||||
stroke="#333"
|
||||
stroke-width="4"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div id="label-yes" class="connector-label">Yes</div>
|
||||
<div id="label-not-sure" class="connector-label">Not sure</div>
|
||||
|
||||
<!-- Level 1 -->
|
||||
<div id="node-root" class="node yellow" style="left: 720px; top: 180px">
|
||||
Should I learn to code?
|
||||
</div>
|
||||
|
||||
<!-- Level 2 -->
|
||||
<div id="node-yes" class="node green" style="left: 440px; top: 560px">Yes</div>
|
||||
<div id="node-not-sure" class="node peach" style="left: 1000px; top: 560px">Not sure</div>
|
||||
|
||||
<!-- Level 3 -->
|
||||
<div id="node-python" class="node lavender" style="left: 280px; top: 960px">
|
||||
<div style="position: relative; display: inline-block">
|
||||
<span id="python-text">Start with Pythom</span>
|
||||
<svg class="squiggle-container" width="64" height="6" viewBox="0 0 64 6">
|
||||
<path
|
||||
id="squiggle"
|
||||
d="M 0 3 Q 2 0 4 3 T 8 3 T 12 3 T 16 3 T 20 3 T 24 3 T 28 3 T 32 3 T 36 3 T 40 3 T 44 3 T 48 3 T 52 3 T 56 3 T 60 3 T 64 3"
|
||||
fill="none"
|
||||
stroke="#E53935"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div id="node-nocode" class="node blue" style="left: 600px; top: 960px">
|
||||
Try no-code first
|
||||
</div>
|
||||
<div id="node-website" class="node pink" style="left: 840px; top: 960px">
|
||||
Build a personal website
|
||||
</div>
|
||||
<div id="node-course" class="node yellow" style="left: 1160px; top: 960px">
|
||||
Take a free intro course
|
||||
</div>
|
||||
|
||||
<!-- Cursor -->
|
||||
<div id="cursor" class="cursor-container">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path
|
||||
d="M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19841L11.7841 12.3673H5.65376Z"
|
||||
fill="white"
|
||||
stroke="black"
|
||||
/>
|
||||
</svg>
|
||||
<div class="cursor-tag">You</div>
|
||||
</div>
|
||||
|
||||
<!-- Emoji -->
|
||||
<div id="emoji-thumb" class="emoji">👍</div>
|
||||
|
||||
<!-- Overlay -->
|
||||
<div id="fade-overlay"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#flowchart-vertical {
|
||||
width: 1440px;
|
||||
height: 2560px;
|
||||
background-color: #ffffff;
|
||||
background-image: radial-gradient(#e5e5e5 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
|
||||
#flowchart-vertical .canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
transform: none;
|
||||
transform-origin: 720px 640px;
|
||||
}
|
||||
|
||||
#flowchart-vertical .node {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
padding: 16px 24px;
|
||||
border-radius: 12px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#flowchart-vertical .node.yellow {
|
||||
background-color: #e8d44d;
|
||||
}
|
||||
#flowchart-vertical .node.green {
|
||||
background-color: #c2e8a0;
|
||||
}
|
||||
#flowchart-vertical .node.peach {
|
||||
background-color: #f5c5a3;
|
||||
}
|
||||
#flowchart-vertical .node.lavender {
|
||||
background-color: #d4c5f9;
|
||||
}
|
||||
#flowchart-vertical .node.blue {
|
||||
background-color: #a8d8f0;
|
||||
}
|
||||
#flowchart-vertical .node.pink {
|
||||
background-color: #f8b4c8;
|
||||
}
|
||||
|
||||
#flowchart-vertical .connectors {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#flowchart-vertical .connector {
|
||||
stroke-dasharray: 1000;
|
||||
stroke-dashoffset: 1000;
|
||||
}
|
||||
|
||||
#flowchart-vertical .connector-label {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
background: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
z-index: 6;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#flowchart-vertical #label-yes {
|
||||
left: 580px;
|
||||
top: 420px;
|
||||
}
|
||||
#flowchart-vertical #label-not-sure {
|
||||
left: 860px;
|
||||
top: 420px;
|
||||
}
|
||||
|
||||
#flowchart-vertical .cursor-container {
|
||||
position: absolute;
|
||||
left: 1440px;
|
||||
top: 2560px;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#flowchart-vertical .cursor-tag {
|
||||
background-color: #9747ff;
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-left: 4px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
#flowchart-vertical .squiggle-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -4px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#flowchart-vertical .emoji {
|
||||
position: absolute;
|
||||
font-size: 32px;
|
||||
left: 390px;
|
||||
top: 960px;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
#flowchart-vertical #fade-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
opacity: 0;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#flowchart-vertical .selection-border {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
border: 2px solid #0b84f3;
|
||||
border-radius: 16px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#flowchart-vertical .text-highlight {
|
||||
background-color: #d0e4ff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
const S = "#flowchart-vertical";
|
||||
const nodePython = document.querySelector(S + " #node-python");
|
||||
const pythonText = document.querySelector(S + " #python-text");
|
||||
|
||||
// 1. Root node scales in
|
||||
tl.to(S + " #node-root", { scale: 1, duration: 0.4, ease: "power2.out" });
|
||||
|
||||
// 2. Hold
|
||||
tl.addLabel("hold1", "+=0.6");
|
||||
|
||||
// 3. Connectors draw
|
||||
tl.to(
|
||||
S + " .connector#path-1-L, " + S + " .connector#path-1-R",
|
||||
{ strokeDashoffset: 0, duration: 0.5, ease: "none" },
|
||||
"hold1",
|
||||
);
|
||||
|
||||
tl.to(
|
||||
S + " #label-yes, " + S + " #label-not-sure",
|
||||
{ opacity: 1, duration: 0.2 },
|
||||
"hold1+=0.25",
|
||||
);
|
||||
|
||||
// 4. Level 2 nodes
|
||||
tl.to(S + " #node-yes", { scale: 1, duration: 0.4, ease: "back.out(1.7)" }, "hold1+=0.4");
|
||||
tl.to(
|
||||
S + " #node-not-sure",
|
||||
{ scale: 1, duration: 0.4, ease: "back.out(1.7)" },
|
||||
"hold1+=0.6",
|
||||
);
|
||||
|
||||
// 5. Hold
|
||||
tl.addLabel("hold2", "+=0.5");
|
||||
|
||||
// 6. Level 2→3 connectors
|
||||
tl.to(
|
||||
S +
|
||||
" .connector#path-2-LL, " +
|
||||
S +
|
||||
" .connector#path-2-LR, " +
|
||||
S +
|
||||
" .connector#path-2-RL, " +
|
||||
S +
|
||||
" .connector#path-2-RR",
|
||||
{ strokeDashoffset: 0, duration: 0.4, ease: "none" },
|
||||
"hold2",
|
||||
);
|
||||
|
||||
// 7. Leaf nodes
|
||||
const leafNodes = [
|
||||
S + " #node-python",
|
||||
S + " #node-nocode",
|
||||
S + " #node-website",
|
||||
S + " #node-course",
|
||||
];
|
||||
leafNodes.forEach((id, i) => {
|
||||
tl.to(
|
||||
id,
|
||||
{ scale: 1, duration: 0.4, ease: "back.out(1.7)" },
|
||||
`hold2+=${0.3 + i * 0.15}`,
|
||||
);
|
||||
});
|
||||
|
||||
tl.to(S + " .squiggle-container", { opacity: 1, duration: 0.1 }, "hold2+=0.3");
|
||||
|
||||
// 8. Hold
|
||||
tl.addLabel("hold3", "+=0.8");
|
||||
|
||||
// 9. Cursor drifts in
|
||||
tl.to(S + " #cursor", { x: -1110, y: -1560, duration: 1, ease: "power1.inOut" }, "hold3");
|
||||
|
||||
// 10. Cursor clicks — selection border
|
||||
tl.add(() => {
|
||||
if (!nodePython) return;
|
||||
if (!nodePython.querySelector(".selection-border")) {
|
||||
const border = document.createElement("div");
|
||||
border.className = "selection-border";
|
||||
nodePython.appendChild(border);
|
||||
}
|
||||
gsap.set(S + " .selection-border", { opacity: 1 });
|
||||
}, "hold3+=1");
|
||||
|
||||
tl.to(
|
||||
S + " #cursor",
|
||||
{ scale: 0.8, duration: 0.05, yoyo: true, repeat: 1, overwrite: "auto" },
|
||||
"hold3+=1",
|
||||
);
|
||||
|
||||
// 11. Hold
|
||||
tl.addLabel("hold4", "+=0.3");
|
||||
|
||||
// 12. Double-click to highlight "Pythom"
|
||||
tl.to(
|
||||
S + " #cursor",
|
||||
{ scale: 0.8, duration: 0.05, yoyo: true, repeat: 3, overwrite: "auto" },
|
||||
"hold4",
|
||||
);
|
||||
tl.add(() => {
|
||||
if (!pythonText) return;
|
||||
pythonText.innerHTML = 'Start with <span class="text-highlight">Pythom</span>';
|
||||
}, "hold4+=0.1");
|
||||
|
||||
// 13. Hold
|
||||
tl.addLabel("hold5", "+=0.2");
|
||||
|
||||
// 14. Type correction: "Pythom" → "Python"
|
||||
const typingStart = tl.labels["hold5"];
|
||||
const words = ["P", "Py", "Pyt", "Pyth", "Pytho", "Python"];
|
||||
words.forEach((word, i) => {
|
||||
tl.add(
|
||||
() => {
|
||||
if (pythonText)
|
||||
pythonText.innerHTML = `Start with <span class="text-highlight">${word}</span>`;
|
||||
},
|
||||
typingStart + i * 0.05,
|
||||
);
|
||||
});
|
||||
|
||||
const typingEnd = typingStart + words.length * 0.05;
|
||||
tl.add(() => {
|
||||
if (pythonText) pythonText.innerHTML = "Start with Python";
|
||||
gsap.set(S + " .squiggle-container", { opacity: 0 });
|
||||
}, typingEnd);
|
||||
|
||||
// 15. Hold
|
||||
tl.addLabel("hold6", typingEnd + 0.5);
|
||||
|
||||
// 16. Cursor clicks away to deselect
|
||||
tl.to(S + " #cursor", { x: -1050, y: -1520, duration: 0.3, overwrite: "auto" }, "hold6");
|
||||
tl.to(
|
||||
S + " #cursor",
|
||||
{ scale: 0.8, duration: 0.05, yoyo: true, repeat: 1, overwrite: "auto" },
|
||||
"hold6+=0.3",
|
||||
);
|
||||
tl.to(S + " .selection-border", { opacity: 0, duration: 0.1 }, "hold6+=0.3");
|
||||
|
||||
// 17. Emoji pop
|
||||
tl.to(
|
||||
S + " #emoji-thumb",
|
||||
{ scale: 1, duration: 0.15, ease: "back.out(2)" },
|
||||
"hold6+=0.4",
|
||||
);
|
||||
|
||||
// 18. Hold
|
||||
tl.addLabel("hold7", "+=2");
|
||||
|
||||
// 19. Fade out
|
||||
tl.to(S + " #fade-overlay", { opacity: 1, duration: 0.5 }, "hold7");
|
||||
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines["flowchart-vertical"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||