Retour au catalogue

Timeline Creative

Timeline style growth path avec ligne de progression animee au scroll, dots lumineux et metriques optionnelles par etape.

timelinemedium Both Responsive a11y
boldelegantuniversalsaasagencystacked
Theme
"use client";

import { motion, useScroll, useTransform } from "framer-motion";
import { useRef } from "react";
import { Sparkles } from "lucide-react";

interface TimelineEvent {
  id: string;
  date: string;
  title: string;
  description: string;
  metric?: string;
  metricLabel?: string;
}

interface TimelineCreativeProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  events: TimelineEvent[];
}

const EASE = [0.16, 1, 0.3, 1] as const;

export default function TimelineCreative({
  badge,
  title = "Notre parcours",
  subtitle,
  events = [],
}: TimelineCreativeProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ["start end", "end start"],
  });
  const lineHeight = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);

  return (
    <section
      className="py-[var(--section-padding-y,6rem)]"
      style={{ backgroundColor: "var(--color-background)" }}
    >
      <div className="mx-auto max-w-4xl px-[var(--container-padding-x,1.5rem)]">
        {/* Header */}
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true, margin: "-80px" }}
          transition={{ duration: 0.5, ease: EASE }}
          className="text-center max-w-2xl mx-auto mb-20"
        >
          {badge && (
            <span
              className="inline-flex items-center gap-1.5 text-xs font-medium tracking-wider uppercase px-3 py-1 rounded-full border mb-4"
              style={{ color: "var(--color-accent)", borderColor: "var(--color-border)" }}
            >
              <Sparkles className="w-3 h-3" />
              {badge}
            </span>
          )}
          {title && (
            <h2
              className="text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl"
              style={{ color: "var(--color-foreground)" }}
            >
              {title}
            </h2>
          )}
          {subtitle && (
            <p className="mt-4 text-base" style={{ color: "var(--color-foreground-muted)" }}>
              {subtitle}
            </p>
          )}
        </motion.div>

        {/* Timeline with growth effect */}
        <div ref={containerRef} className="relative pl-8 md:pl-12">
          {/* Background line */}
          <div
            className="absolute left-3 md:left-5 top-0 bottom-0 w-0.5 rounded-full"
            style={{ backgroundColor: "var(--color-border)" }}
          />
          {/* Animated progress line */}
          <motion.div
            className="absolute left-3 md:left-5 top-0 w-0.5 rounded-full"
            style={{
              height: lineHeight,
              background: `linear-gradient(180deg, var(--color-accent), color-mix(in srgb, var(--color-accent) 40%, transparent))`,
            }}
          />

          <div className="space-y-16">
            {events.map((event, i) => (
              <motion.div
                key={event.id}
                initial={{ opacity: 0, y: 40 }}
                whileInView={{ opacity: 1, y: 0 }}
                viewport={{ once: true, margin: "-60px" }}
                transition={{ duration: 0.7, delay: i * 0.05, ease: EASE }}
                className="relative"
              >
                {/* Glowing dot */}
                <div
                  className="absolute -left-8 md:-left-12 top-1 w-6 h-6 md:w-10 md:h-10 rounded-full flex items-center justify-center"
                  style={{
                    background: `radial-gradient(circle, color-mix(in srgb, var(--color-accent) 25%, transparent) 0%, transparent 70%)`,
                  }}
                >
                  <div
                    className="w-2.5 h-2.5 md:w-3 md:h-3 rounded-full"
                    style={{ backgroundColor: "var(--color-accent)" }}
                  />
                </div>

                {/* Card */}
                <div
                  className="rounded-[var(--radius-xl,1.5rem)] border p-6 md:p-8 transition-all duration-300 hover:translate-y-[-2px] hover:shadow-lg"
                  style={{
                    backgroundColor: "var(--color-background-card)",
                    borderColor: "var(--color-border)",
                  }}
                >
                  <div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
                    <div className="flex-1">
                      <span
                        className="text-xs font-bold tracking-wider uppercase"
                        style={{ color: "var(--color-accent)" }}
                      >
                        {event.date}
                      </span>
                      <h3
                        className="mt-2 text-xl font-bold tracking-tight"
                        style={{ color: "var(--color-foreground)" }}
                      >
                        {event.title}
                      </h3>
                      <p
                        className="mt-3 text-sm leading-relaxed"
                        style={{ color: "var(--color-foreground-muted)" }}
                      >
                        {event.description}
                      </p>
                    </div>

                    {event.metric && (
                      <div
                        className="flex-shrink-0 flex flex-col items-center justify-center w-20 h-20 md:w-24 md:h-24 rounded-[var(--radius-lg,1rem)]"
                        style={{
                          background: `linear-gradient(135deg, color-mix(in srgb, var(--color-accent) 15%, transparent), color-mix(in srgb, var(--color-accent) 5%, transparent))`,
                        }}
                      >
                        <span
                          className="text-xl md:text-2xl font-bold tabular-nums"
                          style={{ color: "var(--color-accent)" }}
                        >
                          {event.metric}
                        </span>
                        {event.metricLabel && (
                          <span
                            className="text-[10px] font-medium uppercase tracking-wider mt-0.5"
                            style={{ color: "var(--color-foreground-muted)" }}
                          >
                            {event.metricLabel}
                          </span>
                        )}
                      </div>
                    )}
                  </div>
                </div>
              </motion.div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

Avis

Timeline Creative — React Timeline Section — Incubator