feat(registry): bring back the video-primitive moves (#3169)

Restores the 208 catalog items reverted after their previews 404'd in
production, this time on the payload mechanism rather than the .html files
that caused the outage.

The generator no longer writes a preview document to docs/public. That writer,
and the machinery under it, existed only to produce files the docs host
discards, so it is gone rather than bypassed. Items now embed the composition
itself via a payload, which is what the previous change already does for the
items that were already in the catalog.

The variables explorer is parked, not restored: it drove its preview through
the same unpublished .html path, so it would have shown an empty frame. Items
that declare variables get the live player plus the static variables table, and
reconnecting the explorer to payloads is a follow-up.
This commit is contained in:
Miguel Ángel
2026-08-10 18:46:03 -04:00
committed by GitHub
parent 0f76305191
commit 9734578e60
1383 changed files with 255573 additions and 5760 deletions
+488 -26
View File
@@ -3,40 +3,21 @@ title: "Magnetic"
description: "VFX composition block"
---
import { InstallCommand } from "/snippets/install-command.jsx";
<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/vfx-magnetic.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/vfx-magnetic.png" autoPlay muted loop playsInline />
## Install
```bash Terminal
npx hyperframes add vfx-magnetic
```
<InstallCommand command="npx hyperframes add vfx-magnetic" />
That writes one file: `compositions/vfx-magnetic.html`.
<Warning>
<Danger>
Live preview needs the `chrome://flags/#canvas-draw-element` flag switched on.
Rendering from the CLI switches it on for you. [How it
works](/guides/html-in-canvas)
</Warning>
## Add it to your video
It runs for 15 seconds at 1920×1080. Paste this into your composition:
```html index.html
<div
data-composition-id="vfx-magnetic"
data-composition-src="compositions/vfx-magnetic.html"
data-start="0"
data-duration="15"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
```
Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
Without it this item's screen renders black. Rendering from the CLI switches
it on for you. [How it works](/guides/html-in-canvas)
</Danger>
## Change the colors
@@ -45,6 +26,487 @@ Set these CSS variables on the block:
- `--bg-color` — Background. Defaults to `#0c0c0c`.
- `--text-color` — Text color. Defaults to `#e8e8e8`.
## Source
<Accordion title={`vfx-magnetic.html`}>
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Magnetic Cursor — HTML-in-Canvas</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0c0c0c;
overflow: hidden;
font-family:
system-ui,
-apple-system,
sans-serif;
color: #e8e8e8;
}
#root {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0c0c0c;
}
canvas {
display: block;
}
.page-content {
width: 1920px;
height: 1080px;
overflow: hidden;
font-family:
system-ui,
-apple-system,
sans-serif;
color: #e8e8e8;
display: flex;
align-items: center;
justify-content: center;
}
.page-inner {
max-width: 900px;
padding: 0 48px;
}
.heading {
font-size: 64px;
font-weight: 700;
line-height: 1.15;
letter-spacing: -0.03em;
color: #f0f0f0;
margin-bottom: 32px;
}
.heading em {
font-style: normal;
color: #70c7ed;
}
.body-text {
font-size: 22px;
line-height: 1.7;
color: rgba(255, 255, 255, 0.6);
margin-bottom: 24px;
}
.body-text:last-of-type {
margin-bottom: 48px;
}
.hint {
font-size: 15px;
color: rgba(255, 255, 255, 0.3);
font-family: ui-monospace, "SF Mono", monospace;
margin-bottom: 48px;
letter-spacing: 0.02em;
}
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.card {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
padding: 24px;
}
.card-icon {
width: 40px;
height: 40px;
border-radius: 10px;
margin-bottom: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
background: rgba(112, 199, 237, 0.12);
}
.card-title {
font-size: 16px;
font-weight: 600;
color: rgba(255, 255, 255, 0.85);
margin-bottom: 8px;
}
.card-desc {
font-size: 14px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.4);
}
/* Subtitles */
.subtitle-bar {
position: absolute;
bottom: 60px;
left: 50%;
transform: translateX(-50%);
z-index: 10;
pointer-events: none;
}
.subtitle {
font-size: 28px;
font-weight: 500;
color: white;
text-align: center;
background: rgba(0, 0, 0, 0.7);
padding: 12px 32px;
border-radius: 8px;
backdrop-filter: blur(8px);
white-space: nowrap;
opacity: 0;
font-family:
system-ui,
-apple-system,
sans-serif;
}
/* Cursor dot */
.cursor-dot {
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(112, 199, 237, 0.8);
box-shadow:
0 0 20px rgba(112, 199, 237, 0.5),
0 0 40px rgba(112, 199, 237, 0.3);
z-index: 10;
pointer-events: none;
transform: translate(-50%, -50%);
opacity: 0;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="magnetic-cursor"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="15"
data-root="true"
>
<canvas
id="gl-canvas"
layoutsubtree
width="1920"
height="1080"
style="position: absolute; top: 0; left: 0; width: 1920px; height: 1080px"
>
<div class="page-content" id="content" style="width: 1920px; height: 1080px">
<div class="page-inner">
<h1 class="heading">Pixels bend toward<br />your <em>cursor</em></h1>
<p class="body-text">
Move your mouse across this page. Every pixel is drawn through a WebGL shader that
warps space toward your cursor position, like a magnet pulling iron filings beneath a
thin rubber sheet.
</p>
<p class="body-text">
The distortion follows a Gaussian falloff — strong at close range, tapering smoothly
to nothing. The text remains readable even under deformation.
</p>
<p class="hint">HTML-in-Canvas API + WebGL2 shader distortion</p>
<div class="card-grid">
<div class="card">
<div class="card-icon">&#9889;</div>
<div class="card-title">Real-time</div>
<div class="card-desc">
60fps distortion on live DOM content via texElementImage2D
</div>
</div>
<div class="card">
<div class="card-icon">&#128268;</div>
<div class="card-title">Native API</div>
<div class="card-desc">
No html2canvas. Browser's own rasterizer at full fidelity.
</div>
</div>
<div class="card">
<div class="card-icon">&#127912;</div>
<div class="card-title">GPU Shaders</div>
<div class="card-desc">
Gaussian warp + chromatic aberration in one fragment shader pass.
</div>
</div>
</div>
</div>
</div>
</canvas>
<!-- Visible cursor dot -->
<div class="cursor-dot" id="cursor-dot"></div>
<!-- Subtitles -->
<div class="subtitle-bar">
<div class="subtitle" id="sub1">
The HTML-in-Canvas API renders live DOM as a WebGL texture
</div>
<div class="subtitle" id="sub2">A magnetic shader warps every pixel toward the cursor</div>
<div class="subtitle" id="sub3">
Chromatic aberration splits RGB channels at the distortion site
</div>
<div class="subtitle" id="sub4">
Impossible without native drawElementImage — no polyfill exists
</div>
</div>
<div
id="driver"
class="clip"
data-start="0"
data-duration="15"
data-track-index="0"
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
></div>
</div>
<script>
var W = 1920,
H = 1080;
var canvas = document.getElementById("gl-canvas");
var content = document.getElementById("content");
var cursorDot = document.getElementById("cursor-dot");
function isSupported() {
var tc = document.createElement("canvas");
if (!("layoutSubtree" in tc)) return false;
tc.setAttribute("layoutsubtree", "");
var ctx = tc.getContext("2d");
return ctx && typeof ctx.drawElementImage === "function";
}
// ── WebGL2 Setup ─────────────────────────────────────────────────
var gl = canvas.getContext("webgl2", { alpha: false, preserveDrawingBuffer: true });
function compile(type, src) {
var s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) console.error(gl.getShaderInfoLog(s));
return s;
}
function link(vsSrc, fsSrc) {
var p = gl.createProgram();
gl.attachShader(p, compile(gl.VERTEX_SHADER, vsSrc));
gl.attachShader(p, compile(gl.FRAGMENT_SHADER, fsSrc));
gl.linkProgram(p);
return p;
}
var VS = [
"#version 300 es",
"in vec2 a_pos;",
"out vec2 v_uv;",
"void main() {",
" v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);",
" gl_Position = vec4(a_pos, 0.0, 1.0);",
"}",
].join("\n");
var FS = [
"#version 300 es",
"precision highp float;",
"in vec2 v_uv;",
"out vec4 fragColor;",
"uniform sampler2D u_content;",
"uniform vec2 u_mouse;",
"uniform vec2 u_resolution;",
"uniform float u_repel;",
"uniform float u_strength;",
"",
"void main() {",
" vec2 uv = v_uv;",
" float aspect = u_resolution.x / u_resolution.y;",
" vec2 delta = u_mouse - uv;",
" vec2 aspectDelta = delta * vec2(aspect, 1.0);",
" float dist = length(aspectDelta);",
" float strength = exp(-dist * dist * 20.0) * 0.04 * u_strength;",
" float direction = mix(1.0, -1.0, u_repel);",
" vec2 displaced = uv + delta * strength * direction;",
" displaced = clamp(displaced, vec2(0.0), vec2(1.0));",
" float aberration = strength * 0.6;",
" vec2 aberDir = normalize(delta + 0.0001) * aberration;",
" float r = texture(u_content, displaced + aberDir * 0.3).r;",
" float g = texture(u_content, displaced).g;",
" float b = texture(u_content, displaced - aberDir * 0.3).b;",
" fragColor = vec4(r, g, b, 1.0);",
"}",
].join("\n");
var prog = link(VS, FS);
// Quad
var vao = gl.createVertexArray();
gl.bindVertexArray(vao);
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
gl.STATIC_DRAW,
);
var aPos = gl.getAttribLocation(prog, "a_pos");
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
// Texture
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// Uniforms
gl.useProgram(prog);
var U = {};
["u_content", "u_mouse", "u_resolution", "u_repel", "u_strength"].forEach(function (n) {
U[n] = gl.getUniformLocation(prog, n);
});
// ── Capture + Render ─────────────────────────────────────────────
var captured = false;
function captureContent() {
if (!isSupported()) return;
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, content);
captured = true;
}
// Also support drawElementImage fallback via 2D context capture
function captureVia2D() {
var tc = document.createElement("canvas");
tc.setAttribute("layoutsubtree", "");
tc.width = W;
tc.height = H;
// Can't use drawElementImage from a different canvas — skip fallback
}
function render(mouseX, mouseY, repel, strength) {
gl.viewport(0, 0, W, H);
gl.useProgram(prog);
gl.bindVertexArray(vao);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.uniform1i(U.u_content, 0);
gl.uniform2f(U.u_mouse, mouseX, mouseY);
gl.uniform2f(U.u_resolution, W, H);
gl.uniform1f(U.u_repel, repel);
gl.uniform1f(U.u_strength, strength);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
// ── Animation State ──────────────────────────────────────────────
var S = {
mouseX: 0.5,
mouseY: 0.5,
repel: 0,
strength: 0,
progress: 0,
};
// ── GSAP Timeline ────────────────────────────────────────────────
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// Driver tween
tl.to(
S,
{
progress: 1,
duration: 15,
ease: "none",
onUpdate: function () {
// Update cursor dot position
cursorDot.style.left = S.mouseX * W + "px";
cursorDot.style.top = S.mouseY * H + "px";
render(S.mouseX, S.mouseY, S.repel, S.strength);
},
},
0,
);
// Cursor appears
tl.to(cursorDot, { opacity: 1, duration: 0.3 }, 0.5);
tl.to(S, { strength: 1, duration: 0.5, ease: "power2.out" }, 0.5);
// Cursor path: smooth figure-8 across the page
// Phase 1: sweep across heading
tl.to(S, { mouseX: 0.35, mouseY: 0.3, duration: 2, ease: "power1.inOut" }, 1);
tl.to(S, { mouseX: 0.6, mouseY: 0.35, duration: 2, ease: "power1.inOut" }, 3);
// Phase 2: circle around text
tl.to(S, { mouseX: 0.4, mouseY: 0.5, duration: 1.5, ease: "sine.inOut" }, 5);
tl.to(S, { mouseX: 0.55, mouseY: 0.55, duration: 1.5, ease: "sine.inOut" }, 6.5);
// Phase 3: repel pulse
tl.to(S, { repel: 1, duration: 0.2, ease: "power4.out" }, 8);
tl.to(S, { repel: 0, duration: 0.5, ease: "power2.out" }, 8.5);
// Phase 4: sweep across cards
tl.to(S, { mouseX: 0.3, mouseY: 0.75, duration: 2, ease: "power1.inOut" }, 9);
tl.to(S, { mouseX: 0.65, mouseY: 0.72, duration: 2, ease: "power1.inOut" }, 11);
// Phase 5: exit
tl.to(S, { mouseX: 0.8, mouseY: 0.2, duration: 1.5, ease: "power2.inOut" }, 13);
tl.to(S, { strength: 0, duration: 0.5 }, 14);
tl.to(cursorDot, { opacity: 0, duration: 0.3 }, 14.2);
// Subtitles
tl.to("#sub1", { opacity: 1, duration: 0.3 }, 1);
tl.to("#sub1", { opacity: 0, duration: 0.3 }, 3.5);
tl.to("#sub2", { opacity: 1, duration: 0.3 }, 4);
tl.to("#sub2", { opacity: 0, duration: 0.3 }, 6.5);
tl.to("#sub3", { opacity: 1, duration: 0.3 }, 7.5);
tl.to("#sub3", { opacity: 0, duration: 0.3 }, 9.5);
tl.to("#sub4", { opacity: 1, duration: 0.3 }, 10.5);
tl.to("#sub4", { opacity: 0, duration: 0.3 }, 13);
window.__timelines["magnetic-cursor"] = tl;
// Paint event for texture upload
canvas.onpaint = function () {
if (!captured) {
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, content);
captured = true;
}
};
setTimeout(function () {
if (isSupported()) {
canvas.requestPaint();
}
tl.seek(0);
}, 0);
</script>
</body>
</html>
```
</Accordion>
## Usage
After installing, add the block to your host composition:
```html
<div data-composition-id="vfx-magnetic" data-composition-src="compositions/vfx-magnetic.html" data-start="0" data-duration="15" data-track-index="1" data-width="1920" data-height="1080"></div>
```
{/* hf:generated-footer */}
Tagged `html-in-canvas` `webgl`.