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>
  );
}

Avis

Hero Animated — React Hero Section — Incubator