Your system is set to reduced motion, so every transition here is held at its final state. That is the system working, not the demo failing.
Rise
The base reveal. Opacity and a 64px vertical offset, driven by the
standalone translate property rather than transform, so it
composes with a hover transform instead of fighting it.
.item { opacity: 0; translate: 0 64px }
.item.in { opacity: 1; translate: 0 0 }
transition 0.6s cubic-bezier(.25,.46,.45,.94)
Lateral
Opposed horizontal entries for split sections. The page root needs
overflow-x: clip — on the root element, not the body, since the
viewport scrollbar is driven by the root.
html { overflow-x: clip }
.left { translate: -72px 0 }
.right { translate: 72px 0 }
Scale
A 0.92 start, for panels that should settle rather than travel. Again a standalone property, so a card can still lift on hover.
.panel { scale: 0.92 }
.panel.in { scale: 1 }
Cascade
Children stagger from the parent's single state change, so a
twenty-card grid still costs one observer entry. Delay is pure CSS, by
nth-child.
.grid.in > * { opacity: 1; translate: 0 0 }
.grid > *:nth-child(2) { transition-delay: 70ms }
Hinge
A 3D entrance hinged on the bottom edge. The perspective sits on the
container, never the card: on the card it would require
transform, and the revealed state would then outrank
:hover and kill the lift permanently.
.stage { perspective: 2000px }
.card { rotate: x -55deg; transform-origin: 50% 100% }
.card.in { rotate: x 0deg }
Trigger geometry
The part that is routinely got backwards. A negative bottom
rootMargin shrinks the observer's root, so the element must travel
120px past the viewport edge before it counts as visible. A positive value fires
early and plays a third of the motion off-screen, where nobody sees it.
new IntersectionObserver(cb, {
threshold: 0,
rootMargin: "0px 0px -120px 0px"
})
Specification
The whole system, as shipped.
| Parameter | Value | Why |
|---|---|---|
| Entrance duration | 0.6s | Long enough to read as deliberate, short enough not to gate scrolling. |
| Cascade step | 70ms | Reads as one gesture; above ~120ms the grid reads as separate events. |
| Easing | cubic-bezier(.25,.46,.45,.94) | Decelerating. Things arriving should slow into place. |
| Trigger inset | -120px | Measured, not guessed. Keeps the full motion on screen. |
| Observer lifetime | persistent | Never disconnected, so reveals replay on scroll-up. |
| Animated properties | opacity, translate, scale, rotate | Compositor-friendly, and free of transform so hover states still work. |
| Reduced motion | honoured | Resting states are never applied; content renders complete. |