mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
## Summary
Two correctness fixes in the HDR transform & clipping pipeline: `parseTransformMatrix` now handles `matrix3d(...)` (GSAP's default `force3D: true`), and shader-transitions sets every non-first scene to `opacity: 0` at `t=0` so the engine doesn't over-composite at the start.
## Why
`Chunk 4` of `plans/hdr-followups.md`. Transform extraction and border-radius computation existed but were dead — an HDR video with `rotation: 45` rendered un-rotated, and 3-scene compositions ghosted at `t=0` because every scene defaulted to CSS `opacity: 1` and contributed to the first frame.
## What changed
**Matrix3d support in `parseTransformMatrix`.** `DOMMatrix.toString()` emits `matrix3d` whenever any ancestor in the chain has used a 3D transform — most importantly GSAP's default `force3D: true`, which converts `translate(...)` into `translate3d(..., 0)`. Without this, every GSAP-driven transform was silently dropped during HDR compositing because `videoFrameInjector.getViewportMatrix()` would return `matrix3d(...)` and the blit path would parse it as `null` and fall back to identity. The 16-value column-major form is converted to its 2D affine projection (indices 0, 1, 4, 5, 12, 13 → m11, m12, m21, m22, m41, m42); Z, perspective, and out-of-plane rotation components are dropped.
**Initial-state opacity in `initEngineMode`.** The browser preview branch uses a GL canvas overlay during transitions, so scene opacity at `t=0` doesn't matter visually. The engine branch reads scene opacity directly via `queryElementStacking()` to decide which layers to composite. Without an explicit initial-state tween, every scene defaulted to CSS `opacity: 1` and contributed to the very first frame, causing ghosting/overlap until the first transition fired. `tl.set()` at position 0 anchors the initial state in the timeline graph so reverse seeks from inside a later transition restore it correctly.
These two fixes together make `el.transform` and `el.borderRadius` (already wired in Chunk 7A's `compositeHdrFrame`) actually flow through the GSAP-animated case, and keep the engine's per-frame compositing aligned with what the user sees in browser preview.
## Test plan
- [x] 6 new `alphaBlit.test.ts` cases (identity matrix3d, translate3d, scale + translate3d, rotateZ, malformed arg count, non-finite values).
- [x] Existing `hdr-regression` Window H already CSS-sets `#scene-b { opacity: 0 }` as a fallback; the new `tl.set` is redundant for that case but harmless and removes the need for compositions to remember the CSS workaround.
- [x] Manual: rotated HDR video (`rotation: 45`) appears rotated; `border-radius: 50%` clips to circle; 3-scene composition has no overlap at `t=0`.
## Stack
Chunk 4 of `plans/hdr-followups.md`. Window F of the regression suite documents the bug; the next PR in the stack tightens the `maxFrameFailures` budget to 0.
@hyperframes/shader-transitions
WebGL shader transitions for HyperFrames compositions. Renders GPU-accelerated scene-to-scene transitions using fragment shaders, driven by GSAP timelines.
Install
npm install @hyperframes/shader-transitions
Or load directly via CDN:
<script src="https://cdn.jsdelivr.net/npm/@hyperframes/shader-transitions/dist/index.global.js"></script>
Usage
import { init } from "@hyperframes/shader-transitions";
const tl = init({
bgColor: "#0a0a0a",
accentColor: "#ff6b2b",
scenes: ["scene-1", "scene-2", "scene-3"],
transitions: [
{ time: 3, shader: "domain-warp", duration: 0.8 },
{ time: 8, shader: "light-leak", duration: 0.7 },
],
});
The init() function captures each scene to a WebGL texture at transition time, crossfades between them using the selected shader, and returns a GSAP timeline. If WebGL is unavailable, it falls back to hard cuts.
With an existing timeline
Pass your own GSAP timeline to layer transitions onto it:
const tl = gsap.timeline({ paused: true });
// ... add your scene animations ...
init({
bgColor: "#000",
scenes: ["intro", "demo", "outro"],
transitions: [
{ time: 5, shader: "cinematic-zoom" },
{ time: 12, shader: "glitch", duration: 0.5 },
],
timeline: tl,
});
Available shaders
| Shader | Description |
|---|---|
domain-warp |
Organic noise-based warp with glowing edge |
ridged-burn |
Ridged noise burn with sparks and heat glow |
whip-pan |
Horizontal motion blur simulating a fast camera pan |
sdf-iris |
Circular iris wipe with glowing ring edge |
ripple-waves |
Concentric ripple distortion radiating from center |
gravitational-lens |
Warping gravity well with chromatic aberration |
cinematic-zoom |
Radial zoom blur with chromatic fringing |
chromatic-split |
RGB channel separation expanding from center |
glitch |
Digital glitch with block displacement and scanlines |
swirl-vortex |
Spiral rotation with noise-based warping |
thermal-distortion |
Heat shimmer rising from the bottom |
flash-through-white |
Flash to white then reveal the next scene |
cross-warp-morph |
Noise-driven morph blending both scenes |
light-leak |
Warm cinematic light leak with lens flare |
API
init(config): GsapTimeline
| Option | Type | Required | Description |
|---|---|---|---|
bgColor |
string |
yes | Fallback background color (hex) for scene capture. Use the composition's body/canvas background — individual scenes set their own background-color via CSS. |
accentColor |
string |
no | Accent color (hex) for shader glow effects |
scenes |
string[] |
yes | Element IDs of each scene, in order |
transitions |
TransitionConfig[] |
yes | Transition definitions (see below) |
timeline |
GsapTimeline |
no | Existing timeline to attach transitions to |
compositionId |
string |
no | Override the data-composition-id for timeline registration |
TransitionConfig
| Option | Type | Default | Description |
|---|---|---|---|
time |
number |
— | Start time in seconds |
shader |
ShaderName |
— | Shader name from the table above |
duration |
number |
0.7 |
Transition duration in seconds |
ease |
string |
"power2.inOut" |
GSAP easing function |
SHADER_NAMES
Array of all available shader name strings, useful for validation or building UIs.
import { SHADER_NAMES } from "@hyperframes/shader-transitions";
// ["domain-warp", "ridged-burn", "whip-pan", ...]
Distribution
| Format | File | Use case |
|---|---|---|
| ESM | dist/index.js |
Bundlers (Vite, webpack, etc.) |
| CJS | dist/index.cjs |
Node.js / require() |
| IIFE | dist/index.global.js |
<script> tag, CDN (global: HyperShader) |
All formats include source maps. TypeScript definitions included.
Related packages
@hyperframes/core-- types, parsers, runtime@hyperframes/engine-- rendering enginehyperframes-- CLI
License
MIT