Twenty One Media
webMay 7, 2026

Five Motion Primitives We Built for Our Site Rebuild

When we rebuilt our homepage last week, polish was non-negotiable. We wanted the kind of motion you see on Vercel or Linear: smooth, purposeful, never decorative for its own sake. But we also didn't want a 40KB animation runtime wrecking our Lighthouse score on pages that didn't need it.

We ended up with five motion primitives. Small, composable, each solving one problem.

ScrollProgress

A 2px gradient bar pinned to the top of the viewport that fills as you scroll down the page. Framer Motion's useScroll gives you a scrollYProgress motion value. Feed that into useSpring with high stiffness and solid damping, and you get something that follows real scroll momentum without feeling laggy or snappy.

The glow comes from a single box-shadow on the element. No canvas, no SVG, no extra DOM nodes.

AnimatedReveal

A scroll-triggered fade-up wrapper. Every major section on the homepage is wrapped in one. When the element enters the viewport, it fades in and translates up from 24px. Once: no re-animation on scroll back.

The easing curve matters here. We use [0.16, 1, 0.3, 1], a fast-out decelerate that reads as natural to the eye. A linear fade feels like a loading state. This curve feels like arrival.

It also supports a stagger prop: pass a number and children animate in sequence. One component, two behaviors.

StatsCounter

Numbers that count up from zero when they scroll into view. The homepage has four stat blocks ("<14d sprints", "6+ live builds") and we wanted them to feel earned.

The tricky part: parseStat strips any prefix or suffix so the numeric core can animate independently. Framing the animation with requestAnimationFrame and a cubic ease-out keeps it smooth across 1400ms. The useMemo around the parse result was a bug fix, not an optimization: without it, the parsed object changed identity every render, the effect restarted, and the counter reset to zero on every tick.

CursorGlow

A radial spotlight that follows your cursor inside a section. The key decision here was to skip Framer Motion entirely. Instead of binding to state and triggering re-renders on every pointermove, we use a ref and write directly to CSS custom properties: --cx and --cy. The background on the glow div is a radial-gradient centered at those vars.

Zero React re-renders. Zero layout thrash. Just direct DOM writes at pointer speed.

It hides on pointerleave via a CSS transition-opacity. Simple, contained, fast.

HeroVideo

The hero background is an 8-second looping video generated with Seedance 2.0 via Higgsfield: volumetric blue and violet light drifting through dark space with subtle holographic geometry. It fits the aesthetic without needing a designer or a stock license.

The component handles three failure cases cleanly: autoplay blocked (poster stays visible), prefers-reduced-motion (video hidden via motion-reduce:hidden), and slow connections (video fades in on the playing event, not on mount). A vignette gradient and a bottom fade keep the headline readable regardless of what frame is showing.


None of these components are complex in isolation. The value is having them ready, composable, and already wired into the layout. When the next page needs to feel alive, we drop in AnimatedReveal and move on. That's the whole idea.