mirror of
https://github.com/hanzoai/frames.git
synced 2026-09-24 00:08:03 +00:00
feat(registry): add Code Animations catalog section (9 blocks, incl. GPU)
Adds a Code Animations catalog section — 9 self-contained, installable blocks: morph, snippet-flight, typing, diff, highlight, scroll (DOM/GSAP) and 3d-extrude, shader-dissolve, particle-assemble (WebGL). Each block ships only its own effect and renders deterministically (paused GSAP timeline seeked per frame, seeded RNG, no render-time data fetch). Wires the catalog nav, registry.json, a new code-animation Studio category, and preview assets.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
---
|
||||
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."
|
||||
---
|
||||
|
||||
# Code 3D Extrude
|
||||
|
||||
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.
|
||||
|
||||
`code` `code-animation` `3d` `webgl` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-3d-extrude
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 8s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-3d-extrude.html` | `compositions/code-3d-extrude.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-3d-extrude" data-composition-src="compositions/code-3d-extrude.html" data-start="0" data-duration="8" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Code Diff"
|
||||
description: "An edit shown as a colored diff — removed lines collapse in red, added lines expand in green."
|
||||
---
|
||||
|
||||
# Code Diff
|
||||
|
||||
An edit shown as a colored diff — removed lines collapse in red, added lines expand in green.
|
||||
|
||||
`code` `code-animation` `diff` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-diff
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 6s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-diff.html` | `compositions/code-diff.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-diff" data-composition-src="compositions/code-diff.html" data-start="0" data-duration="6" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Code Highlight Sweep"
|
||||
description: "A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line."
|
||||
---
|
||||
|
||||
# Code Highlight Sweep
|
||||
|
||||
A highlight band sweeps across a target line while the surrounding context dims — draws the eye to one line.
|
||||
|
||||
`code` `code-animation` `highlight` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-highlight
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 5s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-highlight.html` | `compositions/code-highlight.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-highlight" data-composition-src="compositions/code-highlight.html" data-start="0" data-duration="5" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
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."
|
||||
---
|
||||
|
||||
# Code Morph
|
||||
|
||||
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.
|
||||
|
||||
`code` `code-animation` `morph` `refactor` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-morph
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 7s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-morph.html` | `compositions/code-morph.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-morph" data-composition-src="compositions/code-morph.html" data-start="0" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
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."
|
||||
---
|
||||
|
||||
# Code Particle Assemble
|
||||
|
||||
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.
|
||||
|
||||
`code` `code-animation` `particles` `gpu` `webgl` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-particle-assemble
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 8s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-particle-assemble.html` | `compositions/code-particle-assemble.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-particle-assemble" data-composition-src="compositions/code-particle-assemble.html" data-start="0" data-duration="8" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
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."
|
||||
---
|
||||
|
||||
# Code Scroll To Line
|
||||
|
||||
The camera scrolls a long file to bring a target line to center and spotlights it — for walking through real modules.
|
||||
|
||||
`code` `code-animation` `scroll` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-scroll
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 6s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-scroll.html` | `compositions/code-scroll.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-scroll" data-composition-src="compositions/code-scroll.html" data-start="0" data-duration="6" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
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."
|
||||
---
|
||||
|
||||
# Code Shader Dissolve
|
||||
|
||||
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.
|
||||
|
||||
`code` `code-animation` `shader` `webgl` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-shader-dissolve
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 7s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-shader-dissolve.html` | `compositions/code-shader-dissolve.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-shader-dissolve" data-composition-src="compositions/code-shader-dissolve.html" data-start="0" data-duration="7" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Code Snippet Flight"
|
||||
description: "Discrete code snippets fly in from the side and assemble into a stacked program, staggered. Block-level FLIP."
|
||||
---
|
||||
|
||||
# Code Snippet Flight
|
||||
|
||||
Discrete code snippets fly in from the side and assemble into a stacked program, staggered. Block-level FLIP.
|
||||
|
||||
`code` `code-animation` `flight` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-snippet-flight
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 6s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-snippet-flight.html` | `compositions/code-snippet-flight.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-snippet-flight" data-composition-src="compositions/code-snippet-flight.html" data-start="0" data-duration="6" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "Code Typing"
|
||||
description: "Token-streamed typing reveal with a caret that tracks the frontier — deterministic, no CSS animation."
|
||||
---
|
||||
|
||||
# Code Typing
|
||||
|
||||
Token-streamed typing reveal with a caret that tracks the frontier — deterministic, no CSS animation.
|
||||
|
||||
`code` `code-animation` `typing` `developer`
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="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" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash Terminal
|
||||
npx hyperframes add code-typing
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Details
|
||||
|
||||
| Property | Value |
|
||||
| --- | --- |
|
||||
| Type | Block |
|
||||
| Dimensions | 1920×1080 |
|
||||
| Duration | 5s |
|
||||
|
||||
## Files
|
||||
|
||||
| File | Target | Type |
|
||||
| --- | --- | --- |
|
||||
| `code-typing.html` | `compositions/code-typing.html` | hyperframes:composition |
|
||||
|
||||
## Usage
|
||||
|
||||
After installing, add the block to your host composition:
|
||||
|
||||
```html
|
||||
<div data-composition-id="code-typing" data-composition-src="compositions/code-typing.html" data-start="0" data-duration="5" data-track-index="1" data-width="1920" data-height="1080"></div>
|
||||
```
|
||||
@@ -112,6 +112,20 @@
|
||||
{
|
||||
"tab": "Catalog",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Code Animations",
|
||||
"pages": [
|
||||
"catalog/blocks/code-3d-extrude",
|
||||
"catalog/blocks/code-shader-dissolve",
|
||||
"catalog/blocks/code-particle-assemble",
|
||||
"catalog/blocks/code-morph",
|
||||
"catalog/blocks/code-snippet-flight",
|
||||
"catalog/blocks/code-typing",
|
||||
"catalog/blocks/code-diff",
|
||||
"catalog/blocks/code-highlight",
|
||||
"catalog/blocks/code-scroll"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Captions",
|
||||
"pages": [
|
||||
|
||||
@@ -182,7 +182,8 @@ export type BlockCategory =
|
||||
| "scenes"
|
||||
| "captions"
|
||||
| "effects"
|
||||
| "text-effects";
|
||||
| "text-effects"
|
||||
| "code-animation";
|
||||
|
||||
export interface BlockCategoryMeta {
|
||||
id: BlockCategory;
|
||||
@@ -192,6 +193,7 @@ export interface BlockCategoryMeta {
|
||||
|
||||
export const BLOCK_CATEGORIES: BlockCategoryMeta[] = [
|
||||
{ id: "captions", label: "Captions", color: "cyan" },
|
||||
{ id: "code-animation", label: "Code Animations", color: "emerald" },
|
||||
{ id: "vfx", label: "VFX", color: "purple" },
|
||||
{ id: "transitions", label: "Transitions", color: "blue" },
|
||||
{ id: "effects", label: "Effects", color: "rose" },
|
||||
@@ -205,6 +207,7 @@ export function resolveBlockCategory(tags: string[] | undefined): BlockCategory
|
||||
if (!tags || tags.length === 0) return "scenes";
|
||||
const set = new Set(tags);
|
||||
if (set.has("captions") || set.has("caption-style")) return "captions";
|
||||
if (set.has("code-animation")) return "code-animation";
|
||||
if (set.has("transition")) return "transitions";
|
||||
if (set.has("social") || set.has("overlay")) return "social";
|
||||
if (set.has("data") || set.has("chart") || set.has("map")) return "data";
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import {
|
||||
type BlockCategory,
|
||||
type BlockCategoryMeta,
|
||||
BLOCK_CATEGORIES,
|
||||
resolveBlockCategory,
|
||||
} from "@hyperframes/core/registry";
|
||||
|
||||
export type { BlockCategory, BlockCategoryMeta };
|
||||
export type { BlockCategory };
|
||||
export { BLOCK_CATEGORIES, resolveBlockCategory };
|
||||
|
||||
const COLOR_MAP: Record<BlockCategory, { bg: string; text: string; dot: string }> = {
|
||||
@@ -17,6 +16,7 @@ const COLOR_MAP: Record<BlockCategory, { bg: string; text: string; dot: string }
|
||||
captions: { bg: "bg-cyan-500/15", text: "text-cyan-400", dot: "bg-cyan-400" },
|
||||
effects: { bg: "bg-rose-500/15", text: "text-rose-400", dot: "bg-rose-400" },
|
||||
"text-effects": { bg: "bg-violet-500/15", text: "text-violet-400", dot: "bg-violet-400" },
|
||||
"code-animation": { bg: "bg-emerald-500/15", text: "text-emerald-400", dot: "bg-emerald-400" },
|
||||
};
|
||||
|
||||
export function getCategoryColors(category: BlockCategory) {
|
||||
|
||||
@@ -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/[email protected]/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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"
|
||||
}
|
||||
}
|
||||
+807
@@ -0,0 +1,807 @@
|
||||
<!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/[email protected]/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 });
|
||||
window.__timelines["code-diff"] = 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-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"
|
||||
}
|
||||
}
|
||||
+736
@@ -0,0 +1,736 @@
|
||||
<!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/[email protected]/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 });
|
||||
window.__timelines["code-highlight"] = 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-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"
|
||||
}
|
||||
}
|
||||
+1207
File diff suppressed because it is too large
Load Diff
@@ -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/[email protected]/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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"
|
||||
}
|
||||
}
|
||||
+1551
File diff suppressed because it is too large
Load Diff
@@ -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/[email protected]/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/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,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/[email protected]/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"
|
||||
}
|
||||
}
|
||||
+754
@@ -0,0 +1,754 @@
|
||||
<!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/[email protected]/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 });
|
||||
window.__timelines["code-typing"] = 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-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"
|
||||
}
|
||||
}
|
||||
@@ -471,6 +471,42 @@
|
||||
"name": "code-snippet-visual-studio-light",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-morph",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-snippet-flight",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-typing",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-diff",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-highlight",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-scroll",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-3d-extrude",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-shader-dissolve",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "code-particle-assemble",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "flowchart-vertical",
|
||||
"type": "hyperframes:block"
|
||||
|
||||
@@ -519,17 +519,19 @@ function main(): void {
|
||||
// Items with the same first tag are grouped together. Items without tags
|
||||
// go into an "Other" group. Groups are sorted with a priority order.
|
||||
const GROUP_ORDER: Record<string, number> = {
|
||||
Captions: 0,
|
||||
"HTML-in-Canvas": 1,
|
||||
"Social Overlays": 2,
|
||||
"Shader Transitions": 3,
|
||||
"CSS Transitions": 4,
|
||||
Showcases: 5,
|
||||
Data: 6,
|
||||
Effects: 7,
|
||||
Blocks: 8,
|
||||
"Code Animations": 0,
|
||||
Captions: 1,
|
||||
"HTML-in-Canvas": 2,
|
||||
"Social Overlays": 3,
|
||||
"Shader Transitions": 4,
|
||||
"CSS Transitions": 5,
|
||||
Showcases: 6,
|
||||
Data: 7,
|
||||
Effects: 8,
|
||||
Blocks: 9,
|
||||
};
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
function groupForItem(entry: CatalogEntry): string {
|
||||
const tags = entry.tags;
|
||||
// Two-tag combos for specific grouping
|
||||
@@ -537,6 +539,9 @@ function main(): void {
|
||||
if (tags.includes("transition") && tags.includes("showcase")) return "CSS Transitions";
|
||||
if (tags.includes("captions")) return "Captions";
|
||||
if (tags.includes("html-in-canvas")) return "HTML-in-Canvas";
|
||||
// Code animations (morph, flight, diff, …) — keyed on the code-animation tag so
|
||||
// they group separately from the static code-snippet themes.
|
||||
if (tags.includes("code-animation")) return "Code Animations";
|
||||
// Single-tag mapping
|
||||
if (tags.includes("social")) return "Social Overlays";
|
||||
if (tags.includes("transition"))
|
||||
|
||||
Reference in New Issue
Block a user