{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "announcement-countdown-bar",
  "title": "Announcement — Countdown Bar",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/announcement-countdown-bar/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/AnnouncementCountdownBar.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/separator.json"
  ],
  "description": "Top strip counting down to a deadline in days, hours, minutes and seconds, with the offer line, an inline CTA, and a dismiss control that keeps it closed for the session.",
  "categories": [
    "marketing"
  ],
  "deprecated": true,
  "replacedBy": "announcement"
}