{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "announcement",
  "title": "Announcement",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/announcement/AnnouncementBanner.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { ArrowRight, ChevronLeft, ChevronRight, Megaphone, Terminal, X } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { cn } from '@/lib/utils'\n\nexport interface AnnouncementItem {\n  id: string\n  category: 'release' | 'security' | 'maintenance' | 'feature'\n  badgeText: string\n  title: string\n  actionLabel: string\n  actionUrl?: string\n  cliSnippet?: string\n}\n\nexport interface AnnouncementBannerProps {\n  variant?: 'floating-pill' | 'top-bar' | 'interactive-ticker'\n  autoplay?: boolean\n  className?: string\n}\n\nconst announcements: AnnouncementItem[] = [\n  {\n    id: 'ann-1',\n    category: 'release',\n    badgeText: 'v2.4.0 Release',\n    title: 'Tailwind CSS v4 OKLCH tokens & 281+ production workbenches are live.',\n    actionLabel: 'Read Changelog',\n    actionUrl: 'https://uipkge.dev/changelog',\n    cliSnippet: 'npx shadcn-vue@latest add https://uipkge.dev/r/vue/init.json',\n  },\n  {\n    id: 'ann-2',\n    category: 'feature',\n    badgeText: 'Dual-Framework',\n    title: '100% Vue 3.5 & React 19 component parity achieved with zero runtime CSS.',\n    actionLabel: 'Explore Parity Matrix',\n    actionUrl: 'https://uipkge.dev/components',\n    cliSnippet: 'npx shadcn@latest add https://uipkge.dev/r/react/init.json',\n  },\n  {\n    id: 'ann-3',\n    category: 'security',\n    badgeText: 'Security Notice',\n    title: 'Upstream Reka UI & Radix dependency security patches verified and updated.',\n    actionLabel: 'Security Advisory',\n    actionUrl: 'https://uipkge.dev/security',\n  },\n]\n\nexport function AnnouncementBanner({\n  variant = 'interactive-ticker',\n  autoplay = true,\n  className,\n}: AnnouncementBannerProps) {\n  const [currentIndex, setCurrentIndex] = React.useState(0)\n  const [dismissed, setDismissed] = React.useState(false)\n  const [isPaused, setIsPaused] = React.useState(false)\n  const [copiedCli, setCopiedCli] = React.useState(false)\n\n  React.useEffect(() => {\n    if (!autoplay || isPaused) return\n    const timer = setInterval(() => {\n      setCurrentIndex((prev) => (prev + 1) % announcements.length)\n    }, 6000)\n    return () => clearInterval(timer)\n  }, [autoplay, isPaused])\n\n  const nextAnnouncement = () => {\n    setCurrentIndex((prev) => (prev + 1) % announcements.length)\n  }\n\n  const prevAnnouncement = () => {\n    setCurrentIndex((prev) => (prev - 1 + announcements.length) % announcements.length)\n  }\n\n  const copyCliSnippet = (snippet?: string) => {\n    if (!snippet) return\n    navigator.clipboard.writeText(snippet)\n    setCopiedCli(true)\n    setTimeout(() => setCopiedCli(false), 2000)\n  }\n\n  if (dismissed) return null\n\n  const current = announcements[currentIndex]\n\n  return (\n    <div\n      data-slot=\"announcement-banner\"\n      data-variant={variant}\n      role=\"region\"\n      aria-label=\"Announcement\"\n      className={cn('relative z-50 transition-colors duration-200', className)}\n      onMouseEnter={() => setIsPaused(true)}\n      onMouseLeave={() => setIsPaused(false)}\n    >\n      {/* 1. Interactive Ticker Mode (Default) */}\n      {variant === 'interactive-ticker' && (\n        <div className=\"bg-muted/70 border-border text-foreground flex items-center justify-between gap-3 overflow-hidden border-b px-4 py-2 text-xs shadow-xs backdrop-blur-md\">\n          <div className=\"mx-auto flex w-full max-w-6xl items-center justify-between gap-4\">\n            {/* Left: Status Beacon & Item Indicator */}\n            <div className=\"flex shrink-0 items-center gap-3\">\n              <div className=\"text-muted-foreground flex items-center gap-1.5 font-mono text-xs\">\n                <span className=\"bg-primary size-2 animate-pulse rounded-full\" />\n                <span className=\"text-foreground font-semibold\">Live Dispatch</span>\n              </div>\n\n              <Badge\n                variant=\"secondary\"\n                className={cn(\n                  'px-2 py-0.5 font-mono text-xs',\n                  current.category === 'release' && 'bg-primary/10 text-primary border-primary/20',\n                  current.category === 'security' && 'border-warning/20 bg-warning/10 text-warning',\n                  current.category === 'feature' && 'border-success/20 bg-success/10 text-success',\n                )}\n              >\n                {current.badgeText}\n              </Badge>\n            </div>\n\n            {/* Center: Dynamic Headline & Action Link */}\n            <div className=\"flex flex-1 items-center justify-center gap-2 truncate\">\n              <p className=\"text-foreground truncate font-medium\">{current.title}</p>\n              {current.actionUrl && (\n                <a\n                  href={current.actionUrl}\n                  className=\"text-primary hidden shrink-0 items-center gap-1 font-semibold hover:underline sm:inline-flex\"\n                >\n                  {current.actionLabel} <ArrowRight className=\"size-3\" />\n                </a>\n              )}\n            </div>\n\n            {/* Right: Ticker Navigation & CLI / Dismiss */}\n            <div className=\"flex shrink-0 items-center gap-2\">\n              {current.cliSnippet && (\n                <button\n                  type=\"button\"\n                  className=\"bg-background border-border text-muted-foreground hover:text-foreground hidden items-center gap-1.5 rounded border px-2 py-0.5 font-mono text-xs transition-colors md:inline-flex\"\n                  onClick={() => copyCliSnippet(current.cliSnippet)}\n                >\n                  <Terminal className=\"text-primary size-3\" />\n                  <span>{copiedCli ? 'Copied CLI!' : 'Copy CLI'}</span>\n                </button>\n              )}\n\n              <div className=\"border-border bg-background flex items-center overflow-hidden rounded-md border\">\n                <button\n                  type=\"button\"\n                  className=\"hover:bg-muted text-muted-foreground hover:text-foreground p-1 transition-colors\"\n                  aria-label=\"Previous announcement\"\n                  onClick={prevAnnouncement}\n                >\n                  <ChevronLeft className=\"size-3.5\" />\n                </button>\n                <span className=\"text-muted-foreground px-1.5 font-mono text-xs\">\n                  {currentIndex + 1}/{announcements.length}\n                </span>\n                <button\n                  type=\"button\"\n                  className=\"hover:bg-muted text-muted-foreground hover:text-foreground p-1 transition-colors\"\n                  aria-label=\"Next announcement\"\n                  onClick={nextAnnouncement}\n                >\n                  <ChevronRight className=\"size-3.5\" />\n                </button>\n              </div>\n\n              <button\n                type=\"button\"\n                aria-label=\"Dismiss banner\"\n                className=\"text-muted-foreground hover:text-foreground hover:bg-muted rounded-md p-1 transition-colors\"\n                onClick={() => setDismissed(true)}\n              >\n                <X className=\"size-3.5\" />\n              </button>\n            </div>\n          </div>\n        </div>\n      )}\n\n      {/* 2. Floating Pill Mode */}\n      {variant === 'floating-pill' && (\n        <div className=\"mx-auto w-fit max-w-2xl px-4 py-2\">\n          <div className=\"bg-card/90 border-border text-foreground hover:border-primary/40 flex items-center gap-3 rounded-full border py-1.5 pr-2 pl-3 text-xs shadow-md backdrop-blur-md transition-colors\">\n            <Badge\n              variant=\"secondary\"\n              className=\"bg-primary/10 text-primary border-primary/20 px-2 py-0.5 font-mono text-xs\"\n            >\n              {current.badgeText}\n            </Badge>\n            <p className=\"text-foreground max-w-sm truncate font-medium\">{current.title}</p>\n            {current.actionUrl && (\n              <a\n                href={current.actionUrl}\n                className=\"text-primary inline-flex shrink-0 items-center gap-1 font-semibold hover:underline\"\n              >\n                {current.actionLabel} <ArrowRight className=\"size-3\" />\n              </a>\n            )}\n            <button\n              type=\"button\"\n              aria-label=\"Dismiss announcement\"\n              className=\"text-muted-foreground hover:text-foreground hover:bg-muted rounded-full p-1 transition-colors\"\n              onClick={() => setDismissed(true)}\n            >\n              <X className=\"size-3.5\" />\n            </button>\n          </div>\n        </div>\n      )}\n\n      {/* 3. Top Solid Bar Mode */}\n      {variant === 'top-bar' && (\n        <div className=\"bg-primary text-primary-foreground flex items-center justify-between gap-3 px-4 py-2 text-xs shadow-xs\">\n          <div className=\"mx-auto flex w-full max-w-6xl items-center justify-between gap-4\">\n            <div className=\"flex items-center gap-2 truncate\">\n              <Megaphone className=\"size-4 shrink-0\" />\n              <span className=\"font-mono font-bold\">[{current.badgeText}]</span>\n              <span className=\"truncate\">{current.title}</span>\n            </div>\n            <div className=\"flex shrink-0 items-center gap-3\">\n              {current.actionUrl && (\n                <a\n                  href={current.actionUrl}\n                  className=\"inline-flex items-center gap-1 font-semibold underline-offset-4 hover:underline\"\n                >\n                  {current.actionLabel} <ArrowRight className=\"size-3\" />\n                </a>\n              )}\n              <button\n                type=\"button\"\n                aria-label=\"Dismiss banner\"\n                className=\"text-primary-foreground/80 hover:text-primary-foreground hover:bg-primary-foreground/15 rounded p-1 transition-colors\"\n                onClick={() => setDismissed(true)}\n              >\n                <X className=\"size-3.5\" />\n              </button>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  )\n}\nexport default AnnouncementBanner\n",
      "type": "registry:block",
      "target": "~/components/blocks/announcement/AnnouncementBanner.tsx"
    },
    {
      "path": "packages/registry-react/blocks/announcement/AnnouncementBannerSplit.tsx",
      "content": "'use client'\n\nimport { ArrowRight, CircleAlert, Info } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Separator } from '@/components/ui/separator'\n\nexport function AnnouncementBannerSplit({ tone = 'neutral' }: { tone?: 'neutral' | 'attention' }) {\n  const Icon = tone === 'attention' ? CircleAlert : Info\n\n  return (\n    <section\n      data-slot=\"announcement-banner-split\"\n      className={`border-b ${tone === 'attention' ? 'border-border bg-muted' : 'border-border bg-card'}`}\n    >\n      <div className=\"mx-auto flex max-w-6xl flex-col gap-4 px-6 py-4 sm:flex-row sm:items-center sm:gap-8\">\n        {/* Message column carries the classification; the action column stays\n            the same width whatever the message length, so a stack of these\n            banners aligns down the page. */}\n        <div className=\"flex min-w-0 flex-1 items-start gap-3\">\n          <Icon\n            className={`mt-0.5 size-4 shrink-0 ${tone === 'attention' ? 'text-destructive' : 'text-muted-foreground'}`}\n            aria-hidden=\"true\"\n          />\n          <div className=\"min-w-0\">\n            <div className=\"flex flex-wrap items-center gap-2\">\n              <Badge variant={tone === 'attention' ? 'outline' : 'secondary'}>\n                {tone === 'attention' ? 'Scheduled maintenance' : 'Release 2.4.0'}\n              </Badge>\n              <span className=\"text-muted-foreground font-mono text-xs\">Aug 14, 2026 · 02:00–04:00 UTC</span>\n            </div>\n            <p className=\"mt-1.5 text-sm leading-relaxed\">\n              {tone === 'attention'\n                ? 'Query serving stays online. Scheduled materialisation pauses for the window and resumes automatically.'\n                : 'Command palette, table virtualisation past 10k rows, and AA-contrast sidebar badges in dark mode.'}\n            </p>\n          </div>\n        </div>\n\n        <Separator orientation=\"vertical\" className=\"hidden h-10 sm:block\" />\n\n        <div className=\"flex shrink-0 items-center gap-2\">\n          <Button variant=\"outline\" size=\"sm\">\n            {tone === 'attention' ? 'Status page' : 'Full changelog'}\n            <ArrowRight className=\"ml-1.5 size-3.5\" aria-hidden=\"true\" />\n          </Button>\n          <Button variant=\"ghost\" size=\"sm\">\n            Subscribe\n          </Button>\n        </div>\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/announcement/AnnouncementBannerSplit.tsx"
    },
    {
      "path": "packages/registry-react/blocks/announcement/AnnouncementCornerToast.tsx",
      "content": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport { ArrowRight, Bell, X } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport { Separator } from '@/components/ui/separator'\n\ninterface AnnouncementCornerToastProps {\n  /** Milliseconds before the card slides in. */\n  delay?: number\n  /** sessionStorage key holding the dismissal. */\n  storageKey?: string\n}\n\nexport function AnnouncementCornerToast({\n  delay = 1200,\n  storageKey = 'uipkge:corner-announcement-dismissed',\n}: AnnouncementCornerToastProps) {\n  // Hidden on the server and on first paint; the delay timer is what reveals it.\n  const [visible, setVisible] = useState(false)\n\n  useEffect(() => {\n    const wasDismissed = () => {\n      try {\n        return window.sessionStorage.getItem(storageKey) === '1'\n      } catch {\n        return false\n      }\n    }\n    if (wasDismissed()) return\n    const timer = setTimeout(() => setVisible(true), delay)\n    return () => clearTimeout(timer)\n  }, [delay, storageKey])\n\n  function dismiss() {\n    setVisible(false)\n    try {\n      window.sessionStorage.setItem(storageKey, '1')\n    } catch {\n      // Blocked storage: the dismissal still holds for this page view.\n    }\n  }\n\n  return (\n    <div\n      data-slot=\"announcement-corner-toast\"\n      className={`fixed right-4 bottom-4 z-50 w-[22rem] max-w-[calc(100vw-2rem)] transition duration-300 ${\n        visible ? 'translate-y-0 opacity-100' : 'pointer-events-none invisible translate-y-4 opacity-0'\n      }`}\n      role=\"complementary\"\n      aria-label=\"Product announcement\"\n      aria-hidden={!visible}\n    >\n      <Card className=\"shadow-lg\">\n        <CardContent className=\"p-4\">\n          <div className=\"flex items-start gap-3\">\n            <span\n              className=\"border-border bg-muted flex size-10 shrink-0 items-center justify-center rounded-lg border\"\n              aria-hidden=\"true\"\n            >\n              <Bell className=\"text-primary size-4\" />\n            </span>\n            <div className=\"min-w-0 flex-1\">\n              <Badge variant=\"secondary\" className=\"mb-1.5\">\n                New\n              </Badge>\n              <p className=\"text-sm font-semibold\">Scoping now follows SCIM groups</p>\n              <p className=\"text-muted-foreground mt-1 text-xs leading-relaxed\">\n                Move someone between teams in your IdP and their dashboards follow within the sync window.\n              </p>\n            </div>\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"-mt-1 -mr-1 size-7 shrink-0\"\n              aria-label=\"Dismiss announcement\"\n              onClick={dismiss}\n            >\n              <X className=\"size-3.5\" aria-hidden=\"true\" />\n            </Button>\n          </div>\n\n          <Separator className=\"my-3\" />\n\n          <div className=\"flex items-center gap-2\">\n            <Button size=\"sm\" className=\"h-7\">\n              Read the changelog\n              <ArrowRight className=\"ml-1 size-3.5\" aria-hidden=\"true\" />\n            </Button>\n            <Button variant=\"ghost\" size=\"sm\" className=\"h-7\" onClick={dismiss}>\n              Not now\n            </Button>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/announcement/AnnouncementCornerToast.tsx"
    },
    {
      "path": "packages/registry-react/blocks/announcement/AnnouncementCountdownBar.tsx",
      "content": "'use client'\n\nimport { useEffect, useMemo, useState } from 'react'\nimport { ArrowRight, Timer, X } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Separator } from '@/components/ui/separator'\n\ninterface AnnouncementCountdownBarProps {\n  /** ISO timestamp the countdown runs to. */\n  deadline?: string\n  /** sessionStorage key holding the dismissal. */\n  storageKey?: string\n}\n\nexport function AnnouncementCountdownBar({\n  deadline = '',\n  storageKey = 'uipkge:countdown-dismissed',\n}: AnnouncementCountdownBarProps) {\n  const [dismissed, setDismissed] = useState(false)\n  // Null until mounted: the server has no clock the client agrees with, so the\n  // digits only appear once the timer is running.\n  const [remaining, setRemaining] = useState<number | null>(null)\n\n  const target = useMemo(() => {\n    const parsed = deadline ? Date.parse(deadline) : Number.NaN\n    // Fall back to 72 hours out so the demo and a mis-set prop still read sensibly.\n    return Number.isNaN(parsed) ? Date.now() + 72 * 60 * 60 * 1000 : parsed\n  }, [deadline])\n\n  useEffect(() => {\n    try {\n      setDismissed(window.sessionStorage.getItem(storageKey) === '1')\n    } catch {\n      setDismissed(false)\n    }\n    const tick = () => setRemaining(target - Date.now())\n    tick()\n    const timer = setInterval(tick, 1000)\n    return () => clearInterval(timer)\n  }, [target, storageKey])\n\n  function dismiss() {\n    setDismissed(true)\n    try {\n      window.sessionStorage.setItem(storageKey, '1')\n    } catch {\n      // Blocked storage: the dismissal still holds for this page view.\n    }\n  }\n\n  if (dismissed) return null\n\n  const total = Math.floor(Math.max(remaining ?? 0, 0) / 1000)\n  const parts = [\n    { label: 'd', value: Math.floor(total / 86400) },\n    { label: 'h', value: Math.floor((total % 86400) / 3600) },\n    { label: 'm', value: Math.floor((total % 3600) / 60) },\n    { label: 's', value: total % 60 },\n  ]\n\n  return (\n    <div data-slot=\"announcement-countdown-bar\" className=\"border-border bg-muted/60 border-b backdrop-blur\">\n      <div className=\"mx-auto flex min-h-11 max-w-6xl flex-wrap items-center gap-x-4 gap-y-2 px-6 py-2 text-sm\">\n        <Badge variant=\"secondary\" className=\"shrink-0 gap-1.5\">\n          <Timer className=\"size-3\" aria-hidden=\"true\" />\n          Ends soon\n        </Badge>\n\n        <p className=\"min-w-0\">\n          <span className=\"font-medium\">Annual plans are 20% off</span>\n          <span className=\"text-muted-foreground\"> for teams that start before the quarter closes.</span>\n        </p>\n\n        {/* tabular-nums + fixed-width cells keep the bar from reflowing each second. */}\n        {remaining !== null && (\n          <div className=\"text-muted-foreground flex items-center gap-1 font-mono text-xs\">\n            {parts.map((part) => (\n              <span key={part.label} className=\"tabular-nums\">\n                <span className=\"text-foreground inline-block min-w-[2ch] text-right font-semibold\">\n                  {String(part.value).padStart(2, '0')}\n                </span>\n                {part.label}\n              </span>\n            ))}\n          </div>\n        )}\n\n        <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n          <Button variant=\"ghost\" size=\"sm\" className=\"h-7\">\n            Claim it\n            <ArrowRight className=\"ml-1 size-3.5\" aria-hidden=\"true\" />\n          </Button>\n          <Separator orientation=\"vertical\" className=\"hidden h-5 sm:block\" />\n          <Button variant=\"ghost\" size=\"icon\" className=\"size-7\" aria-label=\"Dismiss announcement\" onClick={dismiss}>\n            <X className=\"size-3.5\" aria-hidden=\"true\" />\n          </Button>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/announcement/AnnouncementCountdownBar.tsx"
    },
    {
      "path": "packages/registry-react/blocks/announcement/AnnouncementRotatingStrip.tsx",
      "content": "'use client'\n\nimport { useEffect, useRef, useState } from 'react'\nimport { ArrowRight } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\n\nconst messages = [\n  { label: 'New', text: 'Row-level scoping now evaluates against SCIM groups.', cta: 'Changelog' },\n  { label: 'Event', text: 'Live walkthrough of the five-week rollout, every Thursday.', cta: 'Save a seat' },\n  { label: 'Docs', text: 'The reconciliation checklist for your first close is published.', cta: 'Read it' },\n]\n\nexport function AnnouncementRotatingStrip({ interval = 6000 }: { interval?: number }) {\n  const [index, setIndex] = useState(0)\n  const paused = useRef(false)\n\n  useEffect(() => {\n    const timer = setInterval(() => {\n      if (!paused.current) setIndex((value) => (value + 1) % messages.length)\n    }, interval)\n    return () => clearInterval(timer)\n  }, [interval])\n\n  return (\n    <div\n      data-slot=\"announcement-rotating-strip\"\n      className=\"border-border bg-muted/60 border-b backdrop-blur\"\n      onMouseEnter={() => (paused.current = true)}\n      onMouseLeave={() => (paused.current = false)}\n      onFocus={() => (paused.current = true)}\n      onBlur={() => (paused.current = false)}\n    >\n      <div className=\"mx-auto flex h-11 max-w-6xl items-center gap-3 px-6 text-sm\">\n        {/* aria-live announces the rotation to screen readers without stealing focus. */}\n        <div className=\"flex min-w-0 flex-1 items-center gap-3\" aria-live=\"polite\" aria-atomic=\"true\">\n          <Badge variant=\"secondary\" className=\"shrink-0\">\n            {messages[index].label}\n          </Badge>\n          <p className=\"min-w-0 truncate\">{messages[index].text}</p>\n          <Button variant=\"link\" size=\"sm\" className=\"hidden h-auto shrink-0 p-0 text-xs sm:inline-flex\">\n            {messages[index].cta}\n            <ArrowRight className=\"ml-1 size-3\" aria-hidden=\"true\" />\n          </Button>\n        </div>\n\n        <div className=\"flex shrink-0 items-center gap-1.5\">\n          {messages.map((message, i) => (\n            <button\n              key={message.text}\n              type=\"button\"\n              className={`focus-visible:ring-ring size-1.5 rounded-full transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none ${\n                i === index ? 'bg-foreground' : 'bg-muted-foreground/40 hover:bg-muted-foreground'\n              }`}\n              aria-label={`Show announcement ${i + 1} of ${messages.length}`}\n              aria-current={i === index ? 'true' : undefined}\n              onClick={() => setIndex(i)}\n            />\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/announcement/AnnouncementRotatingStrip.tsx"
    },
    {
      "path": "packages/registry-react/blocks/announcement/BannerAnnouncementSticky.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { ArrowRight, Megaphone, X } from 'lucide-react'\nimport { Badge } from '@/components/ui/badge'\nimport { cn } from '@/lib/utils'\n\nexport interface BannerAnnouncementStickyProps {\n  className?: string\n}\n\nexport function BannerAnnouncementSticky({ className }: BannerAnnouncementStickyProps) {\n  const [isVisible, setIsVisible] = React.useState(true)\n\n  if (!isVisible) return null\n\n  return (\n    <aside\n      data-slot=\"banner-announcement-sticky\"\n      className={cn(\n        'border-border bg-card/90 relative z-50 border-b px-4 py-2.5 backdrop-blur-md transition-colors sm:px-6',\n        className,\n      )}\n    >\n      <div className=\"mx-auto flex max-w-7xl items-center justify-between gap-4 font-mono text-xs\">\n        {/* Left / Center Content */}\n        <div className=\"flex min-w-0 flex-1 items-center justify-center gap-3 sm:justify-start\">\n          <Badge\n            variant=\"secondary\"\n            className=\"bg-primary/10 text-primary border-primary/20 shrink-0 px-2 py-0.5 text-xs font-bold tracking-wider uppercase\"\n          >\n            v2.4.0 Release\n          </Badge>\n\n          <p className=\"text-foreground flex items-center gap-1.5 truncate\">\n            <Megaphone className=\"text-warning hidden size-3 shrink-0 sm:inline\" />\n            <span>Announcing 450+ Marketing &amp; SaaS Blocks with Tailwind v4 OKLCH token engine.</span>\n          </p>\n\n          <a href=\"#\" className=\"text-primary inline-flex shrink-0 items-center gap-1 font-bold hover:underline\">\n            <span>Read Release Notes</span>\n            <ArrowRight className=\"size-3\" />\n          </a>\n        </div>\n\n        {/* Dismiss Trigger Button */}\n        <button\n          type=\"button\"\n          className=\"hover:bg-muted text-muted-foreground hover:text-foreground flex size-6 shrink-0 items-center justify-center rounded-md transition-colors\"\n          aria-label=\"Dismiss banner\"\n          onClick={() => setIsVisible(false)}\n        >\n          <X className=\"size-3.5\" />\n        </button>\n      </div>\n    </aside>\n  )\n}\nexport default BannerAnnouncementSticky\n",
      "type": "registry:block",
      "target": "~/components/blocks/announcement/BannerAnnouncementSticky.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/separator.json"
  ],
  "description": "Announcement band set covering six placements: a dismissible top banner in solid, soft, and gradient tones, a split release-notes band, a rotating multi-message strip, a session countdown bar, a delayed corner card, and a sticky version-pill banner.",
  "categories": [
    "marketing"
  ]
}