Retour au catalogue
Hero Animated
Hero avec animations riches : titre mot par mot avec blur, orbes flottantes animees. Effet wow pour landing pages.
heromedium Both Responsive a11y
playfulboldsaasagencycentered
Theme
"use client";
import { motion } from "framer-motion";
import { ArrowRight, Sparkles } from "lucide-react";
interface HeroAnimatedProps {
title?: string;
titleAccent?: string;
description?: string;
ctaLabel?: string;
ctaUrl?: string;
ctaSecondaryLabel?: string;
badge?: string;
}
const EASE = [0.16, 1, 0.3, 1] as const;
function FloatingOrb({
size,
x,
y,
delay,
duration,
}: {
size: number;
x: string;
y: string;
delay: number;
duration: number;
}) {
return (
<motion.div
aria-hidden
initial={{ opacity: 0, scale: 0.5 }}
animate={{
opacity: [0.3, 0.6, 0.3],
scale: [0.8, 1.2, 0.8],
y: [0, -20, 0],
}}
transition={{
duration,
delay,
repeat: Infinity,
ease: "easeInOut",
}}
style={{
position: "absolute",
left: x,
top: y,
width: size,
height: size,
borderRadius: "50%",
background: "var(--color-accent)",
opacity: 0.08,
filter: "blur(40px)",
pointerEvents: "none",
}}
/>
);
}
export default function HeroAnimated({
title = "Votre titre principal",
titleAccent = "exceptionnel",
description = "Description du hero",
ctaLabel = "Commencer",
ctaUrl = "#contact",
ctaSecondaryLabel = "",
badge = "",
}: HeroAnimatedProps) {
const words = title.split(" ");
return (
<section
style={{
position: "relative",
overflow: "hidden",
paddingTop: "var(--section-padding-y-lg)",
paddingBottom: "var(--section-padding-y-lg)",
background: "var(--color-background)",
minHeight: "90vh",
display: "flex",
alignItems: "center",
}}
>
{/* Animated orbs */}
<FloatingOrb size={300} x="10%" y="20%" delay={0} duration={6} />
<FloatingOrb size={200} x="70%" y="15%" delay={1.5} duration={8} />
<FloatingOrb size={250} x="80%" y="60%" delay={0.8} duration={7} />
<FloatingOrb size={180} x="5%" y="70%" delay={2} duration={5} />
<div
style={{
width: "100%",
maxWidth: "var(--container-max-width)",
margin: "0 auto",
padding: "0 var(--container-padding-x)",
position: "relative",
zIndex: 1,
textAlign: "center",
}}
>
{badge && (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, ease: EASE }}
>
<span
style={{
display: "inline-flex",
alignItems: "center",
gap: "6px",
padding: "0.5rem 1.25rem",
borderRadius: "var(--radius-full)",
border: "1px solid var(--color-accent-border)",
background: "var(--color-accent-subtle)",
fontSize: "0.8125rem",
fontWeight: 500,
color: "var(--color-foreground-muted)",
marginBottom: "2rem",
}}
>
<Sparkles style={{ width: 14, height: 14, color: "var(--color-accent)" }} />
{badge}
</span>
</motion.div>
)}
{/* Animated word-by-word title */}
<h1
style={{
fontFamily: "var(--font-sans)",
fontSize: "clamp(2.5rem, 5.5vw, 5rem)",
fontWeight: 800,
lineHeight: 1.05,
letterSpacing: "-0.04em",
color: "var(--color-foreground)",
marginBottom: "1.5rem",
}}
>
{words.map((word, i) => (
<motion.span
key={i}
initial={{ opacity: 0, y: 20, filter: "blur(4px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
transition={{
duration: 0.5,
delay: 0.1 + i * 0.08,
ease: EASE,
}}
style={{ display: "inline-block", marginRight: "0.3em" }}
>
{word}
</motion.span>
))}
<motion.em
initial={{ opacity: 0, y: 20, filter: "blur(4px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
transition={{
duration: 0.5,
delay: 0.1 + words.length * 0.08,
ease: EASE,
}}
style={{
display: "inline-block",
fontFamily: "var(--font-serif)",
fontStyle: "italic",
fontWeight: 400,
color: "var(--color-accent)",
}}
>
{titleAccent}
</motion.em>
</h1>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, delay: 0.5, ease: EASE }}
style={{
fontSize: "1.125rem",
lineHeight: 1.7,
color: "var(--color-foreground-muted)",
maxWidth: "520px",
margin: "0 auto 2.5rem",
}}
>
{description}
</motion.p>
<motion.div
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.65, ease: EASE }}
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
alignItems: "center",
gap: "0.75rem",
}}
>
<a
href={ctaUrl}
style={{
display: "inline-flex",
alignItems: "center",
gap: "8px",
padding: "0.875rem 2rem",
borderRadius: "var(--radius-full)",
background: "var(--color-accent)",
color: "var(--color-foreground)",
fontWeight: 600,
fontSize: "0.9375rem",
textDecoration: "none",
}}
>
{ctaLabel}
<ArrowRight style={{ width: 16, height: 16 }} />
</a>
{ctaSecondaryLabel && (
<a
href={ctaUrl}
style={{
display: "inline-flex",
alignItems: "center",
gap: "6px",
padding: "0.875rem 2rem",
borderRadius: "var(--radius-full)",
border: "1px solid var(--color-border)",
color: "var(--color-foreground-muted)",
fontWeight: 500,
fontSize: "0.9375rem",
textDecoration: "none",
}}
>
{ctaSecondaryLabel}
</a>
)}
</motion.div>
</div>
</section>
);
}