heroes /
Event Countdown Hero (Playful Collage)
Vibrant event hero with bold serif headline + script accent (Caveat), two stacked colored-card photos with rotation, scattered star/squiggle decorations, location/dates, and a 4-block static countdown timer. Built from a Design Summit reference. Photos generated via nano-banana.
Preview
Source
tsx
import { Search, Menu } from "lucide-react";
export interface EventCountdownTime {
days: string;
hours: string;
minutes: string;
seconds: string;
}
export interface EventCountdownProps {
brand?: string;
navItems?: { label: string; href: string }[];
navCta?: { label: string; href: string };
eyebrow?: string;
headlinePart1?: string; // first half of "Design Summit:"
headlinePart2?: string; // second half "Unleashing"
scriptHighlight?: string; // big script word "Creative"
headlinePart3?: string; // closing "brilliance"
location?: string;
dates?: string;
primaryCta?: { label: string; href: string };
secondaryCta?: { label: string; href: string };
/** Square portrait — purple card (top right). */
imageOneSrc?: string;
imageOneAlt?: string;
/** Square portrait — pink card (bottom right). */
imageTwoSrc?: string;
imageTwoAlt?: string;
/** Static countdown numbers — replace with live JS in your project. */
countdown?: EventCountdownTime;
}
export default function EventCountdownHero({
brand = "Design Hub",
navItems = [
{ label: "Presentation", href: "#" },
{ label: "Schedule", href: "#" },
{ label: "Speakers", href: "#" },
{ label: "Workshops", href: "#" },
{ label: "About us", href: "#" },
],
navCta = { label: "Contact Us", href: "#" },
eyebrow = "Design Summit:",
headlinePart1 = "Unleashing",
scriptHighlight = "Creative",
headlinePart3 = "brilliance",
location = "San Francisco",
dates = "March 25-27",
primaryCta = { label: "Schedule", href: "#" },
secondaryCta = { label: "Learn more", href: "#" },
imageOneSrc = "/heroes/event-countdown/man.webp",
imageOneAlt = "Speaker portrait",
imageTwoSrc = "/heroes/event-countdown/woman.webp",
imageTwoAlt = "Speaker portrait",
countdown = { days: "01", hours: "02", minutes: "59", seconds: "51" },
}: EventCountdownProps) {
return (
<>
{/* Fonts: Bricolage Grotesque + Caveat (script) — shipped with component */}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,[email protected],400..800&family=Caveat:wght@600;700&display=swap"
/>
<section className="relative overflow-hidden bg-white text-slate-900">
{/* Decorative stars + sparkles + squiggle */}
<Sparkle className="absolute top-24 left-[42%] h-8 w-8 text-yellow-400 -rotate-12" />
<Sparkle className="absolute top-32 right-[26%] h-5 w-5 text-pink-400" />
<Sparkle className="absolute bottom-[42%] right-[8%] h-6 w-6 text-yellow-400 rotate-12" />
<Sparkle className="absolute top-[58%] left-[36%] h-7 w-7 text-yellow-400" />
<Squiggle className="absolute top-[18%] left-[35%] h-6 w-20 text-blue-500 -rotate-6" />
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Nav */}
<nav className="flex items-center justify-between py-6">
<a href="/" className="flex items-center gap-2">
<BrandIcon />
<span className="text-xl font-bold text-slate-900">{brand}</span>
</a>
<div className="hidden items-center gap-6 md:flex">
{navItems.map((item, i) => (
<a
key={item.label}
href={item.href}
className={`text-sm font-medium transition-colors ${
i === 0
? "text-orange-500 underline decoration-2 underline-offset-4"
: "text-slate-700 hover:text-slate-900"
}`}
>
{item.label}
</a>
))}
</div>
<div className="flex items-center gap-3">
<button
type="button"
aria-label="Open menu"
className="rounded-md p-2 text-slate-600 hover:bg-slate-100 md:hidden"
>
<Menu className="h-5 w-5" />
</button>
<a
href={navCta.href}
className="hidden rounded-full bg-orange-500 px-5 py-2.5 text-sm font-semibold text-white shadow-md shadow-orange-500/25 transition-all hover:-translate-y-0.5 hover:bg-orange-600 sm:inline-flex"
>
{navCta.label}
</a>
</div>
</nav>
{/* Hero grid */}
<div className="grid items-start gap-10 py-8 lg:grid-cols-[1.1fr_1fr] lg:gap-12 lg:py-12">
{/* Left: copy */}
<div className="relative z-10">
<p className="mb-3 text-base font-medium text-slate-700 sm:text-lg">
{eyebrow}
</p>
<h1
className="font-extrabold leading-[1.0] tracking-tight text-slate-900"
style={{
fontFamily: "'Bricolage Grotesque', system-ui, sans-serif",
fontSize: "clamp(2.75rem, 7.5vw, 5rem)",
}}
>
{headlinePart1}
<br />
<span
className="inline-block text-blue-600 italic"
style={{ fontFamily: "'Caveat', cursive", fontWeight: 700, fontSize: "1.1em" }}
>
{scriptHighlight}
</span>
<br />
{headlinePart3}
</h1>
<p className="mt-6 text-base text-slate-700 sm:text-lg">
<span className="font-semibold text-slate-900">{location}</span>{" "}
<span className="text-orange-500 font-semibold">{dates}</span>
</p>
<div className="mt-7 flex items-center gap-4">
<a
href={primaryCta.href}
className="inline-flex items-center justify-center rounded-full bg-blue-500 px-7 py-3 text-sm font-semibold text-white shadow-md shadow-blue-500/30 transition-all hover:-translate-y-0.5 hover:bg-blue-600"
>
{primaryCta.label}
</a>
<a
href={secondaryCta.href}
className="inline-flex items-center justify-center rounded-full bg-yellow-200 px-6 py-3 text-sm font-semibold text-slate-900 transition-all hover:-translate-y-0.5 hover:bg-yellow-300"
>
{secondaryCta.label}
</a>
</div>
{/* Countdown */}
<div className="mt-12 grid grid-cols-4 gap-3 max-w-md">
{([
{ value: countdown.days, label: "Days" },
{ value: countdown.hours, label: "Hours" },
{ value: countdown.minutes, label: "Minutes" },
{ value: countdown.seconds, label: "Seconds" },
]).map((unit, i, arr) => (
<div key={unit.label} className="relative text-center">
<div
className="text-3xl sm:text-4xl font-extrabold text-slate-900 tabular-nums"
style={{ fontFamily: "'Bricolage Grotesque', system-ui, sans-serif" }}
>
{unit.value}
</div>
<div className="mt-1 text-[10px] font-medium uppercase tracking-wider text-slate-500">
{unit.label}
</div>
{i < arr.length - 1 && (
<span className="absolute -right-1 top-1.5 text-3xl sm:text-4xl font-extrabold text-slate-300">
:
</span>
)}
</div>
))}
</div>
</div>
{/* Right: stacked photo cards on colored squares */}
<div className="relative">
<div className="relative grid grid-cols-2 gap-4">
{/* Photo 1 — purple card, slight rotate */}
<div className="relative -rotate-3 transition-transform hover:rotate-0">
<div className="absolute inset-0 -z-10 rounded-3xl bg-purple-300" />
<div className="overflow-hidden rounded-3xl">
<img
src={imageOneSrc}
alt={imageOneAlt}
width={400}
height={400}
loading="eager"
fetchPriority="high"
decoding="async"
className="aspect-square w-full object-cover"
/>
</div>
</div>
{/* Photo 2 — pink card, opposite rotate, offset down */}
<div className="relative mt-12 rotate-3 transition-transform hover:rotate-0">
<div className="absolute inset-0 -z-10 rounded-3xl bg-pink-300" />
<div className="overflow-hidden rounded-3xl">
<img
src={imageTwoSrc}
alt={imageTwoAlt}
width={400}
height={400}
loading="eager"
fetchPriority="high"
decoding="async"
className="aspect-square w-full object-cover"
/>
</div>
</div>
</div>
{/* Yellow burst sticker behind photos */}
<div
aria-hidden
className="absolute -top-6 right-6 -z-20 h-44 w-44 rounded-full bg-yellow-300 blur-[2px]"
/>
</div>
</div>
</div>
</section>
</>
);
}
function BrandIcon() {
return (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="none" aria-hidden>
<rect x="2" y="2" width="9" height="9" rx="1.5" fill="#f97316" />
<rect x="13" y="2" width="9" height="9" rx="1.5" fill="#facc15" />
<rect x="2" y="13" width="9" height="9" rx="1.5" fill="#3b82f6" />
<rect x="13" y="13" width="9" height="9" rx="1.5" fill="#ec4899" />
</svg>
);
}
function Sparkle({ className }: { className?: string }) {
return (
<svg viewBox="0 0 24 24" className={className} fill="currentColor" aria-hidden>
<path d="M12 2 L13.5 8.5 L20 10 L13.5 11.5 L12 18 L10.5 11.5 L4 10 L10.5 8.5 Z" />
</svg>
);
}
function Squiggle({ className }: { className?: string }) {
return (
<svg viewBox="0 0 80 24" className={className} fill="none" aria-hidden>
<path
d="M2 12 Q 12 2 22 12 T 42 12 T 62 12 T 78 12"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
/>
</svg>
);
} Claude Code Instructions
CLI Install
npx innovations add event-countdownWhere to use it
Use this for events, conferences, summits, retreats, workshops — anything with a date and a vibe.
In Astro:
---
import EventCountdownHero from '../components/innovations/heroes/event-countdown';
---
<EventCountdownHero />
No client:* required — countdown is static. To make it tick, wrap the component in a client island and pass updated countdown values via props (or fork the component and add a setInterval inside).
In Next.js:
import EventCountdownHero from '@/components/innovations/heroes/event-countdown';
CUSTOMIZATION:
<EventCountdownHero
brand="Your Hub"
eyebrow="Your Event:"
headlinePart1="Unleashing"
scriptHighlight="Bold" /* this is the big Caveat script word */
headlinePart3="brilliance"
location="Your City"
dates="Month 1-3"
primaryCta={{ label: "Register", href: "/register" }}
secondaryCta={{ label: "Schedule", href: "/schedule" }}
imageOneSrc="/your-speaker-1.webp"
imageTwoSrc="/your-speaker-2.webp"
countdown={{ days: "12", hours: "04", minutes: "33", seconds: "07" }}
/>
FONTS: Bricolage Grotesque (display) + Caveat (script accent) shipped via baked-in Google Fonts <link>.
PALETTE: yellow/blue/pink/purple sticker palette + orange CTA. To rebrand, edit the hardcoded Tailwind color classes (yellow-300/400, blue-500/600, pink-300/400, orange-500/600, purple-300).
PHOTO CARDS: each photo sits on a colored square (purple + pink) with a slight ±3° rotation that straightens on hover. The yellow blur burst behind them is decorative. Swap photo backgrounds via the colored card divs in the JSX if you want different palette accents.