{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"countdown-timer","title":"Countdown Timer","description":"Coming soon page section with rounded visuals.","files":[{"path":"components/pro-blocks/landing-page/coming-soon-sections/countdown-timer.tsx","content":"\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useEffect, useState } from \"react\"\n\nconst DEFAULT_OFFSET_DAYS = 14\n\nexport type CountdownLabels = {\n  days: string\n  hours: string\n  minutes: string\n  seconds: string\n}\n\ntype CountdownTimerProps = {\n  /** Launch moment; without it the timer counts down from mount + 14 days. */\n  targetDate?: string | Date\n  labels?: CountdownLabels\n  ariaLabel?: string\n  inverted?: boolean\n  className?: string\n}\n\ntype TimeParts = {\n  days: number\n  hours: number\n  minutes: number\n  seconds: number\n}\n\nconst defaultLabels = {\n  days: \"Days\",\n  hours: \"Hours\",\n  minutes: \"Minutes\",\n  seconds: \"Seconds\",\n} satisfies CountdownLabels\n\nfunction getTimeParts(msLeft: number): TimeParts {\n  const totalSeconds = Math.max(0, Math.floor(msLeft / 1000))\n\n  return {\n    days: Math.floor(totalSeconds / 86400),\n    hours: Math.floor((totalSeconds % 86400) / 3600),\n    minutes: Math.floor((totalSeconds % 3600) / 60),\n    seconds: totalSeconds % 60,\n  }\n}\n\nexport function CountdownTimer({\n  targetDate,\n  labels = defaultLabels,\n  ariaLabel = \"Time left until launch\",\n  inverted = false,\n  className,\n}: CountdownTimerProps) {\n  // Stays null until mounted so server and client render the same placeholder.\n  const [timeParts, setTimeParts] = useState<TimeParts | null>(null)\n\n  useEffect(() => {\n    const target = targetDate\n      ? new Date(targetDate).getTime()\n      : Date.now() + DEFAULT_OFFSET_DAYS * 24 * 60 * 60 * 1000\n\n    const tick = () => setTimeParts(getTimeParts(target - Date.now()))\n\n    tick()\n    const interval = setInterval(tick, 1000)\n    return () => clearInterval(interval)\n  }, [targetDate])\n\n  const segments = [\n    { label: labels.days, value: timeParts?.days },\n    { label: labels.hours, value: timeParts?.hours },\n    { label: labels.minutes, value: timeParts?.minutes },\n    { label: labels.seconds, value: timeParts?.seconds },\n  ]\n\n  return (\n    <div\n      role=\"timer\"\n      aria-label={ariaLabel}\n      className={cn(\"flex gap-3 sm:gap-4\", className)}\n    >\n      {segments.map((segment) => (\n        <div key={segment.label} className=\"flex flex-col items-center gap-2\">\n          <div\n            className={cn(\n              \"flex size-16 items-center justify-center rounded-lg sm:size-20\",\n              inverted ? \"bg-white/10\" : \"bg-muted\"\n            )}\n          >\n            <span\n              className={cn(\n                \"heading-md mb-0 tabular-nums\",\n                inverted && \"text-white\"\n              )}\n            >\n              {segment.value !== undefined\n                ? String(segment.value).padStart(2, \"0\")\n                : \"00\"}\n            </span>\n          </div>\n          <span\n            className={cn(\"paragraph-sm\", inverted && \"text-white/70\")}\n          >\n            {segment.label}\n          </span>\n        </div>\n      ))}\n    </div>\n  )\n}\n","type":"registry:component"}],"meta":{"tags":["coming soon","launch","countdown","waitlist","pre-launch","under construction"]},"categories":["coming-soon-sections"],"type":"registry:component"}