Innovations
heroes /

Announcement Bar + Hero

Dismissable promo banner strip at the top (with localStorage persistence) paired with a centered hero section below.

Preview

Source

tsx
"use client";

import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { X, ArrowRight, Megaphone } from "lucide-react";
import { Button } from "@/components/ui/button";

const STORAGE_KEY = "announcement-bar-dismissed-v1";

export default function AnnouncementBarHero() {
  const [bannerVisible, setBannerVisible] = useState(false);

  useEffect(() => {
    // Guard localStorage in useEffect for SSR safety
    const dismissed = localStorage.getItem(STORAGE_KEY);
    if (!dismissed) {
      setBannerVisible(true);
    }
  }, []);

  const dismiss = () => {
    setBannerVisible(false);
    try {
      localStorage.setItem(STORAGE_KEY, "1");
    } catch {
      // localStorage not available (private mode, etc.)
    }
  };

  return (
    <div className="flex flex-col">
      {/* Announcement Banner */}
      <AnimatePresence>
        {bannerVisible && (
          <motion.div
            initial={{ height: 0, opacity: 0 }}
            animate={{ height: "auto", opacity: 1 }}
            exit={{ height: 0, opacity: 0 }}
            transition={{ duration: 0.3, ease: "easeInOut" }}
            className="overflow-hidden"
          >
            <div className="bg-gradient-to-r from-primary via-violet-600 to-fuchsia-600 text-white">
              <div className="container mx-auto px-4 py-2.5 flex items-center justify-between gap-4">
                <div className="flex items-center gap-2 text-sm font-medium flex-1 justify-center">
                  <Megaphone className="w-4 h-4 shrink-0" />
                  <span>
                    <strong>Black Friday deal:</strong> 40% off all plans through
                    Dec 1st.
                  </span>
                  <a
                    href="#"
                    className="underline underline-offset-2 font-semibold hover:no-underline ml-1 hidden sm:inline"
                  >
                    Claim offer →
                  </a>
                </div>
                <button
                  onClick={dismiss}
                  aria-label="Dismiss announcement"
                  className="shrink-0 rounded-full p-1 hover:bg-white/20 transition-colors"
                >
                  <X className="w-4 h-4" />
                </button>
              </div>
            </div>
          </motion.div>
        )}
      </AnimatePresence>

      {/* Hero Section */}
      <section className="relative min-h-screen flex items-center justify-center overflow-hidden bg-background">
        {/* Background decoration */}
        <div className="absolute inset-0 pointer-events-none">
          <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[900px] h-[600px] bg-gradient-to-b from-primary/8 to-transparent rounded-full blur-3xl" />
        </div>

        <div className="relative z-10 container mx-auto px-6 text-center max-w-3xl">
          <motion.div
            initial={{ opacity: 0, y: 40 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.7, ease: "easeOut" }}
            className="flex flex-col items-center gap-6"
          >
            <span className="inline-flex items-center gap-2 text-xs font-semibold uppercase tracking-widest text-primary bg-primary/10 border border-primary/20 rounded-full px-4 py-2">
              Limited time offer active
            </span>

            <h1 className="text-5xl sm:text-6xl lg:text-7xl font-extrabold tracking-tight text-foreground leading-[1.05]">
              Launch your next
              <br />
              <span className="bg-gradient-to-r from-primary to-fuchsia-500 bg-clip-text text-transparent">
                big thing
              </span>
            </h1>

            <p className="text-lg sm:text-xl text-muted-foreground max-w-xl leading-relaxed">
              Everything you need to go from idea to live product in record time.
              Beautifully designed, obsessively optimized.
            </p>

            <div className="flex flex-col sm:flex-row items-center gap-3 pt-2">
              <Button size="lg" className="gap-2 text-base">
                Get 40% off today
                <ArrowRight className="w-4 h-4" />
              </Button>
              <Button size="lg" variant="outline" className="text-base">
                Learn more
              </Button>
            </div>

            <p className="text-sm text-muted-foreground">
              Offer expires Dec 1st · No credit card required
            </p>
          </motion.div>
        </div>
      </section>
    </div>
  );
}
Claude Code Instructions

CLI Install

npx innovations add announcement-bar

Where to use it

IMPORTANT: Place this ABOVE your main navbar — it should be the very first element in your page layout. In Astro layout (src/layouts/Layout.astro): import AnnouncementBarHero from '../components/innovations/heroes/announcement-bar'; // Add before <slot /> or <Header /> in your layout body In Next.js root layout (app/layout.tsx): import AnnouncementBarHero from '@/components/innovations/heroes/announcement-bar'; // Place before the <main> tag and navbar in your layout Customize the banner message and colors via the gradient classes on the banner div. Change STORAGE_KEY if you update the banner content (this ensures returning users see the new message). The hero section below can be swapped for any other hero component.