Retour au catalogue

Before/After Text

Comparaison textuelle avant/apres avec animations de barrage et de revelation.

comparisonmedium Both Responsive a11y
boldcorporatesaasagencyuniversalstacked
Theme
"use client";

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

interface Pair {
  before: string;
  after: string;
  label: string;
}

interface ComparisonBeforeAfterTextProps {
  title?: string;
  subtitle?: string;
  pairs?: Pair[];
}

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

export default function ComparisonBeforeAfterText({
  title = "Avant / Apres",
  subtitle = "",
  pairs = [],
}: ComparisonBeforeAfterTextProps) {
  return (
    <section style={{ padding: "var(--section-padding-y) 0", background: "var(--color-background)" }}>
      <div style={{ maxWidth: "var(--container-max-width)", margin: "0 auto", padding: "0 var(--container-padding-x)" }}>
        <motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.6, ease: E }} style={{ textAlign: "center", marginBottom: "3rem" }}>
          <h2 style={{ fontFamily: "var(--font-serif)", fontSize: "clamp(1.75rem, 3vw, 2.5rem)", fontWeight: 600, color: "var(--color-foreground)", marginBottom: "0.75rem" }}>{title}</h2>
          {subtitle && <p style={{ fontSize: "1rem", color: "var(--color-foreground-muted)", fontFamily: "var(--font-sans)" }}>{subtitle}</p>}
        </motion.div>

        <div style={{ display: "flex", flexDirection: "column", gap: "1.5rem", maxWidth: "800px", margin: "0 auto" }}>
          {/* Header row */}
          <div style={{ display: "grid", gridTemplateColumns: "1fr auto 1fr", gap: "1rem", alignItems: "center" }}>
            <div style={{ textAlign: "center" }}>
              <span style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--color-foreground-muted)", fontFamily: "var(--font-sans)" }}>Avant</span>
            </div>
            <div style={{ width: "32px" }} />
            <div style={{ textAlign: "center" }}>
              <span style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.08em", color: "var(--color-accent)", fontFamily: "var(--font-sans)" }}>Apres</span>
            </div>
          </div>

          {pairs.map((p, i) => (
            <motion.div
              key={p.label}
              initial={{ opacity: 0, y: 16 }}
              whileInView={{ opacity: 1, y: 0 }}
              viewport={{ once: true }}
              transition={{ duration: 0.5, delay: i * 0.1, ease: E }}
            >
              <p style={{ fontSize: "0.6875rem", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", color: "var(--color-foreground-muted)", fontFamily: "var(--font-sans)", textAlign: "center", marginBottom: "0.5rem" }}>{p.label}</p>
              <div style={{ display: "grid", gridTemplateColumns: "1fr auto 1fr", gap: "1rem", alignItems: "center" }}>
                {/* Before */}
                <motion.div
                  initial={{ opacity: 0, x: -12 }}
                  whileInView={{ opacity: 1, x: 0 }}
                  viewport={{ once: true }}
                  transition={{ duration: 0.5, delay: i * 0.1 + 0.1, ease: E }}
                  style={{ padding: "1rem 1.25rem", borderRadius: "var(--radius-lg)", background: "var(--color-background-alt)", border: "1px solid var(--color-border)", position: "relative" }}
                >
                  <div style={{ display: "flex", alignItems: "flex-start", gap: "0.5rem" }}>
                    <XCircle size={16} style={{ color: "var(--color-foreground-light)", flexShrink: 0, marginTop: "2px" }} />
                    <p style={{ fontSize: "0.875rem", lineHeight: 1.5, color: "var(--color-foreground-muted)", fontFamily: "var(--font-sans)", textDecoration: "line-through", textDecorationColor: "var(--color-foreground-light)" }}>{p.before}</p>
                  </div>
                </motion.div>

                {/* Arrow */}
                <motion.div
                  initial={{ scale: 0 }}
                  whileInView={{ scale: 1 }}
                  viewport={{ once: true }}
                  transition={{ delay: i * 0.1 + 0.2, type: "spring", stiffness: 300 }}
                  style={{ width: "32px", height: "32px", borderRadius: "var(--radius-full)", background: "var(--color-accent)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}
                >
                  <ArrowRight size={14} style={{ color: "var(--color-background)" }} />
                </motion.div>

                {/* After */}
                <motion.div
                  initial={{ opacity: 0, x: 12 }}
                  whileInView={{ opacity: 1, x: 0 }}
                  viewport={{ once: true }}
                  transition={{ duration: 0.5, delay: i * 0.1 + 0.2, ease: E }}
                  style={{ padding: "1rem 1.25rem", borderRadius: "var(--radius-lg)", background: "var(--color-accent-subtle)", border: "1px solid var(--color-accent)", borderColor: "color-mix(in srgb, var(--color-accent) 30%, transparent)" }}
                >
                  <div style={{ display: "flex", alignItems: "flex-start", gap: "0.5rem" }}>
                    <CheckCircle2 size={16} style={{ color: "var(--color-accent)", flexShrink: 0, marginTop: "2px" }} />
                    <p style={{ fontSize: "0.875rem", lineHeight: 1.5, color: "var(--color-foreground)", fontFamily: "var(--font-sans)", fontWeight: 500 }}>{p.after}</p>
                  </div>
                </motion.div>
              </div>
            </motion.div>
          ))}
        </div>
      </div>
    </section>
  );
}

Avis

Before/After Text — React Comparison Section — Incubator