Documentation Index
Fetch the complete documentation index at: https://mintlify.com/gus-16710/invitations/llms.txt
Use this file to discover all available pages before exploring further.
Every section component in an invitation uses Framer Motion to animate its content into view as the guest scrolls through the slides. Animation definitions are centralised in a single Animations.ts file per invitation, which exports named variant objects. Components import only the variants they need, keeping the motion code out of the JSX and making it easy to swap or tweak effects across the whole invitation by editing one file.
Preset Variants from Animations.ts
The file below is the real variant set used by the Daniela XV años invitation. Each export is a Framer Motion Variants object with a hidden state (before the element enters the viewport) and a visible state (the fully rendered position and opacity).
// src/app/quinces/daniela/components/Animations.ts
export const animation01 = {
hidden: { opacity: 0, scale: 0 },
visible: {
opacity: 1,
scale: 1,
transition: { duration: 1, type: "spring" as const, stiffness: 70, delay: 0.4 },
},
};
export const animation02 = {
hidden: { y: 100, rotate: 180, opacity: 0 },
visible: {
y: 0,
rotate: 0,
opacity: 1,
transition: { duration: 1 },
},
};
export const animation03 = {
hidden: { y: 100, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: { duration: 1, delay: 0.8 },
},
};
export const animation04 = {
hidden: { y: -100, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: { duration: 1, delay: 1 },
},
};
export const animation05 = {
hidden: { scale: 2, opacity: 0 },
visible: {
scale: 1,
opacity: 1,
transition: { duration: 2 },
},
};
export const animation06 = {
hidden: { opacity: 0, scale: 0 },
visible: {
opacity: 1,
scale: 1,
transition: {
duration: 1,
delay: 0.6,
},
},
};
export const animation07 = {
hidden: { scale: 0, opacity: 0 },
visible: {
scale: 1,
opacity: 1,
transition: { duration: 1 },
},
};
Variant Reference
| Variant | Hidden state | Visible state | Notable transition |
|---|
animation01 | opacity: 0, scale: 0 | opacity: 1, scale: 1 | Spring, stiffness 70, delay 0.4 s |
animation02 | y: 100, rotate: 180, opacity: 0 | y: 0, rotate: 0, opacity: 1 | Linear 1 s — great for decorative spinners |
animation03 | y: 100, opacity: 0 | y: 0, opacity: 1 | Ease, delay 0.8 s — slides up from below |
animation04 | y: -100, opacity: 0 | y: 0, opacity: 1 | Ease, delay 1 s — drops down from above |
animation05 | scale: 2, opacity: 0 | scale: 1, opacity: 1 | Slow 2 s zoom-out — ideal for background photos |
animation06 | opacity: 0, scale: 0 | opacity: 1, scale: 1 | Ease, delay 0.6 s — scale-in with a 0.2 s later start than animation01 |
animation07 | scale: 0, opacity: 0 | scale: 1, opacity: 1 | Ease, 1 s — no delay, immediate pop |
The variants / initial / whileInView Pattern
Every animated element follows the same three-prop pattern:
<motion.h1
variants={animation01} // the variant object from Animations.ts
initial="hidden" // start in the hidden state
whileInView="visible" // transition to visible when the element enters the viewport
>
Content
</motion.h1>
variants — points to a named variant object so the transition config lives outside the JSX.
initial="hidden" — sets the element’s starting state before it enters the viewport.
whileInView="visible" — triggers the animation when the element scrolls into view; the element returns to hidden when it leaves (enabling re-animation on revisit).
The Header component applies four different variants to its child elements, creating a sequenced entrance: the title scales in first, the photo frame spins in, then the name and date slide up from below and drop down from above respectively.
// src/app/quinces/daniela/components/Header.tsx (excerpt)
import { motion } from "framer-motion";
import { animation01, animation02, animation03, animation04 } from "./Animations";
export default function Header() {
return (
<section style={{ height: "100svh" }}>
{/* Title: spring scale-in with delay 0.4 s */}
<motion.h1
variants={animation01}
initial="hidden"
whileInView="visible"
>
MIS <span>XV</span> AÑOS
</motion.h1>
{/* Portrait photo: spring scale-in */}
<motion.div
className="bg-[url('/img/quinces/daniela/fifteen-girl.jpg')] ..."
variants={animation01}
initial="hidden"
whileInView="visible"
/>
{/* Decorative ring: rotates 180° into position */}
<motion.div
className="bg-[url('/img/quinces/daniela/header-03.png')] ..."
variants={animation02}
initial="hidden"
whileInView="visible"
/>
{/* Name: slides up from y: 100, delay 0.8 s */}
<motion.p
variants={animation03}
initial="hidden"
whileInView="visible"
>
Daniela
</motion.p>
{/* Date: drops down from y: -100, delay 1 s */}
<motion.p
variants={animation04}
initial="hidden"
whileInView="visible"
>
DOMINGO <span>30</span> OCTUBRE
</motion.p>
</section>
);
}
Staggered List Animations
For list-like sections such as Itinerary, Framer Motion’s staggerChildren orchestration is used so each list item animates in with a 0.5 s delay between items.
// src/app/quinces/sarang/components/Itinerary.tsx (excerpt)
const list = {
visible: {
opacity: 1,
transition: {
when: "beforeChildren",
staggerChildren: 0.5,
duration: 1,
},
},
hidden: {
opacity: 0,
transition: { when: "afterChildren" },
},
};
const element = {
visible: { opacity: 1, y: 0 },
hidden: { opacity: 0, y: -100 },
};
// Usage inside the component:
<motion.ol initial="hidden" whileInView="visible" variants={list}>
{itinerary.map((item, index) => (
<motion.div key={index} variants={element}>
{/* item content */}
</motion.div>
))}
</motion.ol>
Scroll-Snap Layout
Each section root element carries style={{ height: "100svh" }}. When the invitation is rendered without Splide (e.g., a scroll-based layout), the <html> element uses CSS scroll-snap to lock the viewport to one section at a time:
// layout.tsx or page wrapper
<html style={{ scrollSnapType: "y mandatory" }}>
<body>
<Header /> {/* height: 100svh, scroll-snap-align: start */}
<Ceremony />
<Reception />
{/* ... */}
</body>
</html>
Each section then implicitly snaps because the browser aligns full-viewport-height children when scrollSnapType: "y mandatory" is set on the scroll container.
Smooth Scrolling with Lenis
Scroll-based invitations wrap the entire page in a SmoothScroll component that configures Lenis for buttery smooth wheel and touch scrolling.
// src/components/SmoothScroll.tsx
"use client";
import { ReactLenis } from "lenis/react";
export default function SmoothScroll({
children,
}: {
children: React.ReactNode;
}) {
return (
<ReactLenis
root
options={{
lerp: 0.08, // smoothness (0 = none, 1 = instant)
smoothWheel: true, // smooth mouse-wheel scrolling
duration: 1.2, // scroll animation duration in seconds
}}
>
{children}
</ReactLenis>
);
}
Wrap the root layout body with <SmoothScroll> to enable it globally for a given invitation route.
Always use whileInView rather than animate for section content. With animate, the animation fires only once on mount — which means elements on slides 2–10 are already in their visible state before the guest ever sees them. whileInView re-triggers the entrance effect each time the slide scrolls into the viewport, creating a fresh, engaging experience on every visit to that section.