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..dc83da5e3ae
--- /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 does no per-frame blur work; the one-time discovery scan still runs on first play.
+
+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. We saw Chrome 157 and 158 expose the API without a flag, but only in headless and Chrome-for-Testing builds; whether it ships in stable Chrome is unverified. Chrome 155 and earlier need `chrome://flags/#html-in-canvas`. Preview detects the API and plays sharp when it is missing, so check your own browser rather than relying on a version number.
+
+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. This covers every defect the timeline scan can see. A defect the scan misses (a WebGL canvas added late, a root shake between scan samples, a face that flips hidden briefly) is caught per frame instead: that one frame renders sharp, blur resumes on the next, and the count is reported as `failedFrames`.
+
+| 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, plus a session-end line when any frames fell back to sharp (`failedFrames > 0`).
+
+## 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 add only the one-time discovery scan (tens of milliseconds on a 25-mover composition).
+- **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 `