Skip to main content
Jun 25, 2026 · 7 min

Motion under a Core Web Vitals budget

How SoiTarot got animation that feels alive on the interactive pages without wrecking performance across fifteen thousand SEO ones.

Khoa

Motion is where machine-built sites give themselves away. They get it wrong in one of two directions: nothing moves, so the site feels dead, or everything moves, so it feels cheap and your Core Web Vitals quietly collapse. On a site that lives or dies by search — fifteen thousand programmatic pages where a tenth of a second of layout shift is a ranking tax — I couldn't afford either. So I stopped treating motion as decoration and started treating it as a budget. This is the design half I gestured at in the brand build; here's the engineering of it.

Pick the stack on purpose, write down why

The first decision was the boring, load-bearing one: what powers the animation. I wrote it down as an architecture decision so no future agent (or future me) could re-litigate it on a whim. The pick was Framer Motion for component animation, Lenis for smooth scroll, Tailwind for the utility layer — and the rejections mattered as much as the pick: GSAP (heavier bundle, worse React composability), React Spring (too verbose for these cases), Locomotive Scroll (heavier than Lenis). On a Core Web Vitals project, "which library" is a performance decision, not a preference.

A scale, not a pile of magic numbers

Animation goes feral when every component invents its own timing. So durations and easings became a small, fixed vocabulary — one table everything had to draw from:

TokenDurationUsed for
instant100mstoggles, immediate feedback
fast240mshover, button press, icon morph
base360msdefault — fades, slides, page micro
slow600mscard lift, modal, drawer
slower1000mshero reveal, large entrance

Three easing curves, and no more: a soft ease-out for things entering, a smooth ease-in-out for things moving both ways, and a gentle spring (about 6% overshoot) reserved for micro-interactions like hover and tap. Watch them run — the curve is the character:

Five durations, three curves. That's the entire motion language. Constraint is what makes a thousand auto-generated pages feel composed instead of random.

The budget is enforced, not suggested

Here's the part that actually protects the search traffic. Motion runs against a hard budget that changes by page type:

  • The 15,000 SEO pages: at most three concurrent animations, nothing slower than 600ms, transform and opacity only — never width, height, top, or left, because those trigger layout. The target is CLS under 0.1 and LCP under 2.5s, and motion is not allowed to threaten it.
  • The interactive pages (drawing cards, reading results): up to six concurrent and durations up to a full second, because here the animation is the product and there's no ranking to protect.

Same design language, two different speed limits. The card-flip can be theatrical on the draw page and is simply not present on a horoscope page that needs to rank.

One source of truth, three outputs

The motion tokens live in exactly one file. From there they're emitted to the three places code reads them: the Framer Motion constants, a set of CSS variables for non-Framer transitions, and the Tailwind config. The rule is that none of those three is ever hand-edited — you change the token and regenerate. It means the design system and the running site can't drift apart, which is the same discipline that let the whole project ship in a week: decide once, in one place, and let everything downstream inherit it.

Reduced motion is a first-class path, not a footnote

Every animated component checks for prefers-reduced-motion and has a real fallback, not a dead stop: the card flip becomes a cross-fade, the cosmic loader becomes a static icon, scroll reveals just appear, and the smooth-scroll layer doesn't initialize at all. Accessibility here isn't a compliance checkbox — it's the same budget logic applied to people, not just to metrics. If someone's told their device they want less movement, the cheapest, calmest version of the site is the correct one.

The point

None of this is exotic. It's a vocabulary, a budget, and a single source of truth — three small acts of discipline. But that's exactly the thread from the wider experiment: when a machine does the making at scale, your job is to set the rails it can't cross. Motion was just one more fence — and the fences, it turns out, were the real product.