Skip to content

feat(registry): add five video primitives - #3116

Open
miguel-heygen wants to merge 4 commits into
mainfrom
feat/video-primitives-batch
Open

feat(registry): add five video primitives#3116
miguel-heygen wants to merge 4 commits into
mainfrom
feat/video-primitives-batch

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

Five video primitives in one PR. Supersedes #3108, #3109, #3110, #3112 and #3113, which are closed in favour of this.

Block What it is
cosmic-orb A nebula sphere behind a refracting glass limb
gallery-tunnel A corridor of panels flying toward camera
lt-neon-border Two arcs of light travelling a rounded panel, snapping corner to corner
weight-wave A crest of thickness moving along a headline
halftone-field A dot grid tracking a flowing noise field

19 files, +3104/-235.

Why these five

Chosen against the catalog's gaps rather than by what was cheapest to build. We are thick in passes, wipes, kinetic type, lower thirds and captions, and thin in hero objects, depth and texture.

  • cosmic-orb is a hero object, a shape we had almost none of. It carries a title card by itself and loops indefinitely.
  • gallery-tunnel is the cheapest real depth win available, and uniquely its panels are content slots — a caller supplies their own screenshots — so it carries a shot rather than decorating one.
  • lt-neon-border: we ship eleven lower thirds and not one had a travelling light.
  • weight-wave: every other kinetic type move we own animates transform, opacity or a mask. This animates the letterforms themselves.
  • halftone-field: we had no texture register at all, and this runs under type without competing with it.

How

All five are closed form: state at frame N is computed from N, never from frame N-1. That is what makes them seekable, and it is the reason each is a rewrite rather than a port — the reference implementations are all requestAnimationFrame accumulators, which render frame 0 forever under a seeking renderer.

The rewrites, concretely: lt-neon-border's corner traversal is floor and frac of t/beat, bit-exact. cosmic-orb's spin collapses to a constant rate. halftone-field is uTime = frame/fps with no state at all. gallery-tunnel keys every choice off a hash of the absolute segment number. weight-wave's per-character weight is a function of distance from a sweeping crest, so stagger falls out of the geometry.

Two framework defects found while building these

Both are fixed in the blocks that hit them, and both are worth knowing about beyond this PR:

A timeline onUpdate callback does not fire on seek(). GSAP defaults suppressEvents to true, so a canvas driven that way freezes on frame 0 under any consumer that seeks. Proven directly: t=0, t=6.0 and t=6.2 produced byte-identical pixels. The repaint hangs off a property setter on the tweened driver instead, since tweened values are always written during render. Note this is the pattern our own registry contributing guide currently recommends.

A CSS-sized image uploads a 1x1 texture. The browser samples the laid-out size, not naturalWidth, so every image panel sampled one dark pixel — invisible against a black backdrop. Isolated with a minimal repro that passed, then bisected against the composition.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)

bun run lint exit 0. Registry item lint passes for all five, 0 errors. hyperframes check passes per block.

check is not a visual gate — a composition can pass it and render nothing — so every block was verified by rendering frames and looking at them, and determinism was verified by hashing rather than by eye. Per block: a direct seek to frame N against stepped playback against an out-of-order scrub, all identical, with a control frame proving the hashes are not just of a static image.

weight-wave's font-axis liveness has a falsifier rather than an assertion: with the font file removed the guard fires and check fails. That matters because the failure mode is silent — every character renders at the default instance while the page looks fine. An ink-only guard would have been a false pass, since the fallback family varies its own ink with weight.

Dependency

weight-wave needs #3107 to land first. Without it the layout audit reports its timeline as static: the sweep fingerprint is rect plus opacity, a font axis touches neither, and Recursive is duplexed so even the element rect stays fixed.

Not covered

Catalog preview assets are not generated for the new blocks; their mdx points at URLs that 404 until scripts/upload-docs-images.sh runs with the publishing profile. Maintainer step.

All WebGL blocks were verified only under software GL; the build box has no hardware GPU.

gallery-tunnel's wall cells are portrait at the default grid, so a 16:9 screenshot letterboxes rather than filling. Deliberate — contain-fit beats squashing — but a caller wanting edge-to-edge artwork will see gutters.

Two arcs of light travel the edge of a rounded panel and snap corner to
corner, trailing a three-layer bloom. We ship eleven lower thirds and none
of them has a travelling light.

The traversal is closed form rather than accumulated, which is what makes
it seekable: total = t/beat, corner = floor(total), and the arc head is
the eased interpolation between two corner positions, each a pure function
of an integer. Seeking to frame N and playing to frame N produce the same
bytes, verified by hash across two separate browser launches with a
control frame to rule out a static image.

Arcs are SVG dash offsets rather than sampled polylines, which is exact
along a path and removes the need for a minimum-arc clamp. The bloom
widens the stroke before blurring, since blur alone reads as haze rather
than emitted light.

Nine variables, every number carrying explicit min, max and step.
Five blocks aimed at the gaps in the catalog rather than at what was
easiest to build. We are thick in passes, wipes, kinetic type and lower
thirds, and thin in hero objects, depth and texture.

cosmic-orb, a nebula sphere behind a refracting glass limb. A hero object,
a shape the catalog had almost none of; it carries a title card alone.
Analytic rather than raymarched, so cost is flat per pixel.

gallery-tunnel, a corridor of panels flying toward camera. The cheapest
real depth win available, and the only one whose panels are content slots,
so it carries a shot rather than decorating one.

lt-neon-border, two arcs of light travelling a rounded panel and snapping
corner to corner. We ship eleven lower thirds and none had a travelling
light.

weight-wave, a crest of thickness moving along a headline. Every other
kinetic type move animates transform, opacity or a mask; this animates the
letterforms themselves. Ships Recursive subset to the two axes it uses.
Requires the sweep-fingerprint fix, since font axes were not part of the
layout audit and a duplexed font removes the last trace of motion.

halftone-field, a dot grid tracking a flowing noise field. The catalog had
no texture register, and this runs under type without competing with it.

All five are closed form: state at frame N is computed from N, never from
frame N-1. That is what makes them seekable, and it is verified per block
by hashing a direct seek against stepped playback and against an
out-of-order scrub, with a control frame to rule out a vacuously static
pass. Frames were rendered and looked at rather than inferred from a
passing check, which is not a visual gate.

Two framework defects surfaced while building these and are fixed in the
blocks that hit them. A timeline onUpdate callback does not fire on seek,
because GSAP suppresses events there by default, so a canvas driven that
way freezes on frame 0; the repaint hangs off a property setter instead.
And a CSS-sized image uploads a 1x1 texture, because the browser samples
the laid-out size rather than the natural one.
@mintlify

mintlify Bot commented Aug 8, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview Aug 8, 2026, 4:30 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

var el = document.createElement("img");
el.id = "gt-img-" + k;
el.alt = "";
el.src = src;
All nine of the block's variables were unreachable at init, not just the
one that showed it. The CLI's runtime injection appends its script before
</body>, so it lands after the composition's own inline script and
window.__hyperframes is undefined at the moment the block reads it. Every
variable resolved to undefined on the play path and on a raw file open.

Eight were masked by hardcoded JS fallbacks that silently duplicated the
declared defaults, so they rendered correctly while ignoring every
override. That masking is why this survived review: only pulseEnvelope,
which had no fallback, showed a visible symptom.

Proven rather than reasoned: changing the declared hue default and
rendering produced a byte-identical frame beforehand, and a different one
after.

The authored data-composition-variables attribute is now the single owner
of declared defaults, with the CSS custom property and the runtime bag
layered on top as overrides. Same pattern as gallery-tunnel, which hit
this first.

The underlying injection-order defect is untouched and affects any
composition reading getVariables() at init.
Its registry-item.json carried cosmic-orb's manifest verbatim: same name,
title, description and files entry. The composition HTML was always
correct; only the manifest was wrong.

The catalog generator keys off the manifest name, so it read one identity
from two directories. Cosmic Orb was emitted twice into the nav and Neon
Border never appeared at all, with no page generated for it. Neither block
was broken, and nothing in lint caught it because both manifests were
individually valid.

The replacement is derived from the composition rather than hand-copied:
nine params read out of its declared data-composition-variables, and
dimensions and duration read off the root attributes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants