{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "announcement-bar",
  "title": "Announcement Bar",
  "description": "Restrained product update strip with gradient sheen and a clean collapse dismiss.",
  "dependencies": [
    "framer-motion"
  ],
  "files": [
    {
      "path": "components/navigation/AnnouncementBar.tsx",
      "content": "'use client';\n\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { useState, type ReactNode } from 'react';\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n//\n// message       Bar copy\n// action        Optional CTA — { label, href?, onClick? }\n// icon          Optional leading mark\n// dismissible   Show collapse dismiss control\n// tone          neutral | accent | inverse\n// sticky        Stick to the top of the viewport\n// onDismiss     Fired after collapse\n// className     Styles for the outer shell\n//\ntype AnnouncementAction = {\n  label: string;\n  href?: string;\n  onClick?: () => void;\n};\n\ntype AnnouncementBarProps = {\n  message?: string;\n  action?: AnnouncementAction;\n  icon?: ReactNode;\n  dismissible?: boolean;\n  tone?: 'neutral' | 'accent' | 'inverse';\n  sticky?: boolean;\n  onDismiss?: () => void;\n  className?: string;\n};\n\nconst TONE = {\n  neutral:\n    'border-[color:var(--color-border,#e5e7eb)] bg-[var(--color-background,#fff)] text-[color:var(--color-foreground,#1f2937)]',\n  accent:\n    'border-rose-200/60 bg-linear-to-r from-rose-50 via-white to-amber-50 text-rose-950',\n  inverse:\n    'border-gray-800 bg-linear-to-r from-gray-950 via-gray-900 to-gray-950 text-white',\n};\n\nexport default function AnnouncementBar({\n  message = 'New: Coverflow Gallery is live — explore the catalog.',\n  action = { label: \"See what's new\", href: '#' },\n  icon,\n  dismissible = true,\n  tone = 'neutral',\n  sticky = false,\n  onDismiss,\n  className = '',\n}: AnnouncementBarProps) {\n  const [open, setOpen] = useState(true);\n  const inverse = tone === 'inverse';\n\n  return (\n    <AnimatePresence initial={false}>\n      {open ? (\n        <motion.div\n          className={[\n            'w-full overflow-hidden border-b',\n            sticky ? 'sticky top-0 z-50' : '',\n            TONE[tone],\n            className,\n          ]\n            .filter(Boolean)\n            .join(' ')}\n          initial={{ height: 0, opacity: 0 }}\n          animate={{ height: 'auto', opacity: 1 }}\n          exit={{ height: 0, opacity: 0 }}\n          transition={{ duration: 0.35, ease: [0.22, 1, 0.36, 1] }}\n        >\n          <div className='relative mx-auto flex max-w-5xl items-center justify-center gap-3 px-4 py-2.5 sm:px-6'>\n            <span\n              className='pointer-events-none absolute inset-0 bg-[linear-gradient(105deg,transparent_40%,rgba(255,255,255,0.35)_50%,transparent_60%)] bg-size-[200%_100%] opacity-40'\n              aria-hidden\n            />\n\n            {icon ? (\n              <span className='relative shrink-0 text-sm' aria-hidden>\n                {icon}\n              </span>\n            ) : (\n              <span\n                className={[\n                  'relative h-1.5 w-1.5 shrink-0 rounded-full',\n                  inverse ? 'bg-white/70' : 'bg-rose-400',\n                ].join(' ')}\n                aria-hidden\n              />\n            )}\n\n            <p className='relative text-center text-xs font-medium tracking-wide sm:text-[13px]'>\n              {message}\n              {action ? (\n                <>\n                  {' '}\n                  <a\n                    href={action.href ?? '#'}\n                    onClick={action.onClick}\n                    className={[\n                      'inline-flex items-center gap-1 underline-offset-4 transition-[opacity,transform] hover:underline',\n                      inverse ? 'text-white/90' : 'text-gray-900',\n                    ].join(' ')}\n                  >\n                    {action.label}\n                    <motion.span\n                      aria-hidden\n                      className='inline-block'\n                      whileHover={{ x: 2 }}\n                    >\n                      →\n                    </motion.span>\n                  </a>\n                </>\n              ) : null}\n            </p>\n\n            {dismissible ? (\n              <button\n                type='button'\n                aria-label='Dismiss announcement'\n                onClick={() => {\n                  setOpen(false);\n                  window.setTimeout(() => onDismiss?.(), 320);\n                }}\n                className={[\n                  'absolute right-3 inline-flex h-7 w-7 items-center justify-center rounded-full transition-colors sm:right-4',\n                  inverse\n                    ? 'text-white/50 hover:bg-white/10 hover:text-white'\n                    : 'text-[color:var(--color-muted-foreground,#9ca3af)] hover:bg-[var(--color-muted,#f3f4f6)] hover:text-[color:var(--color-foreground,#374151)]',\n                ].join(' ')}\n              >\n                <svg viewBox='0 0 12 12' className='h-3 w-3' aria-hidden>\n                  <path\n                    d='M3 3l6 6M9 3L3 9'\n                    stroke='currentColor'\n                    strokeWidth='1.4'\n                    strokeLinecap='round'\n                  />\n                </svg>\n              </button>\n            ) : null}\n          </div>\n        </motion.div>\n      ) : null}\n    </AnimatePresence>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/navigation/AnnouncementBar.tsx"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:component"
}