Retour au catalogue

Trust Animated Process

Section confiance montrant les etapes du processus de securite avec lignes connectees animees.

trustmedium Both Responsive a11y
corporateminimalsaasecommerceuniversalstacked
Theme
"use client";

import { useRef } from "react";
import { motion, useInView } from "framer-motion";
import { Shield, Lock, Eye, CheckCircle } from "lucide-react";
import type { ElementType } from "react";

interface Step {
  icon: string;
  title: string;
  description: string;
}

interface TrustAnimatedProcessProps {
  title?: string;
  subtitle?: string;
  steps?: Step[];
}

const ICON_MAP: Record<string, ElementType> = { Shield, Lock, Eye, CheckCircle };
const EASE = [0.16, 1, 0.3, 1] as const;

export default function TrustAnimatedProcess({
  title = "Votre securite a chaque etape",
  subtitle = "Processus securise",
  steps = [],
}: TrustAnimatedProcessProps) {
  const ref = useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, margin: "-100px" });

  return (
    <section
      style={{
        paddingTop: "var(--section-padding-y)",
        paddingBottom: "var(--section-padding-y)",
        background: "var(--color-background)",
      }}
    >
      <div
        ref={ref}
        className="mx-auto"
        style={{
          maxWidth: "var(--container-max-width)",
          paddingLeft: "var(--container-padding-x)",
          paddingRight: "var(--container-padding-x)",
        }}
      >
        <motion.div
          initial={{ opacity: 0, y: 16 }}
          animate={inView ? { opacity: 1, y: 0 } : {}}
          transition={{ duration: 0.5, ease: EASE }}
          className="text-center mb-16"
        >
          <span className="text-xs font-semibold uppercase tracking-widest" style={{ color: "var(--color-accent)" }}>{subtitle}</span>
          <h2 className="text-3xl md:text-4xl font-bold tracking-tight mt-3" style={{ color: "var(--color-foreground)" }}>{title}</h2>
        </motion.div>

        <div className="relative max-w-3xl mx-auto">
          {/* Connecting line */}
          <div className="absolute left-6 md:left-1/2 top-0 bottom-0 w-px" style={{ background: "var(--color-border)" }}>
            <motion.div
              className="w-full"
              style={{ background: "var(--color-accent)", transformOrigin: "top" }}
              initial={{ height: 0 }}
              animate={inView ? { height: "100%" } : {}}
              transition={{ duration: 1.5, ease: EASE, delay: 0.3 }}
            />
          </div>

          {steps.map((step, i) => {
            const Icon = ICON_MAP[step.icon] ?? Shield;
            const isLeft = i % 2 === 0;
            return (
              <motion.div
                key={step.title}
                initial={{ opacity: 0, x: isLeft ? -30 : 30 }}
                animate={inView ? { opacity: 1, x: 0 } : {}}
                transition={{ duration: 0.5, delay: 0.4 + i * 0.2, ease: EASE }}
                className={`relative flex items-start gap-6 mb-12 last:mb-0 pl-16 md:pl-0 ${isLeft ? "md:flex-row md:text-right" : "md:flex-row-reverse md:text-left"}`}
              >
                {/* Content */}
                <div className={`flex-1 ${isLeft ? "md:pr-12" : "md:pl-12"}`}>
                  <h3 className="text-lg font-semibold mb-2" style={{ color: "var(--color-foreground)" }}>{step.title}</h3>
                  <p className="text-sm leading-relaxed" style={{ color: "var(--color-foreground-muted)" }}>{step.description}</p>
                </div>

                {/* Node */}
                <div
                  className="absolute left-0 md:left-1/2 md:-translate-x-1/2 w-12 h-12 rounded-full flex items-center justify-center shrink-0 z-10"
                  style={{
                    background: "var(--color-background)",
                    border: "2px solid var(--color-accent)",
                    boxShadow: "0 0 0 4px var(--color-background)",
                  }}
                >
                  <Icon className="w-5 h-5" style={{ color: "var(--color-accent)" }} />
                </div>

                {/* Spacer for opposite side */}
                <div className="hidden md:block flex-1" />
              </motion.div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

Avis

Trust Animated Process — React Trust Section — Incubator