This site was loading two complete animation runtimes on every page.
Not two utilities. Two engines: GSAP (with ScrollTrigger and SplitText) and Motion. Measured from the served bytes, they came to about 240 KB raw and 92 KB compressed — roughly a third of all the JavaScript on the site.
Both were genuinely used, which is what made it easy to miss. Neither was dead code. It just never got asked whether it needed to be there.
What the second engine was actually doing
When we went through it, Motion's job on this site came down to six whileInView reveals, one whileTap cursor change, an exit animation on the mobile menu, and a draggable card stack.
That is a scroll reveal and a menu transition. The browser has done both natively for years.
So we replaced them:
- Reveals became one
IntersectionObserverand two CSS classes. About a kilobyte of behaviour instead of two chunks. - The mobile menu became a render flag plus a CSS transition. The staggered link fade is a
transition-delayper item. - The card stack became pointer events, which had a bonus: it is keyboard-operable now. Enter sends the focused card to the back. The library version was a focus stop with nothing to do.
GSAP stayed. It is doing the work that is genuinely hard — splitting a heading into animatable pieces, tying a timeline to scroll position.
The part that mattered more than the bytes
While we were in there, we found something worse than the payload.
The reveals started from opacity: 0 written inline on 33 elements. Not a class — an inline style, in the server-rendered HTML. Which means that if JavaScript failed, or was slow, or the visitor had scripting off, those 33 elements stayed invisible. The homepage's own <h1> shipped with visibility: hidden.
Then a scroll library was responsible for making the content readable.
That is the wrong way round. Content should be visible by default and hidden only when something is definitely there to reveal it. So we inverted it: the hiding rule is scoped to a .js class that a tiny inline script adds, and the reduced-motion media query pins everything visible. If any of that fails, the page still reads.
What it costs now
The two Motion chunks are gone — about 42 KB compressed off every page load, plus the parse and compile time for an entire animation engine that no longer runs. The reveals still work, including for people who ask their system for less motion.
If you want the same result on your own site, the question to ask is not "is this library good". Both of these are good. The question is: what is this library doing that the platform cannot? If the answer is "a fade on scroll", you can delete it.