mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-03 20:37:30 +00:00
sync/hyperframes-codegen-8efa202a
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7b85b28406 |
fix(ci): keep the catalog publish PR when main changes nothing it generates (#5372)
* fix(ci): keep the catalog publish PR when main changes nothing it generates The bot compared the whole branch tree with main, so every main push rewrote the standing PR and dismissed its approval. It now compares only generated catalog files, with search vectors judged by the drift check's cosine rule. * fix(ci): republish when main edits generated files or the branch carries others * docs(registry): motion-blur copies explain their inline styles in three lines * refactor(ci): split the kept-snapshot check into named conditions |
||
|
|
b50dead1da |
feat(registry): gooey split block with metaball goo and directional motion blur (#5123)
* feat(registry): gooey split block with metaball goo and directional motion blur A dot splits into a three-dot menu through a gooey neck, spins into a swirl mark under shutter motion blur, breaks back into three dots and merges into one. Timing, spacing and eases are measured off a reference recording frame by frame. The motion-blur snippet gains an opt-in `sharp: 0` option that hides the crisp frame-time copy while an element moves, leaving only the shutter average. The default is unchanged; the inlined copies carry the same text. * feat(registry): grow gooey-split blades out of swelling dots * refactor(registry): tidy gooey-split pose code and document sharp 0 limits * fix(registry): hide a sharp 0 motion-blur target by marker, not its visibility Hiding through the element's own visibility fought timelines that write it (autoAlpha): a target faded in after attach stayed hidden at rest. A marker attribute and one !important rule now do the hiding, the default path writes nothing, and a declarative sharp other than 0 or 1 is refused like any other bad option. * fix(registry): smear a motion-blur target that was hidden when blur attached Copies froze the target's computed visibility at attach, so a target entering with autoAlpha never smeared (and vanished entirely under sharp 0). The group now carries the target's current visibility each frame, read with the sharp 0 marker lifted, and a copy keeps a descendant's visibility only where it differs from its parent's. * test(registry): pin a copy keeping a descendant's own visibility * fix(registry): keep a child's own hiding in the smear of a target hidden at attach * refactor(registry): restore a lifted visibility in one call and lift any hidden form * fix(registry): restore a lifted visibility even when copying styles throws * fix(registry): let motion-blur copies hide with a clip that has ended The smear group set an explicit visibility on every moving frame, and copies used to freeze an explicit visible, so an ancestor clip hiding after the last update could not hide them: copies from a 0-4 s sub-composition stayed on screen at 16.4 s. The group now sets a visibility only where the element's differs from its parent's. Also regenerates skills-manifest.json for the motion-blur skill doc. |
||
|
|
752b552789 |
feat(registry): blur an element by marking it, not by calling a function (#4045)
attachMotionBlur(el, tl) has an ordering contract: it has to run after every tween is defined and before the composition registers its timeline. Getting it wrong makes the blur silently do nothing, which is the worst shape a failure can take in a renderer nobody watches. An attribute has no order to get wrong. data-hf-motion-blur on any element is now the contract. Empty means defaults; anything else is a JSON object of the same options. The element finds its own timeline: its nearest [data-composition-id] ancestor names the key, and the snippet reads window.__timelines[key]. attachMotionBlur stays exported for a target built after the page has settled. claim(el) is the only thing that attaches, and two triggers call it. An accessor on window.__timelines wraps the registry in a Proxy whose set trap sweeps on every per-key registration, which is what attaches before the renderer captures frame zero. A bounded poll, 32 ms by 250 ticks, reads the registry directly and needs no cooperation from whoever wrote it. The poll is not belt and braces: a registry reference captured before the snippet installs bypasses the trap outright, and a composition that mounts its DOM after registering its timeline has no marked element to sweep at trap time. It runs its whole budget every time rather than stopping the first tick nothing is pending, because on an ordinary page the top-level target is claimed on the registration write, so an early exit kills the poll one tick in, before any late composition arrives. Every refusal is by name, once, and never silent: a value that is not JSON, a value that parses but is not an options object, an option name that is not one of the four, an option value that is not a finite number, a target inside another target, and a second timeline registered under one key. Parsing runs before the timeline lookup, so a malformed value is reported as malformed rather than as a composition that never registered. Two properties of the primitive this change made load-bearing: - Frame rate is resolved from the target's own composition root, not from the first root in the document. The document-wide lookup was survivable while every caller could pass fps explicitly; the declarative form has no per-call argument. Two compositions on one page at different rates now each get their own shutter window, and one call naming both says it cannot. - An attach is all-or-nothing. The mark, the copies and the resize observer go in together and come back out together, so a throw partway through leaves the element untouched rather than marked blurred with copies in the page and no tracker driving them. The sweep is also bounded per element, because the write trap runs it inside the author's own registration statement. Copies are stripped of data-hf-motion-blur, data-composition-id and data-fps, on the clone and every descendant: the first would make a copy a target of its own, and the other two would let a copy win the fps lookup, since copies are inserted before the original. skills/hyperframes-animation/references/motion-blur.md is the agent-facing half, mirroring how colour grading is surfaced. It leads with when NOT to blur, because the attribute makes blurring everything trivial and a smear on motion that was never fast enough reads as a soft, cheap render. Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> Co-authored-by: Vance Ingalls <vance@heygen.com> Co-authored-by: Claude Code <noreply@anthropic.com> |
||
|
|
82f49262ec |
feat(registry): blur every transform driver, and ship the reference case as shutter-slam (#4038)
The motion-blur snippet integrated translation only. Its output stage was one SVG filter per target whose copies were feOffset, and feOffset only translates, so a beat that only scaled or only turned sampled a displacement of zero and rendered sharp. No SVG filter primitive can give a copy its own affine transform, so the sampling loop was never the problem: the output stage was. Copies are now DOM clones of the element in an isolation:isolate group, each at 1/N opacity with mix-blend-mode plus-lighter, each carrying the element's resolved transform at its sample time. plus-lighter adds premultiplied colour, so N + 1 copies at 1/N sum to the average the shutter integral asks for; the isolation is load-bearing rather than tidy, because without a transparent backdrop of its own the first copy adds onto the page and blows a light background out to white. Reading the resolved matrix instead of a list of named GSAP properties is what makes every driver work at once: translation, scale, rotation, 3D rotation and skew arrive in the same one value. Measured against the After Effects export the shutter model was fitted to, per beat, previous stage to this one: scale +0.62 dB, rotate X +0.43, rotate Y +0.84, rotate Z +1.15, translate X -0.18, translate Y -0.04. The two small translate regressions are inherent and not tunable: the filter offset one rasterisation so every copy was pixel-identical, where each copy is now rasterised independently at its own fractional transform with its own subpixel antialiasing. Three consequences of the new stage, each carrying its own fix: - mix-blend-mode flattens preserve-3d, so a copy cannot inherit the parent's 3D context. Each copy carries the parent's perspective as its own first transform function instead. - A copy is styled by nothing that selected the original, since dropping the id also drops every #id rule that gave it size, colour and font. Each copy carries its own resolved style inline, read once and replayed. Those values are px, so a container-relative element needs them again when its box changes: a ResizeObserver re-snapshots rather than paying a subtree walk every frame. - A beat that moved and faded at once kept a full-strength smear behind a vanishing element. The element's opacity now rides the group; the 1/N weight stays on the copies. The axis option is gone. It selected which of x and y to sample, a question the resolved matrix does not have. shutter-slam is the new component: one word through six single-property beats, two translate slams, a scale punch, then a full turn about each axis, which exercises every driver the model covers in one mount. Easing is power2.inOut because it measured best, not because it was the default. A bezier fitted to the reference's own bbox centres looks like the better curve and renders worse: a blurred bbox centre is the shutter window's average rather than the instant, so on an accelerating segment the fit measures its own blur. Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> |