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