feat(lint): add gsap_non_transform_motion rule, migrate registry comps to transforms

Animating layout properties (left/right/top/bottom/margin*) or using
roundProps snaps motion to integer device pixels. Under the seek-by-frame
capture engine this is invisible at high per-frame deltas (fast tweens) but
visibly stutters at low deltas (slow tweens / ease-out tails): sub-pixel
movement rounds to the same pixel for several frames, then jumps a whole
pixel. Transforms (x/y/scale) interpolate sub-pixel and stay smooth.

New rule gsap_non_transform_motion (error):
- Sources timeline tweens from the acorn parser's full animation list, so
  label- and relative-positioned tweens (tl.to(..., "label") / "+=1") are
  covered and real AST keys avoid phantom-prop / nested-brace misreads.
- Exempts html-in-canvas elements (a <canvas layoutsubtree> ancestor): they
  are rasterized from sub-pixel getComputedStyle and do not integer-snap.
  Per-element resolution, so a grouped glass+text tween still flags the
  plain-DOM half. roundProps is never exempt (it rounds before the style).
- Uses Object.hasOwn for the layout-prop lookup, skips set(), and merges
  standalone gsap.* calls.

Migrated 9 registry compositions off layout-prop animation onto transforms,
each render-verified visually identical:
- liquid-glass-{notification,widgets,media-controls,context-menu}: plain-DOM
  text overlays -> transforms; glass panels stay on left/top (canvas-exempt).
- vignelli: curtain wipes left:% -> xPercent.
- lt-mask-reveal: sweep left:% -> x px.
- flowchart, flowchart-vertical, decision-tree: cursor paths left/top -> x/y.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-06-29 09:46:02 -07:00
co-authored by Claude Opus 4.8
parent c811a2750a
commit 619a603eab
11 changed files with 498 additions and 61 deletions
@@ -338,8 +338,8 @@
tl.to(
"#cursor",
{
left: 450,
top: 540,
x: -1470,
y: -540,
duration: 1,
ease: "power1.inOut",
},
@@ -409,7 +409,7 @@
tl.addLabel("hold6", typingEnd + 0.5);
// 16. Cursor moves slightly and clicks away to deselect
tl.to("#cursor", { left: 500, top: 580, duration: 0.3, overwrite: "auto" }, "hold6");
tl.to("#cursor", { x: -1420, y: -500, duration: 0.3, overwrite: "auto" }, "hold6");
tl.to(
"#cursor",
{ scale: 0.8, duration: 0.05, yoyo: true, repeat: 1, overwrite: "auto" },