{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "points-boost",
  "title": "Points Boost",
  "description": "React callout for points multipliers and limited-time boosts. Supports optional countdown for near-ending gamification campaigns. Built on shadcn/ui + Tailwind. Open source.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "registry/trophy/ui/points-boost.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\nexport type PointsBoostStatus = \"active\" | \"scheduled\" | \"finished\"\n\ninterface PointsBoostCta {\n  link: string\n  text: string\n}\n\ninterface PointsBoostData {\n  name: string\n  status: PointsBoostStatus\n  /** Optional subtitle below the title row. */\n  description?: string\n  multiplier: number\n  endDate?: string | Date\n}\n\ninterface PointsBoostProps extends React.HTMLAttributes<HTMLDivElement> {\n  boost: PointsBoostData\n  cta: PointsBoostCta\n}\n\nfunction formatCountdown(ms: number) {\n  const totalSeconds = Math.max(0, Math.floor(ms / 1000))\n  const hours = Math.floor(totalSeconds / 3600)\n  const minutes = Math.floor((totalSeconds % 3600) / 60)\n  const seconds = totalSeconds % 60\n  return `Ends in ${hours}h ${minutes}m ${seconds}s`\n}\n\nconst PointsBoost = React.forwardRef<HTMLDivElement, PointsBoostProps>(\n  ({ className, boost, cta, ...props }, ref) => {\n    const endTimestamp = React.useMemo(() => {\n      if (!boost.endDate) return null\n      const date =\n        boost.endDate instanceof Date ? boost.endDate : new Date(boost.endDate)\n      const timestamp = date.getTime()\n      return Number.isNaN(timestamp) ? null : timestamp\n    }, [boost.endDate])\n\n    const [now, setNow] = React.useState(Date.now())\n\n    React.useEffect(() => {\n      if (!endTimestamp) return\n      const timer = window.setInterval(() => setNow(Date.now()), 1000)\n      return () => window.clearInterval(timer)\n    }, [endTimestamp])\n\n    const countdownLabel = React.useMemo(() => {\n      if (!endTimestamp) return null\n      const remaining = endTimestamp - now\n      if (remaining <= 0) return \"Ended\"\n      return formatCountdown(remaining)\n    }, [endTimestamp, now])\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          \"bg-card text-foreground flex flex-col items-start justify-between gap-4 rounded-xl border px-4 py-3 lg:flex-row lg:items-center\",\n          className\n        )}\n        {...props}\n      >\n        <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n          <p className=\"flex flex-wrap items-center gap-2 font-semibold\">\n            <span className=\"min-w-0 truncate\">{boost.name}</span>\n            <span className=\"bg-muted-foreground/10 text-primary shrink-0 rounded-full px-2 py-0.5\">\n              x{boost.multiplier}\n            </span>\n          </p>\n          {boost.description ? (\n            <p className=\"text-muted-foreground text-sm leading-snug\">\n              {boost.description}\n            </p>\n          ) : null}\n        </div>\n\n        <div className=\"flex shrink-0 items-center gap-3 self-end lg:ml-4 lg:self-center\">\n          {countdownLabel ? (\n            <span className=\"text-muted-foreground text-xs font-medium\">\n              {countdownLabel}\n            </span>\n          ) : null}\n          <Button asChild size=\"sm\">\n            <a href={cta.link}>\n              {cta.text}\n              <ChevronRight className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n            </a>\n          </Button>\n        </div>\n      </div>\n    )\n  }\n)\n\nPointsBoost.displayName = \"PointsBoost\"\n\nexport { PointsBoost }\nexport type { PointsBoostCta, PointsBoostData, PointsBoostProps }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}