VAULT
INTERACTION_060CSS
Letter 3D Rotation
Characters rotate in on the Y axis with real perspective.
Letters / MediumView
Characters flip on the X axis like a departure board. Rotated -90deg on the X axis before the trigger; rotating each character back to 0deg once entry into view or a value change.
/* Letter Flip — Characters flip on the X axis like a departure board. */
.letter-flip {
--dur: 560ms;
--ease: cubic-bezier(0.22, 1, 0.36, 1);
--stagger: 45ms;
will-change: transform, opacity;
}
.letter-flip > * {
opacity: 0;
transform: translateY(14px);
animation: letter-flip-in var(--dur) var(--ease) forwards;
animation-delay: calc(var(--i, 0) * var(--stagger));
}
/* rotated -90deg on the X axis -> rotating each character back to 0deg */
@keyframes letter-flip-in {
to {
opacity: 1;
transform: none;
filter: none;
}
}
@media (prefers-reduced-motion: reduce) {
.letter-flip > * {
opacity: 1;
transform: none;
animation: none;
}
}import { useEffect, useRef } from "react";
/** Letter Flip — trigger: entry into view or a value change */
export function LetterFlip({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
el.dataset["state"] = "in";
return;
}
const io = new IntersectionObserver(
([entry]) => {
if (entry?.isIntersecting) {
el.dataset["state"] = "in";
io.unobserve(el);
}
},
{ threshold: 0.2 },
);
io.observe(el);
return () => io.disconnect();
}, []);
return (
<div ref={ref} className="letter-flip" data-state="out">
{children}
</div>
);
}Characters rotate in on the Y axis with real perspective.
Characters fade in one by one to draw the eye across the word.
Characters fly in from random offsets and lock into the word.
Characters sharpen from a heavy blur into crisp type.