Retour au catalogue

Countdown Event

Countdown evenement avec date, lieu, description et compteur en cards. Ideal pour conferences et meetups.

countdownmedium Both Responsive a11y
corporateminimaleventeducationuniversalcentered
Theme
"use client";

import { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { Calendar, MapPin, Clock } from "lucide-react";

interface CountdownEventProps {
  title?: string;
  eventDate?: string;
  eventDateLabel?: string;
  location?: string;
  description?: string;
  ctaLabel?: string;
  ctaUrl?: string;
}

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

function calcTimeLeft(target: string) {
  const diff = new Date(target).getTime() - Date.now();
  if (diff <= 0) return { days: 0, hours: 0, minutes: 0, seconds: 0 };
  return {
    days: Math.floor(diff / (1000 * 60 * 60 * 24)),
    hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
    minutes: Math.floor((diff / (1000 * 60)) % 60),
    seconds: Math.floor((diff / 1000) % 60),
  };
}

const units = [
  { key: "days", label: "Jours" },
  { key: "hours", label: "Heures" },
  { key: "minutes", label: "Min" },
  { key: "seconds", label: "Sec" },
] as const;

export default function CountdownEvent({
  title = "Conference Design & Tech 2026",
  eventDate = "2026-09-15T09:00:00",
  eventDateLabel = "15 Septembre 2026",
  location = "Palais des Congres, Lyon",
  description = "Rejoignez plus de 2 000 professionnels du design et de la tech pour deux jours d'echanges, de workshops et de conferences inspirantes.",
  ctaLabel = "Reserver ma place",
  ctaUrl = "#tickets",
}: CountdownEventProps) {
  const [timeLeft, setTimeLeft] = useState(calcTimeLeft(eventDate));

  useEffect(() => {
    const id = setInterval(() => setTimeLeft(calcTimeLeft(eventDate)), 1000);
    return () => clearInterval(id);
  }, [eventDate]);

  return (
    <section
      style={{
        position: "relative",
        overflow: "hidden",
        paddingTop: "var(--section-padding-y-lg)",
        paddingBottom: "var(--section-padding-y-lg)",
        background: "var(--color-background)",
        display: "flex",
        alignItems: "center",
      }}
    >
      <div
        style={{
          width: "100%",
          maxWidth: "var(--container-max-width)",
          margin: "0 auto",
          padding: "0 var(--container-padding-x)",
        }}
      >
        <div style={{ display: "grid", gridTemplateColumns: "1fr", gap: "3rem", maxWidth: "900px", margin: "0 auto" }}>
          {/* Top: Title + Info */}
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.6, ease: EASE }}
            style={{ textAlign: "center" }}
          >
            <h2
              style={{
                fontFamily: "var(--font-sans)",
                fontSize: "clamp(2rem, 4vw, 3.25rem)",
                fontWeight: 700,
                lineHeight: 1.1,
                letterSpacing: "-0.02em",
                color: "var(--color-foreground)",
                marginBottom: "1.5rem",
              }}
            >
              {title}
            </h2>

            <div
              style={{
                display: "flex",
                flexWrap: "wrap",
                justifyContent: "center",
                gap: "1.5rem",
                marginBottom: "1.5rem",
              }}
            >
              <span style={{ display: "inline-flex", alignItems: "center", gap: "6px", fontSize: "0.9375rem", color: "var(--color-foreground-muted)" }}>
                <Calendar style={{ width: 16, height: 16, color: "var(--color-accent)" }} />
                {eventDateLabel}
              </span>
              <span style={{ display: "inline-flex", alignItems: "center", gap: "6px", fontSize: "0.9375rem", color: "var(--color-foreground-muted)" }}>
                <MapPin style={{ width: 16, height: 16, color: "var(--color-accent)" }} />
                {location}
              </span>
            </div>

            <p style={{ fontSize: "1.0625rem", lineHeight: 1.7, color: "var(--color-foreground-muted)", maxWidth: "600px", margin: "0 auto" }}>
              {description}
            </p>
          </motion.div>

          {/* Countdown */}
          <motion.div
            initial={{ opacity: 0, scale: 0.96 }}
            animate={{ opacity: 1, scale: 1 }}
            transition={{ duration: 0.5, delay: 0.12, ease: EASE }}
            style={{
              display: "grid",
              gridTemplateColumns: "repeat(4, 1fr)",
              gap: "1rem",
              maxWidth: "520px",
              margin: "0 auto",
              width: "100%",
            }}
          >
            {units.map(({ key, label }) => (
              <div
                key={key}
                style={{
                  display: "flex",
                  flexDirection: "column",
                  alignItems: "center",
                  padding: "1.25rem 0.5rem",
                  borderRadius: "var(--radius-lg)",
                  border: "1px solid var(--color-border)",
                  background: "var(--color-background-alt)",
                }}
              >
                <motion.span
                  key={timeLeft[key]}
                  initial={{ opacity: 0, y: -8 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.25, ease: EASE }}
                  style={{
                    fontFamily: "var(--font-sans)",
                    fontSize: "clamp(1.75rem, 4vw, 2.75rem)",
                    fontWeight: 800,
                    lineHeight: 1,
                    color: "var(--color-foreground)",
                  }}
                >
                  {String(timeLeft[key]).padStart(2, "0")}
                </motion.span>
                <span
                  style={{
                    fontSize: "0.6875rem",
                    fontWeight: 500,
                    textTransform: "uppercase",
                    letterSpacing: "0.08em",
                    color: "var(--color-foreground-muted)",
                    marginTop: "0.5rem",
                  }}
                >
                  {label}
                </span>
              </div>
            ))}
          </motion.div>

          {/* CTA */}
          <motion.div
            initial={{ opacity: 0, y: 8 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.45, delay: 0.22, ease: EASE }}
            style={{ textAlign: "center" }}
          >
            <a
              href={ctaUrl}
              style={{
                display: "inline-flex",
                alignItems: "center",
                gap: "8px",
                padding: "0.875rem 2.25rem",
                borderRadius: "var(--radius-full)",
                background: "var(--color-accent)",
                color: "var(--color-foreground)",
                fontWeight: 600,
                fontSize: "0.9375rem",
                textDecoration: "none",
              }}
            >
              <Clock style={{ width: 16, height: 16 }} />
              {ctaLabel}
            </a>
          </motion.div>
        </div>
      </div>
    </section>
  );
}

Avis

Countdown Event — React Countdown Section — Incubator