Retour au catalogue

Process Animated

Process steps en cards qui s'animent sequentiellement au scroll avec stagger et scale.

processmedium Both Responsive a11y
boldplayfuluniversalsaasagencygrid
Theme
"use client";

import { motion } from "framer-motion";
import { ArrowRight } from "lucide-react";

interface StepItem {
  number: string;
  title: string;
  description: string;
}

interface ProcessAnimatedProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  steps?: StepItem[];
}

const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];

export default function ProcessAnimated({
  badge = "Process",
  title = "Comment ca marche",
  subtitle = "Trois etapes simples pour demarrer.",
  steps = [],
}: ProcessAnimatedProps) {
  return (
    <section
      className="py-20 lg:py-28"
      style={{ background: "var(--color-background)" }}
    >
      <div className="mx-auto max-w-5xl px-6">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6, ease }}
          viewport={{ once: true }}
          className="text-center mb-16"
        >
          {badge && (
            <span
              className="inline-block mb-4 text-xs font-medium tracking-widest uppercase"
              style={{ color: "var(--color-accent)" }}
            >
              {badge}
            </span>
          )}
          <h2
            className="text-3xl md:text-4xl lg:text-5xl font-bold"
            style={{ color: "var(--color-foreground)" }}
          >
            {title}
          </h2>
          {subtitle && (
            <p className="mt-3 text-base" style={{ color: "var(--color-foreground-muted)" }}>
              {subtitle}
            </p>
          )}
        </motion.div>

        <div className="grid md:grid-cols-3 gap-8">
          {steps.map((step, i) => (
            <motion.div
              key={step.number}
              initial={{ opacity: 0, y: 40, scale: 0.95 }}
              whileInView={{ opacity: 1, y: 0, scale: 1 }}
              transition={{
                duration: 0.6,
                ease,
                delay: i * 0.15,
              }}
              viewport={{ once: true, margin: "-80px" }}
              className="relative rounded-xl p-8 text-center"
              style={{
                background: "var(--color-background-alt)",
                border: "1px solid var(--color-border)",
              }}
            >
              {/* Step number */}
              <motion.div
                initial={{ scale: 0 }}
                whileInView={{ scale: 1 }}
                transition={{ duration: 0.4, ease, delay: i * 0.15 + 0.2 }}
                viewport={{ once: true }}
                className="w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-5"
                style={{ background: "var(--color-accent)" }}
              >
                <span
                  className="text-sm font-bold"
                  style={{ color: "var(--color-background)" }}
                >
                  {step.number}
                </span>
              </motion.div>

              <h3
                className="text-lg font-bold mb-3"
                style={{ color: "var(--color-foreground)" }}
              >
                {step.title}
              </h3>
              <p
                className="text-sm leading-relaxed"
                style={{ color: "var(--color-foreground-muted)" }}
              >
                {step.description}
              </p>

              {/* Arrow connector (not on last) */}
              {i < steps.length - 1 && (
                <div className="hidden md:flex absolute -right-4 top-1/2 -translate-y-1/2 z-10">
                  <ArrowRight size={16} style={{ color: "var(--color-accent)", opacity: 0.5 }} />
                </div>
              )}
            </motion.div>
          ))}
        </div>
      </div>
    </section>
  );
}

Avis

Process Animated — React Process Section — Incubator