{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timezone",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "timezone.tsx",
      "content": "\"use client\";\n\nimport {\n  AnimatePresence,\n  motion,\n  useReducedMotion,\n  useSpring,\n} from \"motion/react\";\nimport {\n  forwardRef,\n  type ReactNode,\n  type Ref,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface TimezoneProps {\n  /** City name, alias, or IANA timezone such as `America/Los_Angeles`. */\n  zone: string;\n  /** 12-hour or 24-hour clock formatting. */\n  format?: \"12h\" | \"24h\";\n  /**\n   * Show a timezone label after the clock.\n   * @deprecated Use `showZoneLabel` instead.\n   */\n  showAbbreviation?: boolean;\n  /** Show a timezone label after the clock. */\n  showZoneLabel?: boolean;\n  /**\n   * How the timezone label is rendered when `showZoneLabel` is true.\n   * `abbreviation` shows letter codes like IST or EST.\n   * `offset` shows offset labels like GMT+5:30.\n   */\n  zoneName?: \"abbreviation\" | \"offset\";\n  /** Update every second instead of every minute. */\n  live?: boolean;\n  className?: string;\n  locale?: string;\n  /** Custom content when `zone` cannot be resolved. */\n  fallback?: ReactNode;\n  /** Called when `zone` cannot be resolved. */\n  onError?: (zone: string) => void;\n  /**\n   * When false, disables digit and separator motion even if reduced motion is off.\n   * @default true\n   */\n  animate?: boolean;\n  /**\n   * Adds `aria-live` to a screen-reader-only layer. `true` maps to `\"polite\"`.\n   * Defaults to `\"polite\"` when `live` is false and off when `live` is true.\n   */\n  ariaLive?: boolean | \"polite\" | \"assertive\" | \"off\";\n}\n\ntype TimezoneClockProps = Omit<TimezoneProps, \"fallback\" | \"onError\"> & {\n  resolvedZone: string;\n};\n\ntype TimezoneErrorProps = Pick<\n  TimezoneProps,\n  \"zone\" | \"fallback\" | \"onError\" | \"className\"\n>;\n\n/**\n * Curated aliases that override auto-generated city matches.\n * Some aliases are intentionally regional, such as `portland` (Oregon) and `la` (Los Angeles).\n */\nconst MANUAL_ZONE_ALIASES: Record<string, string> = {\n  sf: \"America/Los_Angeles\",\n  \"san francisco\": \"America/Los_Angeles\",\n  \"san fransisco\": \"America/Los_Angeles\",\n  \"san fransico\": \"America/Los_Angeles\",\n  \"los angeles\": \"America/Los_Angeles\",\n  la: \"America/Los_Angeles\",\n  seattle: \"America/Los_Angeles\",\n  portland: \"America/Los_Angeles\",\n  vancouver: \"America/Vancouver\",\n  denver: \"America/Denver\",\n  chicago: \"America/Chicago\",\n  austin: \"America/Chicago\",\n  dallas: \"America/Chicago\",\n  houston: \"America/Chicago\",\n  miami: \"America/New_York\",\n  atlanta: \"America/New_York\",\n  boston: \"America/New_York\",\n  \"new york\": \"America/New_York\",\n  nyc: \"America/New_York\",\n  toronto: \"America/Toronto\",\n  london: \"Europe/London\",\n  uk: \"Europe/London\",\n  paris: \"Europe/Paris\",\n  berlin: \"Europe/Berlin\",\n  amsterdam: \"Europe/Amsterdam\",\n  madrid: \"Europe/Madrid\",\n  rome: \"Europe/Rome\",\n  dubai: \"Asia/Dubai\",\n  uae: \"Asia/Dubai\",\n  mumbai: \"Asia/Kolkata\",\n  delhi: \"Asia/Kolkata\",\n  bangalore: \"Asia/Kolkata\",\n  india: \"Asia/Kolkata\",\n  singapore: \"Asia/Singapore\",\n  \"hong kong\": \"Asia/Hong_Kong\",\n  tokyo: \"Asia/Tokyo\",\n  seoul: \"Asia/Seoul\",\n  sydney: \"Australia/Sydney\",\n  melbourne: \"Australia/Melbourne\",\n  auckland: \"Pacific/Auckland\",\n  utc: \"UTC\",\n  gmt: \"UTC\",\n};\n\nconst FALLBACK_TIMEZONES = [\n  \"UTC\",\n  \"Etc/UTC\",\n  \"Etc/GMT\",\n  \"America/Los_Angeles\",\n  \"America/New_York\",\n  \"America/Chicago\",\n  \"America/Denver\",\n  \"Europe/London\",\n  \"Europe/Paris\",\n  \"Europe/Berlin\",\n  \"Asia/Tokyo\",\n  \"Asia/Shanghai\",\n  \"Asia/Dubai\",\n  \"Asia/Kolkata\",\n  \"Asia/Singapore\",\n  \"Australia/Sydney\",\n  \"Pacific/Auckland\",\n] as const;\n\nconst DEFAULT_LOCALE = \"en-US\";\nconst LATIN_NUMBERING_SYSTEM = \"latn\" as const;\nconst DIGIT_PATTERN = /\\d/;\nconst COLON_PATTERN = /[:.]/;\nconst LETTER_ABBREV = /^[A-Z]{2,5}$/;\nconst GMT_OFFSET_PATTERN = /GMT([+-])(\\d{1,2})(?::(\\d{2}))?/;\n\n/** [standard, daylight?] */\nconst ABBREVIATION_FALLBACK: Record<string, readonly [string, string?]> = {\n  \"Africa/Cairo\": [\"EET\"],\n  \"America/Anchorage\": [\"AKST\", \"AKDT\"],\n  \"America/Argentina/Buenos_Aires\": [\"ART\"],\n  \"America/Bogota\": [\"COT\"],\n  \"America/Chicago\": [\"CST\", \"CDT\"],\n  \"America/Denver\": [\"MST\", \"MDT\"],\n  \"America/Detroit\": [\"EST\", \"EDT\"],\n  \"America/Edmonton\": [\"MST\", \"MDT\"],\n  \"America/Halifax\": [\"AST\", \"ADT\"],\n  \"America/Lima\": [\"PET\"],\n  \"America/Los_Angeles\": [\"PST\", \"PDT\"],\n  \"America/Mexico_City\": [\"CST\", \"CDT\"],\n  \"America/New_York\": [\"EST\", \"EDT\"],\n  \"America/Phoenix\": [\"MST\"],\n  \"America/Santiago\": [\"CLT\", \"CLST\"],\n  \"America/Sao_Paulo\": [\"BRT\", \"BRST\"],\n  \"America/St_Johns\": [\"NST\", \"NDT\"],\n  \"America/Toronto\": [\"EST\", \"EDT\"],\n  \"America/Vancouver\": [\"PST\", \"PDT\"],\n  \"America/Winnipeg\": [\"CST\", \"CDT\"],\n  \"Asia/Bangkok\": [\"ICT\"],\n  \"Asia/Dhaka\": [\"BST\"],\n  \"Asia/Dubai\": [\"GST\"],\n  \"Asia/Hong_Kong\": [\"HKT\"],\n  \"Asia/Jakarta\": [\"WIB\"],\n  \"Asia/Karachi\": [\"PKT\"],\n  \"Asia/Kolkata\": [\"IST\"],\n  \"Asia/Manila\": [\"PST\"],\n  \"Asia/Seoul\": [\"KST\"],\n  \"Asia/Shanghai\": [\"CST\"],\n  \"Asia/Singapore\": [\"SGT\"],\n  \"Asia/Taipei\": [\"CST\"],\n  \"Asia/Tokyo\": [\"JST\"],\n  \"Atlantic/Reykjavik\": [\"GMT\"],\n  \"Australia/Brisbane\": [\"AEST\"],\n  \"Australia/Melbourne\": [\"AEST\", \"AEDT\"],\n  \"Australia/Perth\": [\"AWST\"],\n  \"Australia/Sydney\": [\"AEST\", \"AEDT\"],\n  \"Etc/GMT\": [\"GMT\"],\n  \"Etc/UTC\": [\"UTC\"],\n  \"Europe/Amsterdam\": [\"CET\", \"CEST\"],\n  \"Europe/Berlin\": [\"CET\", \"CEST\"],\n  \"Europe/Istanbul\": [\"TRT\"],\n  \"Europe/London\": [\"GMT\", \"BST\"],\n  \"Europe/Madrid\": [\"CET\", \"CEST\"],\n  \"Europe/Moscow\": [\"MSK\"],\n  \"Europe/Paris\": [\"CET\", \"CEST\"],\n  \"Europe/Rome\": [\"CET\", \"CEST\"],\n  \"Europe/Warsaw\": [\"CET\", \"CEST\"],\n  \"Europe/Zurich\": [\"CET\", \"CEST\"],\n  \"Pacific/Auckland\": [\"NZST\", \"NZDT\"],\n  \"Pacific/Fiji\": [\"FJT\", \"FJST\"],\n  \"Pacific/Honolulu\": [\"HST\"],\n  UTC: [\"UTC\"],\n};\n\nconst TIME_MOTION = {\n  enterStiffness: 210,\n  enterDamping: 16,\n  exitStiffness: 220,\n  exitDamping: 20,\n  enterY: 16,\n  enterScale: 0.84,\n  exitScale: 0.84,\n} as const;\n\nconst MotionTime = motion.create(\"time\");\n\nlet exitId = 0;\n\ninterface ExitItem {\n  id: number;\n  char: string;\n  exitY: number;\n}\n\nexport interface TimeLabelCell {\n  char: string;\n  key: string;\n  kind: \"digit\" | \"colon\" | \"space\" | \"glyph\";\n}\n\ntype ClockSubscriber = (now: Date) => void;\n\ninterface ClockChannel {\n  intervalId?: number;\n  subscribers: Set<ClockSubscriber>;\n  timeoutId?: number;\n}\n\nconst minuteChannel: ClockChannel = { subscribers: new Set() };\nconst liveChannel: ClockChannel = { subscribers: new Set() };\nlet documentHidden = false;\nlet visibilityListenerAttached = false;\n\nfunction createDateTimeFormatOptions(\n  options: Intl.DateTimeFormatOptions\n): Intl.DateTimeFormatOptions {\n  return {\n    numberingSystem: LATIN_NUMBERING_SYSTEM,\n    ...options,\n  };\n}\n\nfunction isDigitChar(char: string) {\n  return DIGIT_PATTERN.test(char);\n}\n\nfunction isColonChar(char: string) {\n  return COLON_PATTERN.test(char);\n}\n\nfunction normalizeZoneKey(zone: string) {\n  return zone\n    .trim()\n    .toLowerCase()\n    .replace(/[._-]+/g, \" \")\n    .replace(/\\s+/g, \" \");\n}\n\nfunction getAllTimezones() {\n  if (\n    typeof Intl !== \"undefined\" &&\n    typeof Intl.supportedValuesOf === \"function\"\n  ) {\n    try {\n      return Intl.supportedValuesOf(\"timeZone\");\n    } catch {\n      return [...FALLBACK_TIMEZONES];\n    }\n  }\n\n  return [...FALLBACK_TIMEZONES];\n}\n\nfunction buildTimezoneLookup(\n  zones: readonly string[],\n  manualAliases: Record<string, string>\n) {\n  const lookup: Record<string, string> = { ...manualAliases };\n  const cityToZones = new Map<string, string[]>();\n\n  for (const zone of zones) {\n    const zoneKey = normalizeZoneKey(zone);\n    if (!lookup[zoneKey]) {\n      lookup[zoneKey] = zone;\n    }\n\n    const segments = zone.split(\"/\");\n    const citySlug = segments.at(-1) ?? zone;\n    const cityKey = normalizeZoneKey(citySlug.replace(/_/g, \" \"));\n\n    if (cityKey) {\n      const matches = cityToZones.get(cityKey) ?? [];\n      matches.push(zone);\n      cityToZones.set(cityKey, matches);\n    }\n\n    if (segments.length >= 2) {\n      const regionCityKey = normalizeZoneKey(\n        `${segments[0]} ${citySlug.replace(/_/g, \" \")}`\n      );\n      if (!lookup[regionCityKey]) {\n        lookup[regionCityKey] = zone;\n      }\n    }\n  }\n\n  for (const [cityKey, matches] of cityToZones) {\n    const match = matches[0];\n    if (!lookup[cityKey] && match) {\n      lookup[cityKey] = match;\n    }\n  }\n\n  return lookup;\n}\n\ntype TimezoneIndex = {\n  lookup: Record<string, string>;\n  zones: readonly string[];\n  zoneSet: ReadonlySet<string>;\n};\n\nlet timezoneIndex: TimezoneIndex | null = null;\n\nfunction getTimezoneIndex(): TimezoneIndex {\n  if (!timezoneIndex) {\n    const zones = getAllTimezones();\n    timezoneIndex = {\n      zones,\n      zoneSet: new Set(zones),\n      lookup: buildTimezoneLookup(zones, MANUAL_ZONE_ALIASES),\n    };\n  }\n\n  return timezoneIndex;\n}\n\n/** Clears the cached timezone index. Intended for tests. */\nexport function resetTimezoneIndexForTests() {\n  timezoneIndex = null;\n}\n\n/** Every IANA timezone available in the current runtime environment. */\nexport function getWorldTimezones() {\n  return [...getTimezoneIndex().zones].sort((left, right) =>\n    left.localeCompare(right)\n  );\n}\n\nfunction isValidTimeZone(timeZone: string) {\n  try {\n    Intl.DateTimeFormat(DEFAULT_LOCALE, {\n      timeZone,\n      numberingSystem: LATIN_NUMBERING_SYSTEM,\n    }).format(new Date());\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nexport function resolveTimezone(zone: string) {\n  const trimmed = zone.trim();\n  if (!trimmed) {\n    return null;\n  }\n\n  const { lookup, zoneSet } = getTimezoneIndex();\n\n  if (zoneSet.has(trimmed)) {\n    return trimmed;\n  }\n\n  const normalized = normalizeZoneKey(trimmed);\n  const resolved = lookup[normalized];\n  if (resolved) {\n    return resolved;\n  }\n\n  if (trimmed.includes(\"/\") && isValidTimeZone(trimmed)) {\n    return trimmed;\n  }\n\n  if (isValidTimeZone(trimmed)) {\n    return trimmed;\n  }\n\n  return null;\n}\n\nexport function shouldDigitRollUp(previous: string, next: string) {\n  const previousDigit = Number(previous);\n  const nextDigit = Number(next);\n\n  if (!(Number.isFinite(previousDigit) && Number.isFinite(nextDigit))) {\n    return true;\n  }\n\n  if (nextDigit > previousDigit) {\n    return true;\n  }\n\n  if (nextDigit < previousDigit) {\n    return previousDigit === 9 && nextDigit === 0;\n  }\n\n  return true;\n}\n\nexport function resolveTimezoneAriaLive(\n  ariaLive: TimezoneProps[\"ariaLive\"],\n  live: boolean\n): \"polite\" | \"assertive\" | undefined {\n  if (ariaLive === false || ariaLive === \"off\") {\n    return undefined;\n  }\n\n  if (ariaLive === \"assertive\") {\n    return \"assertive\";\n  }\n\n  if (ariaLive === true || ariaLive === \"polite\") {\n    return \"polite\";\n  }\n\n  return live ? undefined : \"polite\";\n}\n\nexport function buildTimeLabelCells(label: string): TimeLabelCell[] {\n  const chars = label.split(\"\");\n  const digitIndexFromRight = new Map<number, number>();\n  const colonIndexFromLeft = new Map<number, number>();\n  let digitCount = 0;\n\n  for (let index = chars.length - 1; index >= 0; index -= 1) {\n    if (isDigitChar(chars[index])) {\n      digitIndexFromRight.set(index, digitCount);\n      digitCount += 1;\n    }\n  }\n\n  let colonCount = 0;\n  for (let index = 0; index < chars.length; index += 1) {\n    if (isColonChar(chars[index])) {\n      colonIndexFromLeft.set(index, colonCount);\n      colonCount += 1;\n    }\n  }\n\n  return chars.map((char, index) => {\n    if (isDigitChar(char)) {\n      return {\n        char,\n        key: `digit-${digitIndexFromRight.get(index) ?? index}`,\n        kind: \"digit\" as const,\n      };\n    }\n\n    if (isColonChar(char)) {\n      return {\n        char,\n        key: `colon-${colonIndexFromLeft.get(index) ?? index}`,\n        kind: \"colon\" as const,\n      };\n    }\n\n    if (char === \" \") {\n      return {\n        char,\n        key: `space-${index}`,\n        kind: \"space\" as const,\n      };\n    }\n\n    return {\n      char,\n      key: `glyph-${index}`,\n      kind: \"glyph\" as const,\n    };\n  });\n}\n\nfunction notifyChannel(channel: ClockChannel) {\n  const now = new Date();\n  for (const subscriber of channel.subscribers) {\n    subscriber(now);\n  }\n}\n\nfunction stopChannel(channel: ClockChannel) {\n  if (typeof window === \"undefined\") {\n    return;\n  }\n\n  if (channel.timeoutId !== undefined) {\n    window.clearTimeout(channel.timeoutId);\n    channel.timeoutId = undefined;\n  }\n\n  if (channel.intervalId !== undefined) {\n    window.clearInterval(channel.intervalId);\n    channel.intervalId = undefined;\n  }\n}\n\nfunction startChannel(channel: ClockChannel, live: boolean) {\n  if (\n    typeof window === \"undefined\" ||\n    documentHidden ||\n    channel.subscribers.size === 0\n  ) {\n    return;\n  }\n\n  if (channel.intervalId !== undefined || channel.timeoutId !== undefined) {\n    return;\n  }\n\n  const intervalMs = live ? 1000 : 60_000;\n  const startInterval = () => {\n    channel.intervalId = window.setInterval(\n      () => notifyChannel(channel),\n      intervalMs\n    );\n  };\n\n  if (live) {\n    startInterval();\n    return;\n  }\n\n  const msUntilNextMinute = intervalMs - (Date.now() % intervalMs);\n  channel.timeoutId = window.setTimeout(() => {\n    channel.timeoutId = undefined;\n    notifyChannel(channel);\n    startInterval();\n  }, msUntilNextMinute);\n}\n\nfunction restartClockChannels() {\n  stopClockChannels();\n  ensureClockChannels();\n}\n\nfunction ensureClockChannels() {\n  startChannel(liveChannel, true);\n  startChannel(minuteChannel, false);\n}\n\nfunction stopClockChannels() {\n  stopChannel(liveChannel);\n  stopChannel(minuteChannel);\n}\n\nfunction attachVisibilityListener() {\n  if (visibilityListenerAttached || typeof document === \"undefined\") {\n    return;\n  }\n\n  visibilityListenerAttached = true;\n  documentHidden = document.hidden;\n\n  document.addEventListener(\"visibilitychange\", () => {\n    documentHidden = document.hidden;\n\n    if (documentHidden) {\n      stopClockChannels();\n      return;\n    }\n\n    notifyChannel(minuteChannel);\n    notifyChannel(liveChannel);\n    restartClockChannels();\n  });\n}\n\nfunction subscribeClock(live: boolean, subscriber: ClockSubscriber) {\n  attachVisibilityListener();\n\n  const channel = live ? liveChannel : minuteChannel;\n  channel.subscribers.add(subscriber);\n  subscriber(new Date());\n  ensureClockChannels();\n\n  return () => {\n    channel.subscribers.delete(subscriber);\n\n    if (channel.subscribers.size === 0) {\n      stopChannel(channel);\n    }\n  };\n}\n\nfunction useNow(live: boolean) {\n  const [now, setNow] = useState<Date | null>(null);\n\n  useEffect(() => subscribeClock(live, setNow), [live]);\n\n  return now;\n}\n\nfunction parseOffsetMinutes(date: Date, timeZone: string) {\n  try {\n    const part =\n      new Intl.DateTimeFormat(\n        \"en-US\",\n        createDateTimeFormatOptions({\n          timeZone,\n          timeZoneName: \"longOffset\",\n        })\n      )\n        .formatToParts(date)\n        .find((segment) => segment.type === \"timeZoneName\")?.value ?? \"\";\n\n    const match = part.match(GMT_OFFSET_PATTERN);\n    if (!match) {\n      return 0;\n    }\n\n    const sign = match[1] === \"-\" ? -1 : 1;\n    return sign * (Number(match[2]) * 60 + Number(match[3] ?? 0));\n  } catch {\n    return 0;\n  }\n}\n\nfunction isDaylightSaving(date: Date, timeZone: string) {\n  const year = date.getUTCFullYear();\n  const winter = new Date(Date.UTC(year, 0, 15, 12));\n  const summer = new Date(Date.UTC(year, 6, 15, 12));\n  const winterOffset = parseOffsetMinutes(winter, timeZone);\n  const summerOffset = parseOffsetMinutes(summer, timeZone);\n\n  if (winterOffset === summerOffset) {\n    return false;\n  }\n\n  return (\n    parseOffsetMinutes(date, timeZone) === Math.max(winterOffset, summerOffset)\n  );\n}\n\nfunction readTimeZoneNamePart(\n  date: Date,\n  timeZone: string,\n  locale: string,\n  timeZoneName: Intl.DateTimeFormatOptions[\"timeZoneName\"]\n) {\n  try {\n    return new Intl.DateTimeFormat(\n      locale,\n      createDateTimeFormatOptions({\n        timeZone,\n        timeZoneName,\n        hour: \"numeric\",\n      })\n    )\n      .formatToParts(date)\n      .find((part) => part.type === \"timeZoneName\")?.value;\n  } catch {\n    return new Intl.DateTimeFormat(\n      DEFAULT_LOCALE,\n      createDateTimeFormatOptions({\n        timeZone,\n        timeZoneName,\n        hour: \"numeric\",\n      })\n    )\n      .formatToParts(date)\n      .find((part) => part.type === \"timeZoneName\")?.value;\n  }\n}\n\nexport function getTimezoneAbbreviation(\n  date: Date,\n  timeZone: string,\n  locale: string\n) {\n  const intlShort = readTimeZoneNamePart(date, timeZone, locale, \"short\");\n  if (intlShort && LETTER_ABBREV.test(intlShort)) {\n    return intlShort;\n  }\n\n  const entry = ABBREVIATION_FALLBACK[timeZone];\n  if (entry) {\n    const [standard, daylight] = entry;\n    if (daylight && isDaylightSaving(date, timeZone)) {\n      return daylight;\n    }\n    return standard;\n  }\n\n  if (intlShort) {\n    return intlShort;\n  }\n\n  return readTimeZoneNamePart(date, timeZone, locale, \"shortOffset\") ?? \"\";\n}\n\nfunction getTimezoneOffsetLabel(date: Date, timeZone: string, locale: string) {\n  return readTimeZoneNamePart(date, timeZone, locale, \"shortOffset\") ?? \"\";\n}\n\nexport function formatTimezoneTime({\n  date,\n  locale,\n  timeZone,\n  format,\n  showZoneLabel,\n  zoneName,\n  live,\n}: {\n  date: Date;\n  locale: string;\n  timeZone: string;\n  format: \"12h\" | \"24h\";\n  showZoneLabel: boolean;\n  zoneName: \"abbreviation\" | \"offset\";\n  live: boolean;\n}) {\n  const options = createDateTimeFormatOptions({\n    timeZone,\n    hour: \"numeric\",\n    minute: \"2-digit\",\n    second: live ? \"2-digit\" : undefined,\n    hour12: format === \"12h\",\n  });\n\n  let label: string;\n  try {\n    label = new Intl.DateTimeFormat(locale, options).format(date);\n  } catch {\n    label = new Intl.DateTimeFormat(DEFAULT_LOCALE, options).format(date);\n  }\n\n  if (!showZoneLabel) {\n    return label;\n  }\n\n  const zoneLabel =\n    zoneName === \"abbreviation\"\n      ? getTimezoneAbbreviation(date, timeZone, locale)\n      : getTimezoneOffsetLabel(date, timeZone, locale);\n\n  return zoneLabel ? `${label} ${zoneLabel}` : label;\n}\n\nfunction toTimezoneDateTime(date: Date, timeZone: string) {\n  try {\n    const parts = new Intl.DateTimeFormat(\n      \"en-CA\",\n      createDateTimeFormatOptions({\n        timeZone,\n        year: \"numeric\",\n        month: \"2-digit\",\n        day: \"2-digit\",\n        hour: \"2-digit\",\n        minute: \"2-digit\",\n        second: \"2-digit\",\n        hour12: false,\n      })\n    ).formatToParts(date);\n\n    const get = (type: Intl.DateTimeFormatPartTypes) =>\n      parts.find((part) => part.type === type)?.value ?? \"00\";\n\n    return `${get(\"year\")}-${get(\"month\")}-${get(\"day\")}T${get(\"hour\")}:${get(\"minute\")}:${get(\"second\")}`;\n  } catch {\n    return date.toISOString();\n  }\n}\n\nfunction resolveShowZoneLabel({\n  showZoneLabel,\n  showAbbreviation,\n}: Pick<TimezoneProps, \"showZoneLabel\" | \"showAbbreviation\">) {\n  return showZoneLabel ?? showAbbreviation ?? true;\n}\n\nfunction getPlaceholderWidth({\n  format,\n  live,\n  showZoneLabel,\n}: {\n  format: \"12h\" | \"24h\";\n  live: boolean;\n  showZoneLabel: boolean;\n}) {\n  if (format === \"24h\") {\n    if (live) {\n      return showZoneLabel ? \"14ch\" : \"8ch\";\n    }\n    return showZoneLabel ? \"12ch\" : \"5ch\";\n  }\n\n  if (live) {\n    return showZoneLabel ? \"16ch\" : \"11ch\";\n  }\n\n  return showZoneLabel ? \"14ch\" : \"8ch\";\n}\n\nfunction RollingDigit({\n  char,\n  animate,\n  className,\n}: {\n  char: string;\n  animate: boolean;\n  className?: string;\n}) {\n  const [exitQueue, setExitQueue] = useState<ExitItem[]>([]);\n  const prevCharRef = useRef(char);\n  const isFirstRender = useRef(true);\n\n  const springConfig = {\n    stiffness: TIME_MOTION.enterStiffness,\n    damping: TIME_MOTION.enterDamping,\n  };\n  const y = useSpring(0, springConfig);\n  const opacity = useSpring(1, springConfig);\n  const scale = useSpring(1, springConfig);\n\n  useEffect(() => {\n    if (!animate) {\n      return;\n    }\n\n    const prev = prevCharRef.current;\n    prevCharRef.current = char;\n\n    if (isFirstRender.current) {\n      isFirstRender.current = false;\n      return;\n    }\n\n    if (char === prev || !isDigitChar(prev)) {\n      return;\n    }\n\n    const up = shouldDigitRollUp(prev, char);\n    const id = exitId++;\n    setExitQueue((queue) => {\n      const next = [\n        ...queue,\n        {\n          id,\n          char: prev,\n          exitY: up ? -TIME_MOTION.enterY : TIME_MOTION.enterY,\n        },\n      ];\n      return next.length > 1 ? next.slice(-1) : next;\n    });\n\n    y.jump(up ? TIME_MOTION.enterY : -TIME_MOTION.enterY);\n    opacity.jump(0);\n    scale.jump(TIME_MOTION.enterScale);\n\n    y.set(0);\n    opacity.set(1);\n    scale.set(1);\n  }, [animate, char, opacity, scale, y]);\n\n  if (!animate) {\n    return <span className={className}>{char}</span>;\n  }\n\n  return (\n    <span\n      className={cn(\n        \"relative isolate grid min-h-[1em] min-w-[1ch] place-items-center overflow-hidden [&>*]:col-start-1 [&>*]:row-start-1\",\n        className\n      )}\n    >\n      <AnimatePresence>\n        {exitQueue.map(({ id, char: exitChar, exitY }) => (\n          <motion.span\n            animate={{\n              opacity: 0,\n              scale: TIME_MOTION.exitScale,\n              y: exitY,\n            }}\n            aria-hidden\n            className=\"[backface-visibility:hidden]\"\n            initial={{ opacity: 1, scale: 1, y: 0 }}\n            key={id}\n            onAnimationComplete={() =>\n              setExitQueue((queue) => queue.filter((item) => item.id !== id))\n            }\n            transition={{\n              type: \"spring\",\n              stiffness: TIME_MOTION.exitStiffness,\n              damping: TIME_MOTION.exitDamping,\n            }}\n          >\n            {exitChar}\n          </motion.span>\n        ))}\n      </AnimatePresence>\n      <motion.span\n        aria-hidden\n        className=\"[backface-visibility:hidden]\"\n        style={{ opacity, scale, y }}\n      >\n        {char}\n      </motion.span>\n    </span>\n  );\n}\n\nfunction FluidColon({\n  char,\n  className,\n  live,\n  animate,\n}: {\n  char: string;\n  live: boolean;\n  animate: boolean;\n  className?: string;\n}) {\n  if (!animate) {\n    return (\n      <span aria-hidden className={cn(\"inline-block\", className)}>\n        {char}\n      </span>\n    );\n  }\n\n  return (\n    <motion.span\n      animate={live ? { opacity: [1, 0.35, 1] } : { opacity: 1 }}\n      aria-hidden\n      className={cn(\"inline-block\", className)}\n      transition={\n        live\n          ? {\n              duration: 1,\n              ease: \"easeInOut\",\n              repeat: Number.POSITIVE_INFINITY,\n            }\n          : undefined\n      }\n    >\n      {char}\n    </motion.span>\n  );\n}\n\nfunction FadingGlyph({\n  char,\n  animate,\n  className,\n}: {\n  char: string;\n  animate: boolean;\n  className?: string;\n}) {\n  if (!animate) {\n    return <span className={className}>{char}</span>;\n  }\n\n  return (\n    <AnimatePresence initial={false} mode=\"popLayout\">\n      <motion.span\n        animate={{ opacity: 1, y: 0 }}\n        aria-hidden\n        className={className}\n        exit={{ opacity: 0, y: -4 }}\n        initial={{ opacity: 0, y: 4 }}\n        key={char}\n        transition={{\n          type: \"spring\",\n          stiffness: 280,\n          damping: 24,\n        }}\n      >\n        {char}\n      </motion.span>\n    </AnimatePresence>\n  );\n}\n\nfunction TimeLabel({\n  label,\n  live,\n  animate,\n  className,\n  visuallyHidden,\n}: {\n  label: string;\n  live: boolean;\n  animate: boolean;\n  className?: string;\n  visuallyHidden?: boolean;\n}) {\n  const cells = useMemo(() => buildTimeLabelCells(label), [label]);\n\n  if (!animate) {\n    return (\n      <span\n        aria-hidden={visuallyHidden || undefined}\n        className={cn(\"inline-flex items-baseline text-inherit\", className)}\n      >\n        {label}\n      </span>\n    );\n  }\n\n  return (\n    <span\n      aria-hidden={visuallyHidden || undefined}\n      className={cn(\"inline-flex items-baseline text-inherit\", className)}\n    >\n      {cells.map(({ char, key, kind }) => {\n        if (kind === \"digit\") {\n          return <RollingDigit animate={animate} char={char} key={key} />;\n        }\n\n        if (kind === \"colon\") {\n          return (\n            <FluidColon animate={animate} char={char} key={key} live={live} />\n          );\n        }\n\n        if (kind === \"space\") {\n          return <span key={key}>&nbsp;</span>;\n        }\n\n        return <FadingGlyph animate={animate} char={char} key={key} />;\n      })}\n    </span>\n  );\n}\n\nconst TimezoneClock = forwardRef<HTMLElement, TimezoneClockProps>(\n  function TimezoneClock(\n    {\n      zone,\n      resolvedZone,\n      format = \"12h\",\n      showAbbreviation,\n      showZoneLabel,\n      zoneName = \"abbreviation\",\n      live = false,\n      className,\n      locale = DEFAULT_LOCALE,\n      animate: animateProp = true,\n      ariaLive,\n    },\n    ref\n  ) {\n    const reduceMotion = useReducedMotion();\n    const normalizedLocale = locale.trim() || DEFAULT_LOCALE;\n    const resolvedShowZoneLabel = resolveShowZoneLabel({\n      showZoneLabel,\n      showAbbreviation,\n    });\n    const now = useNow(live);\n    const animate = animateProp && !reduceMotion;\n    const resolvedAriaLive = resolveTimezoneAriaLive(ariaLive, live);\n\n    const timeClassName = cn(\n      \"inline-flex origin-center text-inherit tabular-nums tracking-tight\",\n      className\n    );\n    const title = `${zone} (${resolvedZone})`;\n    const placeholderWidth = getPlaceholderWidth({\n      format,\n      live,\n      showZoneLabel: resolvedShowZoneLabel,\n    });\n\n    if (!now) {\n      return (\n        <time\n          className={timeClassName}\n          dateTime=\"\"\n          ref={ref as Ref<HTMLTimeElement>}\n          suppressHydrationWarning\n          title={title}\n        >\n          <span\n            aria-hidden\n            className=\"inline-block opacity-0\"\n            style={{ minWidth: placeholderWidth }}\n          >\n            00:00:00\n          </span>\n        </time>\n      );\n    }\n\n    const label = formatTimezoneTime({\n      date: now,\n      locale: normalizedLocale,\n      timeZone: resolvedZone,\n      format,\n      showZoneLabel: resolvedShowZoneLabel,\n      zoneName,\n      live,\n    });\n    const dateTime = toTimezoneDateTime(now, resolvedZone);\n    const clockKey = `${resolvedZone}-${format}-${resolvedShowZoneLabel ? zoneName : \"plain\"}-${live ? \"live\" : \"minute\"}`;\n    const timeContent = (\n      <>\n        {resolvedAriaLive ? (\n          <span aria-live={resolvedAriaLive} className=\"sr-only\">\n            {label}\n          </span>\n        ) : null}\n        <TimeLabel\n          animate={animate}\n          label={label}\n          live={live}\n          visuallyHidden={Boolean(resolvedAriaLive)}\n        />\n      </>\n    );\n\n    if (!animate) {\n      return (\n        <time\n          className={timeClassName}\n          dateTime={dateTime}\n          ref={ref as Ref<HTMLTimeElement>}\n          suppressHydrationWarning\n          title={title}\n        >\n          {timeContent}\n        </time>\n      );\n    }\n\n    return (\n      <AnimatePresence initial={false} mode=\"wait\">\n        <MotionTime\n          animate={{ opacity: 1, y: 0, scale: 1 }}\n          className={timeClassName}\n          dateTime={dateTime}\n          exit={{ opacity: 0, y: -6, scale: 0.98 }}\n          initial={false}\n          key={clockKey}\n          ref={ref as Ref<HTMLTimeElement>}\n          suppressHydrationWarning\n          title={title}\n          transition={{\n            type: \"spring\",\n            stiffness: 260,\n            damping: 26,\n          }}\n        >\n          {timeContent}\n        </MotionTime>\n      </AnimatePresence>\n    );\n  }\n);\n\nTimezoneClock.displayName = \"TimezoneClock\";\n\nconst TimezoneError = forwardRef<HTMLElement, TimezoneErrorProps>(\n  function TimezoneError({ zone, fallback, className, onError }, ref) {\n    const onErrorRef = useRef(onError);\n    onErrorRef.current = onError;\n\n    useEffect(() => {\n      onErrorRef.current?.(zone);\n    }, [zone]);\n\n    return (\n      <output\n        className={cn(\n          \"inline-flex text-destructive text-inherit tabular-nums\",\n          className\n        )}\n        ref={ref as Ref<HTMLOutputElement>}\n        title={zone}\n      >\n        {fallback ?? `Unknown timezone: ${zone}`}\n      </output>\n    );\n  }\n);\n\nTimezoneError.displayName = \"TimezoneError\";\n\nconst Timezone = forwardRef<HTMLElement, TimezoneProps>(function Timezone(\n  { zone, fallback, onError, className, ...clockProps },\n  ref\n) {\n  const resolvedZone = useMemo(() => resolveTimezone(zone), [zone]);\n\n  if (!resolvedZone) {\n    return (\n      <TimezoneError\n        className={className}\n        fallback={fallback}\n        onError={onError}\n        ref={ref}\n        zone={zone}\n      />\n    );\n  }\n\n  return (\n    <TimezoneClock\n      {...clockProps}\n      className={className}\n      ref={ref}\n      resolvedZone={resolvedZone}\n      zone={zone}\n    />\n  );\n});\n\nTimezone.displayName = \"Timezone\";\n\nexport default Timezone;\nexport { Timezone };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Timezone",
  "description": "Inline live clock for city aliases or IANA timezones with fluid digit rolls, pulsing separators, and spring transitions."
}
