Retour au catalogue
CTA Countdown
CTA avec compte a rebours integre pour creer l'urgence. Ideal pour les offres limitees et les lancements.
ctamedium Both Responsive a11y
bolddarksaasecommerceuniversalcentered
Theme
"use client";
import { motion } from "framer-motion";
import { useState, useEffect } from "react";
import { ArrowRight, Clock } from "lucide-react";
interface CtaCountdownProps {
title?: string;
description?: string;
ctaLabel?: string;
ctaUrl?: string;
/** Target date ISO string */
targetDate?: string;
badge?: string;
}
const EASE = [0.16, 1, 0.3, 1] as const;
function useCountdown(target: string) {
const [remaining, setRemaining] = useState({ d: 0, h: 0, m: 0, s: 0 });
useEffect(() => {
function calc() {
const diff = Math.max(0, new Date(target).getTime() - Date.now());
setRemaining({
d: Math.floor(diff / 86400000),
h: Math.floor((diff % 86400000) / 3600000),
m: Math.floor((diff % 3600000) / 60000),
s: Math.floor((diff % 60000) / 1000),
});
}
calc();
const id = setInterval(calc, 1000);
return () => clearInterval(id);
}, [target]);
return remaining;
}
const pad = (n: number) => String(n).padStart(2, "0");
export default function CtaCountdown({
title = "Offre limitee dans le temps",
description = "Profitez de -30% sur tous nos plans avant la fin de l'operation.",
ctaLabel = "En profiter maintenant",
ctaUrl = "#signup",
targetDate = new Date(Date.now() + 3 * 86400000).toISOString(),
badge = "Offre speciale",
}: CtaCountdownProps) {
const { d, h, m, s } = useCountdown(targetDate);
const units = [
{ label: "Jours", value: pad(d) },
{ label: "Heures", value: pad(h) },
{ label: "Min", value: pad(m) },
{ label: "Sec", value: pad(s) },
];
return (
<section
style={{
paddingTop: "var(--section-padding-y)",
paddingBottom: "var(--section-padding-y)",
background: "var(--color-background-dark)",
}}
>
<div
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: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: EASE }}
className="text-center max-w-2xl mx-auto"
>
{badge && (
<span
className="inline-flex items-center gap-2 mb-6 text-xs font-semibold uppercase tracking-widest px-4 py-1.5 rounded-full"
style={{
background: "var(--color-accent)",
color: "var(--color-background)",
}}
>
<Clock className="h-3.5 w-3.5" />
{badge}
</span>
)}
<h2
className="text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight"
style={{ color: "var(--color-foreground-on-dark)" }}
>
{title}
</h2>
<p
className="mt-4 text-base leading-relaxed max-w-md mx-auto"
style={{ color: "var(--color-foreground-on-dark)", opacity: 0.7 }}
>
{description}
</p>
{/* Countdown */}
<div className="flex justify-center gap-4 mt-8">
{units.map((u) => (
<div key={u.label} className="text-center">
<div
className="text-3xl md:text-4xl font-bold tabular-nums px-4 py-3 rounded-xl"
style={{
background: "var(--color-background-card)",
color: "var(--color-accent)",
minWidth: "72px",
}}
>
{u.value}
</div>
<p
className="mt-1.5 text-[11px] uppercase tracking-wider"
style={{ color: "var(--color-foreground-on-dark)", opacity: 0.5 }}
>
{u.label}
</p>
</div>
))}
</div>
<div className="mt-10">
<a
href={ctaUrl}
className="inline-flex items-center gap-2 px-7 py-3.5 rounded-full text-sm font-semibold transition-transform hover:scale-105"
style={{
background: "var(--color-accent)",
color: "var(--color-background)",
}}
>
{ctaLabel}
<ArrowRight className="h-4 w-4" />
</a>
</div>
</motion.div>
</div>
</section>
);
}