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
@@ -0,0 +1,349 @@
---
title: "Zoom Through Transition"
description: "A transition that zooms through a focal card into the next scene."
---
import { InstallCommand } from "/snippets/install-command.jsx";
<iframe
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
title="zoom-through-transition preview"
loading="lazy"
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@0.7/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/components/zoom-through-transition.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/zoom-through-transition.png");document.body.appendChild(p)});<\/script></body></html>`}
/>
## Install
<InstallCommand command="npx hyperframes add zoom-through-transition" />
That writes one file: `compositions/components/zoom-through-transition.html`.
## Variables
Every one of these has a default, so the piece works untouched. Set the ones you
want to change on the element:
| Variable | Default | Accepts | What it does |
| --- | --- | --- | --- |
| `from-label` | `Before` | string | Caption on the outgoing scene, the one on screen when the transition starts. |
| `to-label` | `Zoom Through Transition` | string | Caption on the incoming scene, the one the timeline wipes into frame. |
| `depth` | `standard` | `close`, `standard`, `far` | Multiplies how far both scenes travel and how hard the outgoing one blurs, so the push covers a shorter or longer run in the same time. |
| `accent` | `blue` | `blue`, `green`, `violet` | Theme token for the ring marking the incoming scene. Blue rides --accent, green --brand, violet --accent-2; each falls back to a neutral hairline. |
Set them with `data-variable-values` on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:
```html wrap
<div
data-composition-id="zoom-through-transition"
data-composition-src="compositions/components/zoom-through-transition.html"
data-variable-values='{"from-label":"Before","to-label":"Zoom Through Transition","depth":"standard","accent":"blue"}'
></div>
```
## Source
<Accordion title={`zoom-through-transition.html`}>
```html
<!doctype html>
<!--
Zoom Through Transition - a scene push that wipes the incoming scene through
the outgoing one while the outgoing one drifts back and blurs.
Mount contract: MOUNTABLE SUB-COMPOSITION. The runtime clones only <template>
contents; #root fills the host box (inset: 0, container-type: size), and the
file registers one paused timeline under the literal "zoom-through-transition"
key. Variables come from window.__hyperframes.getVariables().
This file owns its motion. It previously shipped as a fragment whose only
executable timeline lived in demo.html, so an installed copy rendered the
finished pose and never moved.
Variables (declared in data-composition-variables below):
- from-label (string, default "Before"): caption on the outgoing scene.
- to-label (string, default "Zoom Through Transition"): caption on the
incoming scene, clipped out of frame until the wipe brings it in.
- depth (close | standard | far, default standard): multiplies how far both
scenes travel and how hard the outgoing one blurs. Every input it
multiplies is zero at rest, so it collapses to the shipped pose there. It
deliberately leaves the two scale tracks alone: the timeline drives those
from their own start values, which a multiplier would distort.
- accent (blue | green | violet, default blue): ink of the ring that marks
the incoming scene. Both scenes are monochrome placeholder material; the
ring is the one accent-coloured element. blue rides --accent, green rides
--brand, violet rides --accent-2, and each falls back to a neutral
hairline so an unthemed composition renders in black and white.
Envelope: the push starts at 0.35s (clamped to fit shorter durations), runs
0.9s, then holds the incoming scene for whatever is left.
-->
<html
lang="en"
data-composition-id="zoom-through-transition"
data-composition-duration="3"
data-composition-variables='[
{ "id": "from-label", "type": "string", "role": "content", "label": "Outgoing label", "description": "Caption on the outgoing scene, the one on screen when the transition starts.", "default": "Before" },
{ "id": "to-label", "type": "string", "role": "content", "label": "Incoming label", "description": "Caption on the incoming scene, the one the timeline wipes into frame.", "default": "Zoom Through Transition" },
{ "id": "depth", "type": "enum", "role": "motion", "label": "Depth", "description": "Multiplies how far both scenes travel and how hard the outgoing one blurs, so the push covers a shorter or longer run in the same time.", "default": "standard", "options": [{ "value": "close", "label": "Close" }, { "value": "standard", "label": "Standard" }, { "value": "far", "label": "Far" }] },
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Theme token for the ring marking the incoming scene. Blue rides --accent, green --brand, violet --accent-2; each falls back to a neutral hairline.", "default": "blue", "options": [{ "value": "blue", "label": "Blue" }, { "value": "green", "label": "Green" }, { "value": "violet", "label": "Violet" }] }
]'
>
<head>
<meta charset="UTF-8" />
<title>Zoom Through Transition</title>
</head>
<body>
<template>
<div id="root" data-composition-id="zoom-through-transition" data-duration="3" data-fps="30">
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Root fills the host-owned box; the scenes measure against it. */
#root {
position: absolute;
inset: 0;
container-type: size;
isolation: isolate;
overflow: hidden;
background: #111827;
}
.hf-remocn-zoom-through-transition {
--hf-accent: var(--accent, rgba(255, 255, 255, 0.28));
--hf-l1: rgba(255, 255, 255, 0.72);
--hf-l3: rgba(255, 255, 255, 0.18);
--hf-l4: rgba(255, 255, 255, 0.08);
--hf-hair: rgba(255, 255, 255, 0.14);
--hf-ink: #111827;
--hf-muted: #6b7280;
--hf-surface: #ffffff;
color: var(--hf-ink);
font-family: Inter, system-ui, sans-serif;
}
.hf-remocn-zoom-through-transition {
position: absolute;
inset: 0;
overflow: hidden;
background: #111827;
}
.hf-remocn-zoom-through-transition .scene {
position: absolute;
inset: 0;
display: grid;
place-items: center;
font-weight: 900;
font-size: 13.5cqmin;
letter-spacing: -0.05em;
color: #fff;
}
.hf-remocn-zoom-through-transition .from {
background:
repeating-linear-gradient(
90deg,
rgba(255, 255, 255, 0.04) 0 1px,
transparent 1px 4px
),
var(--hf-l4);
transform: translate(
calc(var(--hf-from-x, 0px) * var(--hf-depth, 1)),
calc(var(--hf-from-y, 0px) * var(--hf-depth, 1))
)
scale(var(--hf-from-scale, 1));
filter: blur(calc(var(--hf-from-blur, 0px) * var(--hf-depth, 1)));
}
.hf-remocn-zoom-through-transition .to {
background:
repeating-linear-gradient(
90deg,
rgba(255, 255, 255, 0.05) 0 1px,
transparent 1px 4px
),
var(--hf-l3);
box-shadow: inset 0 0 0 2px var(--hf-accent);
transform: translate(
calc(var(--hf-to-x, 0px) * var(--hf-depth, 1)),
calc(var(--hf-to-y, 0px) * var(--hf-depth, 1))
)
scale(var(--hf-to-scale, 1));
clip-path: inset(
var(--hf-clip-top, 0%) var(--hf-clip-right, 100%) var(--hf-clip-bottom, 0%)
var(--hf-clip-left, 0%)
);
}
.hf-remocn-zoom-through-transition .fx {
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.75), transparent);
mix-blend-mode: screen;
transform: translateX(var(--hf-fx-x, -110%));
}
.hf-remocn-zoom-through-transition .rgb {
position: absolute;
inset: 0;
pointer-events: none;
opacity: var(--hf-rgb-opacity, 0);
background: linear-gradient(
90deg,
rgba(239, 68, 68, 0.35),
transparent,
rgba(34, 211, 238, 0.35)
);
transform: translateX(var(--hf-rgb-x, 0px));
}
</style>
<div class="hf-remocn-zoom-through-transition">
>
<div class="scene from">Before</div>
<div class="scene to">Zoom Through Transition</div>
<div class="fx"></div>
<div class="rgb"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
(function () {
"use strict";
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
// Unrecognised overrides return to their declared defaults.
var depths = { close: 0.55, standard: 1, far: 1.7 };
// Each choice routes to a different contract token so the variable stays
// meaningful under a theme rather than collapsing into one themed accent.
var HAIRLINE = "rgba(255, 255, 255, 0.28)";
var accents = {
blue: "var(--accent, " + HAIRLINE + ")",
green: "var(--brand, " + HAIRLINE + ")",
violet: "var(--accent-2, " + HAIRLINE + ")",
};
function pick(table, value, fallback) {
return Object.prototype.hasOwnProperty.call(table, value) ? value : fallback;
}
function label(value) {
return typeof value === "string" && value !== "" ? value : null;
}
var depth = depths[pick(depths, vars.depth, "standard")];
var accent = accents[pick(accents, vars.accent, "blue")];
var fromLabel = label(vars["from-label"]);
var toLabel = label(vars["to-label"]);
var roots = document.querySelectorAll(".hf-remocn-zoom-through-transition");
var mountRoot = document.getElementById("root");
for (var i = 0; i < roots.length; i += 1) {
var root = roots[i];
root.style.setProperty("--hf-depth", String(depth));
root.style.setProperty("--hf-accent", accent);
var fromScene = root.querySelector(".from");
var toScene = root.querySelector(".to");
if (fromLabel !== null && fromScene) {
fromScene.textContent = fromLabel;
}
if (toLabel !== null && toScene) {
toScene.textContent = toLabel;
}
}
// The motion used to live only in this file's demo page, so an installed
// copy rendered the finished pose and never moved. The timeline belongs
// with the markup it drives: build it here, paused, and register it under
// this composition's own id so the runtime seeks it frame by frame.
if (!mountRoot || !window.gsap) return;
var SELF = ".hf-remocn-zoom-through-transition";
var duration = Math.max(0.001, parseFloat(mountRoot.dataset.duration || "3"));
// The push reads as a transition only with a beat of the outgoing scene
// before it. Clamped so the wipe always lands inside the duration.
var PUSH = 0.9;
var START = Math.max(0, Math.min(0.35, duration - PUSH));
var tl = window.gsap.timeline({ paused: true });
tl.fromTo(
SELF + " .to",
{ "--hf-clip-right": "100%", "--hf-to-x": "60px", "--hf-to-scale": 1.04 },
{
"--hf-clip-right": "0%",
"--hf-to-x": "0px",
"--hf-to-scale": 1,
duration: PUSH,
ease: "power3.inOut",
},
START,
);
tl.fromTo(
SELF + " .from",
{ "--hf-from-x": "0px", "--hf-from-scale": 1, "--hf-from-blur": "0px" },
{
"--hf-from-x": "-70px",
"--hf-from-scale": 0.96,
"--hf-from-blur": "5px",
duration: PUSH,
ease: "power3.inOut",
},
START,
);
tl.fromTo(
SELF + " .fx",
{ "--hf-fx-x": "-110%" },
{ "--hf-fx-x": "110%", duration: 0.58, ease: "power2.inOut" },
START + 0.18,
);
// repeat/yoyo resolves from absolute time on every seek, so the strobe is
// reproducible at any frame rather than accumulated across ticks.
tl.fromTo(
SELF + " .rgb",
{ "--hf-rgb-opacity": 0, "--hf-rgb-x": "-16px" },
{
"--hf-rgb-opacity": 1,
"--hf-rgb-x": "16px",
repeat: 3,
yoyo: true,
duration: 0.07,
ease: "steps(1)",
},
START + 0.32,
);
// HOLD: the incoming scene rests for whatever is left of the duration.
tl.set({}, {}, duration);
tl.seek(0);
window.__timelines = window.__timelines || {};
window.__timelines["zoom-through-transition"] = tl;
})();
</script>
</div>
</template>
</body>
</html>
```
</Accordion>
## Usage
Open `compositions/components/zoom-through-transition.html` and paste its contents into your composition. See the comment header in the file for timeline integration guidance.
{/* hf:generated-footer */}
Tagged `transition-primitive` `remocn-port`.
## Related topics
- [Browse the complete Catalog](/catalog)
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
- [Build a richer composition](/go-further)