Retour au catalogue
Event Hero Countdown
Hero 100vh pour evenement avec titre clip-reveal ligne par ligne, countdown a rebours avec effet slot machine (AnimatePresence) sur chaque chiffre, badge date/lieu, CTA bounce et fond aurora radial premium.
eventcomplex Both Responsive a11y
boldelegantluxuryeventeducationagencyuniversalcenteredfullscreen
Theme
"use client";
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { MapPin, Calendar, ArrowRight } from "lucide-react";
interface EventHeroCountdownProps {
eventName?: string;
tagline?: string;
date?: string;
location?: string;
ctaLabel?: string;
ctaHref?: string;
targetDate?: string;
}
const EASE = [0.16, 1, 0.3, 1] as const;
interface CountdownState {
days: number;
hours: number;
minutes: number;
}
function useCountdown(target: string): CountdownState {
const [diff, setDiff] = useState<CountdownState>({ days: 0, hours: 0, minutes: 0 });
useEffect(() => {
const calc = () => {
const ms = Math.max(0, new Date(target).getTime() - Date.now());
setDiff({
days: Math.floor(ms / 86400000),
hours: Math.floor((ms % 86400000) / 3600000),
minutes: Math.floor((ms % 3600000) / 60000),
});
};
calc();
const id = setInterval(calc, 60000);
return () => clearInterval(id);
}, [target]);
return diff;
}
function SlotDigit({ value }: { value: string }) {
return (
<div style={{ position: "relative", overflow: "hidden", height: "1.15em", display: "inline-block", minWidth: "0.65em" }}>
<AnimatePresence mode="popLayout" initial={false}>
<motion.span
key={value}
initial={{ y: "100%", opacity: 0 }}
animate={{ y: "0%", opacity: 1 }}
exit={{ y: "-100%", opacity: 0 }}
transition={{ duration: 0.35, ease: EASE }}
style={{ display: "block", lineHeight: 1.15 }}
>
{value}
</motion.span>
</AnimatePresence>
</div>
);
}
function CountdownNumber({ value, label }: { value: number; label: string }) {
const str = String(value).padStart(2, "0");
return (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: "0.5rem" }}>
<div style={{
display: "flex",
fontFamily: "var(--font-sans)",
fontSize: "clamp(3rem, 8vw, 6rem)",
fontWeight: 800,
color: "var(--color-foreground)",
letterSpacing: "-0.04em",
lineHeight: 1,
}}>
<SlotDigit value={str[0]} />
<SlotDigit value={str[1]} />
</div>
<span style={{
fontSize: "0.6875rem",
fontWeight: 600,
textTransform: "uppercase",
letterSpacing: "0.1em",
color: "var(--color-foreground-muted)",
}}>
{label}
</span>
</div>
);
}
function TitleWord({ word, delay }: { word: string; delay: number }) {
return (
<span style={{ display: "inline-block", overflow: "hidden", verticalAlign: "bottom", marginRight: "0.3em" }}>
<motion.span
initial={{ y: "110%" }}
animate={{ y: "0%" }}
transition={{ duration: 0.75, delay, ease: EASE }}
style={{ display: "block" }}
>
{word}
</motion.span>
</span>
);
}
export default function EventHeroCountdown({
eventName = "Design Summit 2026",
tagline = "The defining event for product designers and founders.",
date = "September 18, 2026",
location = "San Francisco, CA",
ctaLabel = "Register now",
ctaHref = "#",
targetDate = "2026-09-18T09:00:00",
}: EventHeroCountdownProps) {
const countdown = useCountdown(targetDate);
const words = eventName.split(" ");
return (
<section
style={{
minHeight: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
overflow: "hidden",
background: "var(--color-background)",
}}
>
{/* Aurora background */}
<div
aria-hidden
style={{
position: "absolute",
inset: 0,
background: [
"radial-gradient(ellipse 80% 60% at 50% -10%, color-mix(in srgb, var(--color-accent) 12%, transparent) 0%, transparent 70%)",
"radial-gradient(ellipse 50% 40% at 80% 80%, color-mix(in srgb, var(--color-accent) 6%, transparent) 0%, transparent 60%)",
].join(", "),
pointerEvents: "none",
}}
/>
<div style={{
maxWidth: "var(--container-max-width)",
margin: "0 auto",
padding: "0 var(--container-padding-x)",
width: "100%",
textAlign: "center",
position: "relative",
zIndex: 1,
}}>
{/* Date + location badges */}
<motion.div
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1, ease: EASE }}
style={{ display: "flex", justifyContent: "center", gap: "0.75rem", marginBottom: "2rem", flexWrap: "wrap" }}
>
{[
{ icon: <Calendar style={{ width: 13, height: 13 }} />, text: date },
{ icon: <MapPin style={{ width: 13, height: 13 }} />, text: location },
].map((badge, i) => (
<span
key={i}
style={{
display: "inline-flex", alignItems: "center", gap: "0.375rem",
padding: "0.375rem 0.875rem",
borderRadius: "var(--radius-full)",
border: "1px solid var(--color-border)",
background: "var(--color-background-card)",
fontSize: "0.8125rem",
color: "var(--color-foreground-muted)",
fontWeight: 500,
}}
>
{badge.icon}
{badge.text}
</span>
))}
</motion.div>
{/* Title clip-reveal */}
<h1
style={{
fontFamily: "var(--font-sans)",
fontSize: "clamp(2.5rem, 7vw, 5.5rem)",
fontWeight: 800,
color: "var(--color-foreground)",
letterSpacing: "-0.03em",
lineHeight: 1.05,
marginBottom: "1.25rem",
}}
>
{words.map((word, i) => (
<TitleWord key={i} word={word} delay={0.25 + i * 0.1} />
))}
</h1>
{/* Tagline */}
<motion.p
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.55, delay: 0.5 + words.length * 0.1, ease: EASE }}
style={{
fontSize: "clamp(1rem, 2vw, 1.1875rem)",
color: "var(--color-foreground-muted)",
maxWidth: "520px",
margin: "0 auto 3.5rem",
lineHeight: 1.65,
}}
>
{tagline}
</motion.p>
{/* Countdown */}
<motion.div
initial={{ opacity: 0, scale: 0.96 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5, delay: 0.65, ease: EASE }}
style={{
display: "flex",
justifyContent: "center",
alignItems: "flex-start",
gap: "clamp(1.5rem, 5vw, 4rem)",
marginBottom: "3.5rem",
}}
>
<CountdownNumber value={countdown.days} label="Days" />
<div style={{ fontSize: "clamp(2rem, 5vw, 4rem)", fontWeight: 800, color: "var(--color-border)", lineHeight: 1, paddingTop: "0.1em" }}>:</div>
<CountdownNumber value={countdown.hours} label="Hours" />
<div style={{ fontSize: "clamp(2rem, 5vw, 4rem)", fontWeight: 800, color: "var(--color-border)", lineHeight: 1, paddingTop: "0.1em" }}>:</div>
<CountdownNumber value={countdown.minutes} label="Minutes" />
</motion.div>
{/* CTA */}
<motion.a
href={ctaHref}
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.45, delay: 0.8, ease: EASE }}
whileHover={{ scale: 1.04 }}
whileTap={{ scale: 0.97 }}
style={{
display: "inline-flex",
alignItems: "center",
gap: "0.5rem",
padding: "0.9375rem 2.25rem",
borderRadius: "var(--radius-full)",
background: "var(--color-foreground)",
color: "var(--color-background)",
fontWeight: 700,
fontSize: "0.9375rem",
textDecoration: "none",
letterSpacing: "-0.01em",
cursor: "pointer",
}}
>
{ctaLabel}
<motion.span
animate={{ x: [0, 4, 0] }}
transition={{ duration: 1.4, repeat: Infinity, ease: "easeInOut", repeatDelay: 0.6 }}
style={{ display: "flex" }}
>
<ArrowRight style={{ width: 16, height: 16 }} />
</motion.span>
</motion.a>
</div>
</section>
);
}