Performant motion with Claude Code: 60fps, reduced-motion, and Core Web Vitals (with the aino.agency case)
Performant motion with Claude Code: transform-and-opacity rule, tiered reduced-motion, Lenis with INP control, MCP audits, and the aino.agency case.
- motion performance 60fps accessibility
- 60fps transform opacity

Contents
- Why does motion drop 60fps and hurt Core Web Vitals?
- How do I isolate transform and opacity with Claude Code?
- How do I honor prefers-reduced-motion in tiers?
- How do I tune Lenis without hurting INP?
- What does the aino.agency case prove about little JS?
- How do I audit motion with Playwright MCP, Chrome DevTools MCP, and Context7?
- When do I cut animation instead of optimizing?
- Frequently asked questions
- Conclusion: rules, reduce branches, and audits before publishing
- Sources
Motion breaks publishing at three predictable points. Scroll loses fluidity because the wrong property recomputes layout every frame. Pages shift because pins and reveals enter with no reserved space. Part of the audience receives animation they asked never to see, because no static alternative covers prefers-reduced-motion.
This guide treats all three as acceptance criteria, with a property rule, a tiered reduce branch, Lenis tuned with INP under control, and in-browser audits with Claude Code-ready commands and prompts. For the library map plus the per-job decision rule, return to the complete motion guide.
Why does motion drop 60fps and hurt Core Web Vitals?
Wrong properties cost frames, layout, and response. Transform and opacity pass through compositing, recomputing no boxes and repainting no full layers. Top, left, width, height, and margin trigger reflow, which recomputes position and size and sinks frame rates on long lists and pinned scroll. That is the standing rule of the 60fps-animation skill in the iart-ai repo, per the iart-ai repo.
The Core Web Vitals tie runs direct. LCP worsens when an animated hero delays the largest visible content. CLS worsens when pins enter with no reserved space or reveals swap heights with no placeholder. INP worsens when inertial smooth, heavy listeners, and long animations delay click and touch answers. The lib analysis with a React and Next.js table, tying Lenis, lerp, and INP together, sits in snigdha from Jun 18, 2026.
The fix starts before code, with three limits. Short durations for gesture confirmation. Reserved space for every pin and reveal. No essential content trapped in motion with no alternative. Those limits turn into an auditable checklist in the audit section, verified through in-browser LCP, CLS, and INP.
What to measure before optimizing
Measure LCP on the hero, CLS on pins and reveals, INP on animated buttons and filters. If LCP climbs with intros, CLS climbs with pins, or INP climbs with smooth scroll, the trouble sits in the motion architecture, never in easing micro-tuning.
How do I isolate transform and opacity with Claude Code?
Skills set the standard, prompts lock the run. The 60fps-animation skill in the iart-ai set teaches the 60fps pattern with transform and opacity, with reflow-free hero, transition, and micro-interaction examples, per the iart-ai repo. Without that lock, the model blends top with translate and width with scale, creating two sources of truth for one move.
Prompts work better with an explicit ban. Describe the component, the allowed property, the banned property, centralized duration and easing. One direct hero example: "Build this hero animation using only transform and opacity, no top, left, width or height animation." Then add selectors, translate and scale values, mobile behavior. For React micro-interactions, the complement is transform layout, as the layout docs show on motion.dev.
// Allowed: compositing without reflow
gsap.to(".card", { x: 24, scale: 1.04, opacity: 1, duration: 0.28, ease: "power2.out" });
// Banned in this guide: recomputes layout
// gsap.to(".card", { left: 24, width: 320 });Centralize duration and easing tokens in one file. Short durations for hover and press, medium for section entrances, no long timelines for click confirmations. Once Claude Code receives ready tokens, it stops inventing curves per section and motion gains steady rhythm.
How do I honor prefers-reduced-motion in tiers?
Every meaningful animation needs a legible static equivalent. The legal and technical base is WCAG 2.3.3, covering interaction-triggered animation and demanding a switch to kill nonessential movement. The accessible-animation skill in the same iart-ai repo organizes that into a tiered model, with reduction levels per severity instead of all-or-nothing, per the iart-ai repo.
The reach number explains why that branch is no detail. Past 50% of mobile sites already honored prefers-reduced-motion in 2024, in a Web Almanac reading cited through annnimate. At that adoption level, publishing with no reduce branch means publishing outside the standard half the mobile web already follows.
With GSAP, the split uses gsap.matchMedia. One branch serves no-preference with full pin, scrub, and parallax. The other serves reduce with visible content, no pin or scrub.
import gsap from "gsap";
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
// full pin, scrub, and parallax here
});
mm.add("(prefers-reduced-motion: reduce)", () => {
gsap.set(".pinned-hero, .reveal, .layer-back", { clearProps: "all", opacity: 1, y: 0 });
});With Motion in React, the split uses MotionConfig with reducedMotion="user" plus the useReducedMotion hook to fork variants. The pattern sits in the docs on motion.dev and follows the same matchMedia logic, with a static version losing no information.
import { MotionConfig, motion, useReducedMotion } from "motion/react";
export function RevealCard() {
const reduce = useReducedMotion();
return (
<MotionConfig reducedMotion="user">
<motion.div
initial={{ opacity: 0, y: reduce ? 0 : 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
/>
</MotionConfig>
);
}The tiered model closes the loop. Tier one removes parallax and scrub. Tier two removes autoplay and loops. Tier three freezes transitions into hard cuts with full content. Every tier holds hierarchy and reading, never hiding prices, CTAs, or form feedback.
How do I tune Lenis without hurting INP?
Lenis lends scroll inertia, and unbounded inertia delays answers. The lerp parameter drives smoothing, with a cited range between 0.05 and 0.15: low values scroll looser and answer slower, high values scroll tighter and answer faster. The analysis tying that range to INP in React and Next.js stacks sits in snigdha from Jun 18, 2026, with the lenis.on(scroll, ScrollTrigger.update) sync pattern also in youngju.dev from May 16, 2026 and adamarant from May 28, 2026.
For reduced-motion users, the exit is stopping the smooth. The lenis.stop call freezes inertia and returns native scroll, which preserves reading and answers for the reduce branch. Pair with matchMedia to switch Lenis on and off alongside pin and scrub.
import Lenis from "lenis";
import { ScrollTrigger } from "gsap/ScrollTrigger";
const lenis = new Lenis({ lerp: 0.1 });
lenis.on("scroll", ScrollTrigger.update);
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
lenis.stop();
}For the full pin, scrub, and parallax base on that bridge, with raf on the GSAP ticker and zeroed lagSmoothing, revisit cinematic scroll with GSAP and Lenis. The working INP rule tests the range with real clicks on filters and menus: if low values delay confirmations, raise lerp or kill smooth on that route and measure again.
What does the aino.agency case prove about little JS?
Aino.agency shows restraint with direction builds identity. The site, SOTD in the Awwwards animation picks, uses ASCII as texture plus plain physics in vanilla, at 30kb of total JS. No animation framework loads the page. Movement answers cursor and scroll in a few lines, and reading follows clear hierarchy.
The useful pattern sits in the economy. One texture technique, ASCII here, plus one short physics pass for gesture answers. No background video, no competing loops, no pins over dense text. Each section requests one gesture and returns one visible change, which holds attention at no bundle cost.
The Claude Code lesson is scope, not tooling. Ask for cheap texture plus short vanilla physics wherever identity allows. Save GSAP, Motion, and vector runtimes for narrative scroll, stateful micro-interactions, and input-driven vectors. When little JS suffices, little JS is the right stack.
How do I audit motion with Playwright MCP, Chrome DevTools MCP, and Context7?
In-browser audits decide what stays live. The flow runs Playwright MCP for navigation and measuring, Chrome DevTools MCP for performance, Context7 for current docs before fixing APIs. That trio's organization, with frontend-design, skill collections, and Figma MCP for handoff, sits in the frontend toolkit for Claude Code.
Playwright MCP runs with automatic vision plus DevTools capacity for LCP, CLS, and INP, through the @playwright/cli package. Chrome DevTools MCP adds performance timelines and traces. Context7 verifies current GSAP, Motion, and Lenis signatures before long correction passes.
npx -y @playwright/cli install --with-deps
npx -y @playwright/cli test --vision auto --caps=devtoolsAudit prompts work better literal, with output per file and line. Two prompts cover the loop:
"Audit this page for transform-only animations, CLS from pins and reveals, INP from smooth scroll and long transitions, and prefers-reduced-motion coverage. Report file, line, failing metric and fix."
"Generate a reduced-motion variant for every animated section with static equivalents, no pin, no scrub and no autoplay, following WCAG 2.3.3. Keep headings, CTAs and form feedback fully visible."
The table below compresses the checklist. Each row carries a visible symptom, likely cause, direct fix, and in-browser verification. It works as Definition of Done before deploy.
| Symptom | Cause | Fix | Verification |
|---|---|---|---|
| Jank on lists and scroll | Top, left, width, or height animation with reflow | Swap to transform and opacity with short durations | Reflow-free timeline in Chrome DevTools MCP, jump-free scroll in Playwright MCP |
| CLS on pins and reveals | Pins with no reserved space, reveals with no placeholder | Reserve pin heights plus reveal placeholders | Zeroed CLS on reload with --caps=devtools plus mobile-viewport tests |
| High INP on clicks and filters | Low lerp with long inertia plus long transitions | Raise lerp inside 0.05 to 0.15 or kill smooth on the route | INP inside bounds on real-click tests through @playwright/cli |
| Animation with no alternative | No reduce branch for prefers-reduced-motion | Add matchMedia with reduce plus MotionConfig reducedMotion="user" | Reduce-on tests show full static content with lenis.stop applied |
| Autoplay below the fold | Players and loops live with no IntersectionObserver | Pause outside the viewport, play only on entry | No live canvas below the fold mid-scroll in traces |
| Loops distracting from forms | Continuous movement near inputs and CTAs | Remove loops from form zones, hold focus visible | Forms complete undistracted with feedback intact |
When do I cut animation instead of optimizing?
Some animations deserve no fine-tuning. Long intros delaying LCP, parallax fighting paragraphs, loops beside forms, transitions hiding click confirmations make that list. The criterion is chore-based: if motion delays reading, confuses state, or spends attention explaining no change, the fix is cutting and remeasuring reading.
The same bar applies to interactive vectors. Gesture-less loops, page-wide racing Rive, hierarchy-free triggers turn into noise and sink reading. For the inputs call, with Rive for states and dotLottie for light loops, revisit Rive vs Lottie with Claude Code. If the vector shifts no state on gestures, swap autoplay for play-on-entry or drop the instance and freeze the opening frame.
Cutting also simplifies audits. Fewer timelines to measure, fewer reduce branches to hold, less JS to load. On over-budget pages, cutting with motive beats optimizing without criteria.
Frequently asked questions
Why do only transform and opacity hold 60fps?
Because they composite with no box recalculation. Top, left, width, and margin trigger reflow and repaint, which costs frames on scroll and lists. The 60fps-animation skill in the iart-ai repo locks that pattern with reflow-free hero and micro-interaction examples.
How do I switch Lenis and scrub off for reduced-motion users?
Use gsap.matchMedia with a reduce branch, call lenis.stop, and ship static content at opacity 1 with no pin. In React, add MotionConfig reducedMotion="user" with useReducedMotion to fork variants, per motion.dev. The Lenis-with-reduced-motion pattern sits in adamarant from May 28, 2026.
How do I audit LCP, CLS, and INP with Playwright MCP?
Run the @playwright/cli package with --vision auto and --caps=devtools to capture LCP, CLS, and INP mid-navigation, and add Chrome DevTools MCP for timelines. The flow with those MCPs plus Context7 for docs sits in the frontend toolkit for Claude Code.
When does cutting motion beat optimizing?
When it delays chores, confuses state, or fights reading while explaining no change. Long intros over LCP, parallax over text, and loops near forms are direct candidates. Cut, remeasure LCP, CLS, and reading, and reintroduce only with motive plus a reduce branch.
Conclusion: rules, reduce branches, and audits before publishing
Performant motion wants three hits in sequence. The transform-and-opacity rule holding 60fps with no reflow. Tiered reduce branches with matchMedia, MotionConfig, and lenis.stop, in a tiered model tied to WCAG 2.3.3 and 50%+ mobile-site adoption. Lenis with lerp between 0.05 and 0.15 plus INP measured on real clicks. The aino.agency case, SOTD with ASCII and vanilla physics at 30kb of total JS, shows restraint with direction sustains awards. Audits with Playwright MCP, Chrome DevTools MCP, and Context7 close the loop with literal commands and prompts.
The next step follows the bottleneck. For scroll with pin and parallax, return to the GSAP-with-Lenis satellite. For stateful vectors, advance to Rive versus Lottie. For the full map, return to the pillar and settle what stays live.
Sources
Read next
Motion •
Motion Design for the Web: The Complete Guide
Scroll, text, images and video: the complete catalog of motion techniques for the web, with implementation in Next.js and the cases where each one pays off.
- motion
- scroll
The definitive guide — a Next.js site built around motion and scroll
The scroll foundation that, when missing, keeps the animations from working at all: Lenis, GSAP and Next.js wired in the right order and the mistakes to avoid.
- next.js
- lenis
Infra •
Documentation: deploying a Next.js application with GitHub + Hostinger
Every push becomes a live site with no hosting panel involved: connecting GitHub to Hostinger, the build settings that break and the checks after each deploy.
- deploy
- github


