Innovations
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,wght@12..96,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-countdown

Where 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.