- Shipped
- August 19, 2026 at 8:27 PM UTC
- Author
- Kamo
- Commit
- f911fd4
Step A of the hydration work. Framer Motion is gone from the homepage's eager bundle: 798,723 -> 687,593 bytes raw, 245,367 -> 209,422 gzip. That is 35,945 fewer bytes over the wire, against a prediction of 36,444 made by measuring the chunk beforehand, plus the parse and execution that came with it. Fourteen components used it. Not one of them needed it. Every below-the-fold section drove a `whileInView` or a `useInView` from react-intersection-observer to fade its content up on scroll. That is two libraries and a hydration boundary per section to do what `animation-timeline: view()` does natively, on the compositor, with no runtime — and it carried the same defect the hero had, because Framer serialises `initial` into the server HTML. 96 elements were arriving at `opacity: 0` and staying invisible until React hydrated. Content below the fold was gated on a JavaScript bundle. It is 9 now, and all nine are deliberate: the JumpToBar before its first scroll, the hero reel before it is wanted, and seven hexagon decorations at opacity 0.1-0.15 that a substring match for "opacity:0" catches. The `@supports (animation-timeline: view())` guard is the load-bearing part. The base `.reveal` rule sets no opacity of its own, so a browser without scroll-driven animations applies nothing and the content is simply visible. Nothing here can ship hidden content, which is the property the old version did not have. The rest were smaller and more obviously misplaced: · `whileHover` / `whileTap` on cards and buttons became `.lift` and `.press`. Hover feedback that needed hydration before it worked was always the wrong shape. · HoverMorph ran an *infinite* keyframe loop on `border-radius` — a property the compositor cannot animate — for as long as a pointer rested on a card. It is a transition now. · FAQ's accordion animated `height: 0 -> auto`, the one transition CSS could not do for years and a fair reason to have reached for a library. A grid row going `0fr -> 1fr` does it natively. The answers also stay in the DOM while collapsed now, where the presence transition used to unmount them, so find-in-page and search engines can see them; the buttons gained the `aria-expanded` they should always have had. · HeroOfferBanner imported the entire runtime for `useReducedMotion`, which is one media query. · Footer wrapped the locale-aware <Link> in `motion.create` so a hover state could animate, which put the runtime in the shared layout of every page. MagneticButton is the one thing genuinely lost. It tilted the CTA toward the was the last import holding 36 KB in place — for an effect no touch device can produce at all. The button keeps `.press`. Its file is left in the tree, unused; it is worth knowing that importing it again silently restores the runtime. The conversion was done by a scanner rather than by regex, and it is in the session scratchpad rather than the repo because it is a one-off. JSX attribute values contain nested braces, strings and arrow functions, so `<motion.div ...>` cannot be matched by anything that does not track depth — every animation-only prop was dropped and everything else preserved verbatim and in order. Each file's diff was read individually afterwards; Features had two variant objects left orphaned by it, and those are removed. What this does NOT do is stop the sections hydrating — they are all still `"use client"`, because HomeClient is, and a client component cannot import a server one. That is Step B, and it is a change to the page's composition rather than to these files. This step deliberately lands alone so the reveals can be looked at in a browser before anything structural moves.