{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "countdown",
  "title": "Countdown",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/countdown/Countdown.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Snippet } from 'svelte'\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export type CountdownFormat = 'DD:HH:MM:SS' | 'HH:MM:SS' | 'MM:SS' | 'SS'\n\n  export interface CountdownParts {\n    days: number\n    hours: number\n    minutes: number\n    seconds: number\n  }\n\n  /** React parity alias — the full render-prop payload (`parts` + display string + finished). */\n  export type CountdownRenderProps = CountdownParts & { display: string; finished: boolean }\n\n  export interface CountdownProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n    /** Target date/time. Accepts a Date, ISO string, or epoch ms number. */\n    target: Date | string | number\n    /** Display format. Custom tokens: DD days, HH hours, MM minutes, SS seconds. */\n    format?: CountdownFormat | string\n    /** Pause the countdown. */\n    paused?: boolean\n    /** Optional label rendered above the countdown. */\n    label?: string\n    /** Show leading zeros (e.g. 05 vs 5). */\n    pad?: boolean\n    /** Separator between units. */\n    separator?: string\n    /** Fires once when the countdown reaches zero. */\n    onfinish?: () => void\n    /** Fires every second with the remaining ms. */\n    ontick?: (remaining: number) => void\n    /** Default content override — receives parts, the formatted display string, and finished. */\n    children?: Snippet<[CountdownParts & { display: string; finished: boolean }]>\n    /** Per-unit overrides. */\n    days?: Snippet<[{ days: number }]>\n    hours?: Snippet<[{ hours: number }]>\n    minutes?: Snippet<[{ minutes: number }]>\n    seconds?: Snippet<[{ seconds: number }]>\n    ref?: HTMLDivElement | null\n  }\n</script>\n\n<script lang=\"ts\">\n  import { untrack } from 'svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    target,\n    format = 'DD:HH:MM:SS',\n    paused = false,\n    label = '',\n    pad = true,\n    separator = ':',\n    onfinish,\n    ontick,\n    children,\n    days: daysSnippet,\n    hours: hoursSnippet,\n    minutes: minutesSnippet,\n    seconds: secondsSnippet,\n    class: className,\n    ref = $bindable(null),\n    ...restProps\n  }: CountdownProps = $props()\n\n  let now = $state(Date.now())\n  let finished = $state(false)\n\n  const targetMs = $derived(\n    target instanceof Date ? target.getTime() : typeof target === 'number' ? target : new Date(target).getTime(),\n  )\n  const remainingMs = $derived(Math.max(0, targetMs - now))\n\n  const parts = $derived<CountdownParts>(byParts(remainingMs))\n\n  function byParts(total: number): CountdownParts {\n    const days = Math.floor(total / 86_400_000)\n    const hours = Math.floor((total % 86_400_000) / 3_600_000)\n    const minutes = Math.floor((total % 3_600_000) / 60_000)\n    const seconds = Math.floor((total % 60_000) / 1000)\n    return { days, hours, minutes, seconds }\n  }\n\n  /** Values actually painted for each unit under the active format (rolled-up totals for compact formats). */\n  const displayParts = $derived.by((): CountdownParts => {\n    const { days, hours, minutes, seconds } = parts\n    if (format === 'HH:MM:SS') {\n      return { days, hours: days * 24 + hours, minutes, seconds }\n    }\n    if (format === 'MM:SS') {\n      return { days, hours, minutes: days * 24 * 60 + hours * 60 + minutes, seconds }\n    }\n    if (format === 'SS') {\n      return { days, hours, minutes, seconds: Math.floor(remainingMs / 1000) }\n    }\n    // DD:HH:MM:SS and custom token formats use modular parts.\n    return { days, hours, minutes, seconds }\n  })\n\n  function pad2(n: number) {\n    return pad ? String(n).padStart(2, '0') : String(n)\n  }\n\n  /** Split a unit string into chars so only changed digits remount + flip. */\n  function unitChars(n: number) {\n    return [...pad2(n)]\n  }\n\n  const display = $derived.by(() => {\n    const { days, hours, minutes, seconds } = parts\n    const sep = separator\n    if (format === 'DD:HH:MM:SS') return `${pad2(days)}${sep}${pad2(hours)}${sep}${pad2(minutes)}${sep}${pad2(seconds)}`\n    if (format === 'HH:MM:SS') return `${pad2(days * 24 + hours)}${sep}${pad2(minutes)}${sep}${pad2(seconds)}`\n    if (format === 'MM:SS') return `${pad2(days * 24 * 60 + hours * 60 + minutes)}${sep}${pad2(seconds)}`\n    if (format === 'SS') return pad2(Math.floor(remainingMs / 1000))\n    // Custom token format: replace DD, HH, MM, SS tokens.\n    return format\n      .replace('DD', pad2(days))\n      .replace('HH', pad2(hours))\n      .replace('MM', pad2(minutes))\n      .replace('SS', pad2(seconds))\n  })\n\n  $effect(() => {\n    // Restart the timer when the target or paused flag changes. Everything\n    // time-derived is read inside untrack so the 1s tick doesn't re-trigger\n    // this effect (which would reset `finished` and recreate the interval).\n    const watchedTarget = target\n    const watchedPaused = paused\n    void watchedTarget\n    let alreadyDone = false\n    untrack(() => {\n      finished = false\n      now = Date.now()\n      // Fire finish immediately when the target is already past (don't wait for first tick).\n      if (remainingMs <= 0) {\n        finished = true\n        alreadyDone = true\n        onfinish?.()\n      }\n    })\n    if (alreadyDone || watchedPaused) return\n    const timer = setInterval(() => {\n      now = Date.now()\n      ontick?.(remainingMs)\n      if (remainingMs <= 0) {\n        finished = true\n        clearInterval(timer)\n        onfinish?.()\n      }\n    }, 1000)\n    return () => clearInterval(timer)\n  })\n</script>\n\n<div\n  bind:this={ref}\n  data-uipkge\n  data-slot=\"countdown\"\n  data-finished={finished}\n  data-paused={paused}\n  class={cn('inline-flex flex-col gap-1', className)}\n  {...restProps}\n>\n  {#if label}\n    <span data-slot=\"countdown-label\" class=\"text-muted-foreground text-xs font-medium tracking-wide uppercase\">\n      {label}\n    </span>\n  {/if}\n  <div\n    data-slot=\"countdown-display\"\n    class=\"flex items-baseline gap-1 font-mono tabular-nums\"\n    role=\"timer\"\n    aria-live={finished || paused ? 'off' : 'polite'}\n    aria-atomic=\"true\"\n  >\n    {#if children}\n      {@render children({ ...parts, display, finished })}\n    {:else}\n      {#if format.includes('DD')}\n        {#if daysSnippet}\n          {@render daysSnippet({ days: parts.days })}\n        {:else}\n          <span data-slot=\"countdown-days\" class=\"text-foreground inline-flex text-2xl font-semibold\">\n            {#each unitChars(displayParts.days) as ch, i (`d-${i}-${ch}`)}\n              <span class=\"countdown-digit inline-block tabular-nums\">{ch}</span>\n            {/each}\n          </span>\n        {/if}\n      {/if}\n      {#if format.includes('DD') && format.includes('HH')}\n        <span class=\"text-muted-foreground text-2xl\">{separator}</span>\n      {/if}\n      {#if format.includes('HH')}\n        {#if hoursSnippet}\n          {@render hoursSnippet({ hours: parts.hours })}\n        {:else}\n          <span data-slot=\"countdown-hours\" class=\"text-foreground inline-flex text-2xl font-semibold\">\n            {#each unitChars(displayParts.hours) as ch, i (`h-${i}-${ch}`)}\n              <span class=\"countdown-digit inline-block tabular-nums\">{ch}</span>\n            {/each}\n          </span>\n        {/if}\n      {/if}\n      {#if format.includes('HH') && format.includes('MM')}\n        <span class=\"text-muted-foreground text-2xl\">{separator}</span>\n      {/if}\n      {#if format.includes('MM')}\n        {#if minutesSnippet}\n          {@render minutesSnippet({ minutes: parts.minutes })}\n        {:else}\n          <span data-slot=\"countdown-minutes\" class=\"text-foreground inline-flex text-2xl font-semibold\">\n            {#each unitChars(displayParts.minutes) as ch, i (`m-${i}-${ch}`)}\n              <span class=\"countdown-digit inline-block tabular-nums\">{ch}</span>\n            {/each}\n          </span>\n        {/if}\n      {/if}\n      {#if format.includes('MM') && format.includes('SS')}\n        <span class=\"text-muted-foreground text-2xl\">{separator}</span>\n      {/if}\n      {#if format.includes('SS')}\n        {#if secondsSnippet}\n          {@render secondsSnippet({ seconds: parts.seconds })}\n        {:else}\n          <span data-slot=\"countdown-seconds\" class=\"text-foreground inline-flex text-2xl font-semibold\">\n            {#each unitChars(displayParts.seconds) as ch, i (`s-${i}-${ch}`)}\n              <span class=\"countdown-digit inline-block tabular-nums\">{ch}</span>\n            {/each}\n          </span>\n        {/if}\n      {/if}\n    {/if}\n  </div>\n</div>\n\n<style>\n  @keyframes countdown-digit-flip {\n    0% {\n      opacity: 0;\n      transform: translateY(45%) scale(0.92);\n    }\n    100% {\n      opacity: 1;\n      transform: translateY(0) scale(1);\n    }\n  }\n\n  [data-slot='countdown'] .countdown-digit {\n    animation: countdown-digit-flip 280ms cubic-bezier(0.22, 1, 0.36, 1) both;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot='countdown'] .countdown-digit {\n      animation: none !important;\n    }\n  }\n</style>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/countdown/Countdown.svelte"
    },
    {
      "path": "packages/registry-svelte/components/countdown/index.ts",
      "content": "export {\n  default as Countdown,\n  type CountdownProps,\n  type CountdownParts,\n  type CountdownRenderProps,\n  type CountdownFormat,\n} from './Countdown.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/countdown/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Countdown timer that shows time remaining to a target date. Digits flip on each tick. Supports DD:HH:MM:SS, HH:MM:SS, MM:SS, and SS formats plus custom token formats, snippet overrides for days/hours/minutes/seconds, a label, leading-zero padding, a custom separator, paused state, and on-finish/tick callbacks.",
  "categories": [
    "display",
    "utility"
  ]
}