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