From 70e845301a4d4832a23b72f2872784cdca01dc71 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Wed, 21 Oct 2026 15:44:13 -0700 Subject: [PATCH 1/2] docs: guide and skill note for opt-in capture motion blur Co-Authored-By: Claude Sonnet 5.5 --- docs/docs.json | 1 + docs/guides/motion-blur.mdx | 87 +++++++++++++++++++++++++ skills-manifest.json | 2 +- skills/hyperframes/references/motion.md | 1 + 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 docs/guides/motion-blur.mdx diff --git a/docs/docs.json b/docs/docs.json index 93c96f02c0f..90b5c44470f 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1001,6 +1001,7 @@ "concepts/frame-adapters", "concepts/determinism", "guides/html-in-canvas", + "guides/motion-blur", "guides/vfx-chain", "guides/figma" ] diff --git a/docs/guides/motion-blur.mdx b/docs/guides/motion-blur.mdx new file mode 100644 index 00000000000..e3deb18a8c6 --- /dev/null +++ b/docs/guides/motion-blur.mdx @@ -0,0 +1,87 @@ +--- +title: "Motion Blur" +description: "Smear fast-moving elements along their path, in Studio preview and in renders. Off until you turn it on." +--- + +Capture motion blur gives fast-moving elements a real shutter smear. For each frame, HyperFrames reads where every mover was across the shutter interval and redraws it along that path. A box sliding across the frame leaves a streak instead of a sharp copy at one position. + +It is **opt-in**. Nothing blurs until you add `data-motion-blur="on"` to the composition root. + +## Turn it on + +```html +
+ ... +
+``` + +That is all. The same attribute drives Studio preview and `hyperframes render`, so what you see is what you render. + +| Attribute | Default | Meaning | +| --- | --- | --- | +| `data-motion-blur` | none | `on` enables blur. `off` opts out, on the root or on any subtree. | +| `data-motion-blur-shutter` | `180` | Shutter angle in degrees, 0–720. Larger is a longer smear. | +| `data-motion-blur-samples` | `16` | Sub-frame samples per frame, 2–64. More is smoother and slower. | +| `data-motion-blur-events` | off | `on` also runs timeline callbacks (`onUpdate` and similar) at each sample. Use it when the motion is driven by callbacks, which blur cannot otherwise see. | + +## What it blurs + +Elements that move quickly in 2D: position, scale and rotation changes from GSAP, CSS animations and transitions, and the Web Animations API. Slow movers and static content are left sharp. A composition with nothing moving is never wrapped and costs nothing extra. + +Blur is an image-space approximation. A mover's box and its contents move together, and glows or shadows outside its box stay sharp. 3D movers are skipped. + +## Requirements + +Blur draws through Chrome's [HTML in Canvas](/guides/html-in-canvas) API. + +- **Renders** use the render browser, which HyperFrames launches with the API enabled. +- **Studio preview** uses your own browser. Chrome 157 and 158 builds expose the API without a flag. Chrome 155 and earlier need `chrome://flags/#html-in-canvas`. Without the API, preview plays sharp. + +If Studio's browser cannot blur, Studio tells the render, and the render also skips blur so the output matches what you saw. + +## When blur turns itself off + +html-in-canvas captures a few things wrongly. A wrongly blurred frame is worse than a sharp one, so when HyperFrames finds one of these it turns blur off **for the whole composition, from the first frame**, instead of switching mid-render. + +| Found in the composition | Why | +| --- | --- | +| A WebGL or WebGPU canvas anywhere in the document | Captured frozen. | +| `backface-visibility: hidden` inside a 3D context (including from an animation) | Drawn mirrored. | +| A transform on the root element | Ignored by the capture. | +| Opacity below 1, or `mix-blend-mode`, on the root element | Not applied by the capture. | +| An `iframe`, `object`, `embed` or custom element in the root | Not captured faithfully. | +| A nested capture canvas, such as a VFX block | Hangs the renderer. | +| An HDR render | The HDR compositor's layer mask does not reach the blur canvases. | +| A distributed render | Chunks cannot share the single-page capture. | + +Large see-through movers (a full-frame translucent sweep, say) are skipped too, because blurring them would drag the content underneath along. Their fast children still blur. + +### Find out why it is off + +```bash +npx hyperframes check +``` + +| Finding | Severity | Meaning | +| --- | --- | --- | +| `motion_blur_off` | warning | Blur is on but was switched off. The message names the cause and a fix. | +| `motion_blur_no_movers` | info | Blur is on but nothing moves fast enough to blur. | +| `motion_blur_unverified` | info | The check could not confirm blur in this browser. | + +In the page, `window.__hfMotionBlur.report()` returns `state`, `reason`, `reasonCode` and `movers`. A render also logs one line per session saying whether blur ran and why not. + +## Cost and limits + +- **Render time.** About 1.7× for a blurred composition (median over 20 real compositions). Compositions where blur is off, or nothing moves, cost the same as before. +- **Editing.** The root is wrapped in the capture canvas only while playing or rendering. Paused, it is plain DOM, so click-to-select in Studio works as usual. +- **Preview rate.** Blurred preview can run at half the frame rate on heavy compositions. +- **Video.** A `