#26

feat: autozoom — cinematic zoom-in for recorded interactions

Openlgariv-dnwants to merge intofrom

https://github.com/user-attachments/assets/309e903f-f9c2-4bb3-92be-0c1938ca2bfc

Summary

Adds an opt-in autozoom behavior: during recording, the camera eases into each click or keystroke target, holds through the interaction, and releases back to the full viewport. The result matches the feel of a human-produced product walkthrough without any manual direction.

Enable with one line:

{ "autoZoom": true }

Tune with an object if you want to override defaults:

{
  "autoZoom": {
    "approachS": 0.5,
    "holdAfterS": 0.3,
    "sessionGapS": 4.0,
    "minZoomRatio": 0.6
  }
}

Try it without waiting for this PR to merge

Published to npm as a scoped fork (Apache-2.0, fork-notice in each package's README):

npm install -g @lgariv/webreel
# then: webreel record  (same CLI interface, plus autoZoom)

How it works

  • Each interaction step produces a ZoomEvent with the target's bbox and the SoundEvent timestamp of the actual click/keystroke (not the step's postDelay end, which lags by 1–2 s with cursor animation).
  • For type steps, holdUntilMs extends the camera hold through the last keystroke so typing is always visible.
  • Events within sessionGapS form a single pan session; the camera doesn't release between them.
  • Inside a session, spatial sub-grouping frames targets whose union bbox fits one crop together (Cursor's "show the whole widget" pattern) instead of panning between individual targets.
  • A mutation observer runs in the recorded page during click/drag steps and collects elements that appear or become visible. Their bboxes are unioned with the click target so opening a dropdown / modal / tooltip brings the whole widget into frame. Visibility is judged by Element.checkVisibility() so opacity-transitioned menus are detected correctly.
  • Smoothstep easing via FFmpeg's st/ld register slots makes each transition a single fluid curve instead of piecewise-linear jumps.

The final ffmpeg zoompan expression is generated at the end of recording and passed to the compositor via the new ComposeOptions.zoomFilter field.

Commit layout

Split into seven conventional commits so each layer can be reviewed independently, bottom-up:

  1. feat(core): add autozoom filter generation — core algorithm + tests
  2. feat(core): detect click-revealed UI via mutation observer — reveal observer + tests
  3. feat(compositor): accept an optional zoompan filter — compositor plumbing
  4. feat(webreel): add autoZoom option to video config — VideoConfig.autoZoom field
  5. feat(webreel): integrate autozoom and reveal capture into runner — step-loop wiring
  6. docs(skill): document autozoom usage and config — SKILL.md + examples.md
  7. feat(examples): add autozoom demo — the demo the preview video above was recorded from

(An eighth commit on this branch renames the packages to @lgariv/* for the npm publish above — that's downstream housekeeping, not part of what upstream would merge.)

Test plan

  • pnpm --filter @webreel/core test — 133 tests pass (includes new autozoom + revealObserver suites)
  • pnpm --filter webreel test — 110 tests pass
  • Record examples/autozoom end-to-end; verify typing is visible, role dropdown menu is framed with its button, save forms its own sub-group
  • WEBREEL_DEBUG_ZOOM=1 logs keyframes and reveal bboxes for tuning
  • Try on a non-toy demo (someone else's config) and confirm defaults are reasonable

Activity

17
vercel[bot]6mo ago

@lgariv-dn is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

vercel[bot]reviewed6mo ago
vercel[bot]6mo ago

Additional Suggestions:

  1. The README code example imports from the old package name @webreel/core instead of the renamed @lgariv/webreel-core, causing a module-not-found error for users following the example.
  1. The fixed versioning group in .changeset/config.json references old package names (@webreel/core and webreel) that were renamed, causing the coordinated version bump to silently become a no-op.

rileyjshaw2mo ago

This is awesome!!