{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"motion-number-input","type":"registry:component","title":"Number Input (Motion)","description":"NumberInput with motion: odometer digits that spin as far as the number travelled, steppers that repeat when held, and a limit you can feel: the number strains against it, harder with each push.","dependencies":["lucide-react","motion"],"registryDependencies":["https://dashral.com/r/utils.json","https://dashral.com/r/lib-locale.json","https://dashral.com/r/lib-portal.json","https://dashral.com/r/motion-field.json","https://dashral.com/r/motion-odometer.json","https://dashral.com/r/motion-reduced.json","https://dashral.com/r/motion-tokens.json","https://dashral.com/r/field.json","https://dashral.com/r/number-input.json"],"files":[{"path":"registry/dashral/motion/number-input.tsx","type":"registry:file","target":"components/dashral/motion/number-input.tsx","content":"\"use client\"\n\nimport * as React from \"react\"\nimport { createPortal } from \"react-dom\"\nimport { MinusIcon, PlusIcon } from \"lucide-react\"\nimport { AnimatePresence, animate, motion, useMotionValue } from \"motion/react\"\nimport { FieldWrapper, splitFieldProps, useFieldControl, type FieldClassNames } from \"../field\"\nimport type { NumberInput as KitNumberInput } from \"../number-input\"\nimport { useLocale } from \"../lib/locale\"\nimport { usePortalContainer } from \"../lib/portal\"\nimport { cn } from \"../lib/utils\"\n\nimport { Odometer } from \"../lib/motion-odometer\"\nimport { motionMessages } from \"../lib/motion-field\"\nimport { useReducedMotion } from \"../lib/motion-reduced\"\nimport { duration, ease, spring } from \"../lib/motion-tokens\"\n\nexport type NumberInputProps = React.ComponentProps<typeof KitNumberInput> & {\n  /** Drag the number sideways to change it, a step every few pixels; past a limit it pulls back like a rubber band. */\n  scrub?: boolean\n  /** A thin bar along the bottom of the box that shows where the value sits between min and max. Needs both. */\n  meter?: boolean\n  /** \"Max 50\" above the box's end for a moment when a step meets a limit. On by default. */\n  limitHint?: boolean\n  /**\n   * \"pill\": a rounded, taller field with − and + as round buttons and no dividers, the suffix beside the number\n   * (\"10 seats\") instead of at the far end.\n   */\n  variant?: \"default\" | \"pill\"\n}\n\ntype FieldKeys = \"label\" | \"description\" | \"error\" | \"labelAction\" | \"withAsterisk\"\n\n/** The locale's decimal separator, \".\" or \",\". */\nconst decimalSeparator = (locale: string) => new Intl.NumberFormat(locale).formatToParts(1.1).find((p) => p.type === \"decimal\")?.value ?? \".\"\n\n/** Reads a number typed in the locale's format; null for nothing or something unreadable. */\nfunction parse(text: string, locale: string) {\n  const separator = decimalSeparator(locale)\n  const cleaned = text.replace(new RegExp(`[^0-9\\\\-${separator === \".\" ? \"\\\\.\" : \",\"}]`, \"g\"), \"\").replace(separator, \".\")\n  if (cleaned === \"\" || cleaned === \"-\") return null\n  const value = Number(cleaned)\n  return Number.isFinite(value) ? value : null\n}\n\nconst noSubscribe = () => () => {}\n\n/** Pressed and held, a stepper waits this long, then repeats faster and faster down to the quickest. */\nconst holdDelay = 400\nconst holdQuickest = 35\n/** Held against a limit, it keeps pushing at this pace; pushes this close together count as one effort, each harder. */\nconst pushPace = 260\nconst pushWindow = 700\n/** Pixels of drag per step when scrubbing. */\nconst scrubStep = 8\n\n/** Past a limit, a drag gives less and less, and never more than `most` pixels (a rubber band). */\nconst rubber = (distance: number, most = 12) => Math.sign(distance) * most * (1 - 1 / (Math.abs(distance) / most / 2 + 1))\n\n/** A light buzz on phones that have one, for a step or a limit felt under the thumb. */\nconst buzz = (pattern: number | number[]) => {\n  if (typeof navigator !== \"undefined\" && \"vibrate\" in navigator) navigator.vibrate(pattern)\n}\n\n/**\n * The kit's NumberInput with motion: the same props, keys and formatting, and more. Its digits are odometer wheels:\n * each column turns as far as the number really travelled (5 to 50 spins the ones round), blurring with its speed,\n * and a column that comes or goes opens or closes its width. Hold − or + and it repeats, faster and faster. At a\n * limit the number strains toward it and springs back, harder with each push in a row, the button's icon shakes, the\n * box flashes and \"Max 50\" rises above it; phones buzz. `scrub` lets you drag the number, `meter` shows where it sits\n * between min and max. While you type it's a plain field. With reduced motion the number changes at once.\n */\nexport function NumberInput(props: NumberInputProps) {\n  const [field, control] = splitFieldProps(props)\n  return (\n    <FieldWrapper {...field} renderMessages={motionMessages}>\n      <NumberInputControl {...control} />\n    </FieldWrapper>\n  )\n}\n\nfunction NumberInputControl({ classNames: _classNames, ...controlProps }: Omit<NumberInputProps, FieldKeys> & { classNames?: FieldClassNames }) {\n  const {\n    value,\n    defaultValue = null,\n    onValueChange,\n    min,\n    max,\n    step = 1,\n    locale: localeProp,\n    formatOptions,\n    stepper = true,\n    prefix,\n    suffix,\n    scrub = false,\n    meter = false,\n    limitHint = true,\n    variant = \"default\",\n    className,\n    disabled,\n    onBlur,\n    onKeyDown,\n    ref,\n    ...props\n  } = useFieldControl(controlProps)\n  const reduced = useReducedMotion()\n  const { t, locale: contextLocale } = useLocale()\n  const locale = localeProp ?? contextLocale\n  const [internal, setInternal] = React.useState<number | null>(defaultValue)\n  const current = value === undefined ? internal : value\n  const [editing, setEditing] = React.useState<string | null>(null)\n  const format = React.useMemo(() => new Intl.NumberFormat(locale, formatOptions), [locale, formatOptions])\n  const digits = format.resolvedOptions().maximumFractionDigits ?? 20\n  // A percent shows the value × 100 (0.45 as 45%), so it reads typed text ÷ 100 and keeps two more decimals than it\n  // shows; a finer step (0.005) keeps its own.\n  const percent = formatOptions?.style === \"percent\"\n  const precision = Math.min(20, Math.max(digits + (percent ? 2 : 0), (String(step).split(\".\")[1] ?? \"\").length))\n  const read = (text: string) => {\n    const typed = parse(text, locale)\n    return typed !== null && percent ? typed / 100 : typed\n  }\n\n  // The value as of the last change, for a held stepper's repeats, which outrun renders.\n  const latest = React.useRef(current)\n  React.useLayoutEffect(() => {\n    latest.current = current\n  }, [current])\n\n  const box = React.useRef<HTMLDivElement>(null)\n  const icons = { [-1]: React.useRef<HTMLSpanElement>(null), [1]: React.useRef<HTMLSpanElement>(null) } as Record<-1 | 1, React.RefObject<HTMLSpanElement | null>>\n  // How far the number is pushed or pulled sideways: a strain at a limit, a rubber band while scrubbing.\n  const pushX = useMotionValue(0)\n  const meterPush = useMotionValue(1)\n  const effort = React.useRef({ edge: 0, count: 0, at: 0 })\n  const touch = React.useRef(false)\n  // The note at a limit, and where the box's top end corner was when it showed (the note sits above it, outside any\n  // clipping parent: a list row that folds, a scrolling panel).\n  const [hint, setHint] = React.useState<{ edge: -1 | 1; top: number; side: number; rtl: boolean } | null>(null)\n  // The note goes to document.body, which only exists once the page has hydrated.\n  const hydrated = React.useSyncExternalStore(noSubscribe, () => true, () => false)\n  // In a themed part of the page, the note opens in its container, wearing its look.\n  const portalContainer = usePortalContainer()\n  const hintTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n  const holdTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n  React.useEffect(\n    () => () => {\n      clearTimeout(hintTimer.current)\n      clearTimeout(holdTimer.current)\n    },\n    []\n  )\n\n  function clamp(next: number) {\n    const rounded = Number(next.toFixed(precision))\n    return Math.min(max ?? Infinity, Math.max(min ?? -Infinity, rounded))\n  }\n\n  function commit(next: number | null) {\n    const clamped = next === null ? null : clamp(next)\n    if (value === undefined) setInternal(clamped)\n    if (clamped !== latest.current) onValueChange?.(clamped)\n    latest.current = clamped\n    return clamped\n  }\n\n  /** A step that met a limit: the number strains that way and springs home, harder with each push in a row. */\n  function strain(edge: -1 | 1) {\n    const now = performance.now()\n    const push = effort.current\n    push.count = push.edge === edge && now - push.at < pushWindow ? push.count + 1 : 1\n    push.edge = edge\n    push.at = now\n    if (touch.current) buzz([10, 30, 10])\n    const node = box.current\n    if (limitHint && node) {\n      const rect = node.getBoundingClientRect()\n      const rtl = getComputedStyle(node).direction === \"rtl\"\n      setHint({ edge, top: rect.top, side: rtl ? rect.left : window.innerWidth - rect.right, rtl })\n      clearTimeout(hintTimer.current)\n      hintTimer.current = setTimeout(() => setHint(null), 1400)\n    }\n    if (node) {\n      node.dataset.limit = \"\"\n      setTimeout(() => delete node.dataset.limit, 360)\n    }\n    if (reduced || !node) return\n    // + sits at the end, so in a right-to-left box \"toward max\" is to the left.\n    const toward = edge * (getComputedStyle(node).direction === \"rtl\" ? -1 : 1)\n    animate(pushX, 0, { type: \"spring\", stiffness: 700, damping: 16, velocity: toward * (150 + 45 * Math.min(push.count - 1, 4)) })\n    animate(meterPush, [1, 2.5, 1], { duration: duration.standard, ease: ease.standard })\n    const icon = icons[edge].current\n    if (icon) animate(icon, { rotate: [0, -14, 11, -6, 3, 0] }, { duration: 0.36, ease: \"easeOut\" })\n  }\n\n  /** Goes to a value: clamped, and a value past a limit strains toward it. Returns whether the number moved. */\n  function stepTo(next: number, strains = true) {\n    const before = latest.current\n    const after = commit(next)\n    setEditing(null)\n    // Past a limit, by the value as kept (0.99 + 0.01 lands on 1, not a hair past it).\n    const kept = Number(next.toFixed(precision))\n    const edge = max !== undefined && kept > max ? 1 : min !== undefined && kept < min ? -1 : 0\n    if (edge && strains) strain(edge)\n    return after !== before\n  }\n\n  function stepBy(amount: number) {\n    const base = editing !== null ? read(editing) : latest.current\n    return stepTo((base ?? min ?? 0) + amount)\n  }\n\n  /** What was typed, read and committed; a number past a limit is clamped to it, straining that way. */\n  function commitTyped(typed: string) {\n    const next = read(typed)\n    if (next === null) {\n      commit(null)\n      setEditing(null)\n    } else stepTo(next)\n  }\n\n  function stopHold() {\n    clearTimeout(holdTimer.current)\n    holdTimer.current = undefined\n  }\n  /** One step now; held, it repeats, faster each time; held against a limit, it keeps pushing on it. */\n  function startHold(direction: -1 | 1) {\n    stopHold()\n    let count = 0\n    const tick = (first: boolean) => {\n      const moved = stepBy(direction * step)\n      if (moved && touch.current) buzz(4)\n      holdTimer.current = setTimeout(() => tick(false), first ? (moved ? holdDelay : pushPace + 120) : moved ? Math.max(holdQuickest, 150 * 0.86 ** ++count) : pushPace)\n    }\n    tick(true)\n  }\n\n  // Scrubbing: a drag across the number, a step every few pixels; past a limit, a rubber band.\n  const drag = React.useRef<{ pointer: number; x: number; from: number; active: boolean } | null>(null)\n  const [scrubbing, setScrubbing] = React.useState(false)\n  function scrubMove(event: React.PointerEvent<HTMLSpanElement>) {\n    const state = drag.current\n    if (!state || state.pointer !== event.pointerId) return\n    const dx = (event.clientX - state.x) * (getComputedStyle(event.currentTarget).direction === \"rtl\" ? -1 : 1)\n    if (!state.active) {\n      if (Math.abs(dx) < 4) return\n      state.active = true\n      event.currentTarget.setPointerCapture(event.pointerId)\n      ;(document.activeElement as HTMLElement | null)?.blur?.()\n      setScrubbing(true)\n    }\n    const travel = dx / scrubStep\n    stepTo(state.from + Math.trunc(travel) * step, false)\n    const raw = state.from + travel * step\n    const over = max !== undefined && raw > max ? ((raw - max) / step) * scrubStep : min !== undefined && raw < min ? ((raw - min) / step) * scrubStep : 0\n    if (!reduced) pushX.set(rubber(over))\n  }\n  function scrubEnd(event: React.PointerEvent<HTMLSpanElement>) {\n    const state = drag.current\n    if (!state || state.pointer !== event.pointerId) return\n    drag.current = null\n    if (!state.active) return\n    setScrubbing(false)\n    animate(pushX, 0, reduced ? { duration: 0 } : spring.move)\n  }\n\n  const atMin = current !== null && min !== undefined && current <= min\n  const atMax = current !== null && max !== undefined && current >= max\n  const text = current === null ? \"\" : format.format(current)\n  // The wheels stand in for the input's own text until you type; they stay mounted, so they turn on commit.\n  const rolling = editing === null && text !== \"\"\n  const pill = variant === \"pill\"\n  const stepButton = cn(\n    \"flex shrink-0 items-center justify-center text-muted-foreground transition-[color,background-color,opacity] outline-none hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40 aria-disabled:opacity-40\",\n    pill ? \"size-9 rounded-full text-foreground active:bg-accent [&_svg]:size-4\" : \"h-full w-8 [&_svg]:size-3.5\"\n  )\n  // In a pill the suffix sits beside the number (\"10 seats\"), so it rides with the wheels.\n  const besideNumber = pill && suffix\n  const press = reduced ? undefined : { scale: 0.85 }\n  const range = meter && min !== undefined && max !== undefined && max > min ? { min, max } : null\n\n  const stepperButton = (direction: -1 | 1) => {\n    const limit = direction > 0 ? atMax : atMin\n    const Icon = direction > 0 ? PlusIcon : MinusIcon\n    return (\n      <button\n        type=\"button\"\n        tabIndex={-1}\n        aria-label={t(direction > 0 ? \"increase\" : \"decrease\")}\n        // At a limit it only looks off: pressed, it pushes on the limit instead of doing nothing.\n        aria-disabled={limit || undefined}\n        className={cn(stepButton, !pill && (direction > 0 ? \"border-s\" : \"border-e\"))}\n        disabled={disabled}\n        onPointerDown={(event) => {\n          if (event.button !== 0) return\n          event.preventDefault()\n          touch.current = event.pointerType === \"touch\"\n          startHold(direction)\n        }}\n        onPointerUp={stopHold}\n        onPointerLeave={stopHold}\n        onPointerCancel={stopHold}\n        onContextMenu={(event) => event.preventDefault()}\n        // A click without a pointer (a screen reader's): one step.\n        onClick={(event) => event.detail === 0 && stepBy(direction * step)}\n      >\n        <motion.span ref={icons[direction]} className=\"flex\" whileTap={press} transition={spring.press}>\n          <Icon aria-hidden />\n        </motion.span>\n      </button>\n    )\n  }\n\n  return (\n    <div className=\"relative min-w-0\">\n      <div\n        ref={box}\n        data-slot=\"number-input\"\n        data-disabled={disabled || undefined}\n        className={cn(\n          \"relative flex w-full min-w-0 items-center overflow-hidden border border-input bg-transparent shadow-xs transition-[color,box-shadow,border-color]\",\n          pill ? \"h-11 gap-1 rounded-full px-1 text-base\" : \"h-9 rounded-md text-base md:text-sm\",\n          \"focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/15 has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:ring-destructive/15\",\n          \"data-limit:border-warning data-limit:ring-2 data-limit:ring-warning/20\",\n          \"data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n          className\n        )}\n      >\n        {stepper && stepperButton(-1)}\n        {prefix && <span className=\"ps-3 text-muted-foreground select-none\">{prefix}</span>}\n        <span\n          className={cn(\"relative flex h-full min-w-0 flex-1\", scrub && !disabled && \"cursor-ew-resize touch-pan-y\", scrubbing && \"select-none\")}\n          onPointerDown={(event) => {\n            if (!scrub || disabled || event.button !== 0) return\n            drag.current = { pointer: event.pointerId, x: event.clientX, from: latest.current ?? min ?? 0, active: false }\n          }}\n          onPointerMove={scrubMove}\n          onPointerUp={scrubEnd}\n          onPointerCancel={scrubEnd}\n        >\n          <input\n            ref={ref}\n            type=\"text\"\n            inputMode={digits === 0 ? \"numeric\" : \"decimal\"}\n            role=\"spinbutton\"\n            autoComplete=\"off\"\n            aria-valuenow={current ?? undefined}\n            aria-valuemin={min}\n            aria-valuemax={max}\n            aria-valuetext={current === null ? undefined : format.format(current)}\n            disabled={disabled}\n            className={cn(\n              \"h-full w-full min-w-0 bg-transparent px-3 tabular-nums caret-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed\",\n              prefix ? \"ps-1.5\" : \"\",\n              suffix && !besideNumber ? \"pe-1.5\" : \"\",\n              stepper && \"text-center\",\n              scrub && !disabled && \"cursor-[inherit] focus:cursor-text\",\n              rolling && \"text-transparent\"\n            )}\n            value={editing ?? text}\n            onChange={(event) => setEditing(event.target.value)}\n            onBlur={(event) => {\n              stopHold()\n              if (editing !== null) commitTyped(editing)\n              setEditing(null)\n              onBlur?.(event)\n            }}\n            onKeyDown={(event) => {\n              onKeyDown?.(event)\n              if (event.defaultPrevented) return\n              touch.current = false\n              const big = step * 10\n              const moves: Record<string, number | undefined> = {\n                ArrowUp: event.shiftKey ? big : step,\n                ArrowDown: event.shiftKey ? -big : -step,\n                PageUp: big,\n                PageDown: -big,\n              }\n              if (event.key in moves) {\n                event.preventDefault()\n                stepBy(moves[event.key]!)\n              } else if ((event.key === \"Home\" && min !== undefined) || (event.key === \"End\" && max !== undefined)) {\n                event.preventDefault()\n                // Already there, Home or End pushes on the limit like a step past it.\n                stepTo(event.key === \"Home\" ? (atMin ? min! - step : min!) : atMax ? max! + step : max!)\n              } else if (event.key === \"Enter\" && editing !== null) {\n                commitTyped(editing)\n              }\n            }}\n            {...props}\n          />\n          {text !== \"\" && (\n            <span\n              aria-hidden\n              className={cn(\n                \"pointer-events-none absolute inset-0 flex items-center overflow-hidden px-3 tabular-nums\",\n                prefix ? \"ps-1.5\" : \"\",\n                suffix && !besideNumber ? \"pe-1.5\" : \"\",\n                stepper && \"justify-center\",\n                !rolling && \"invisible\"\n              )}\n            >\n              <motion.span className=\"flex items-baseline\" style={{ x: pushX }}>\n                <Odometer value={current ?? 0} format={format} />\n                {besideNumber && <Unit text={suffix} reduced={reduced} />}\n              </motion.span>\n            </span>\n          )}\n        </span>\n        {suffix && !besideNumber && <span className=\"pe-3 text-muted-foreground select-none\">{suffix}</span>}\n        {stepper && stepperButton(1)}\n        {range && (\n          <span aria-hidden className={cn(\"pointer-events-none absolute bottom-0 h-px origin-bottom bg-foreground/5\", pill ? \"inset-x-5\" : \"inset-x-0\")}>\n            <motion.span\n              className=\"block h-full origin-bottom rounded-full bg-primary/60\"\n              initial={false}\n              animate={{ width: `${((Math.min(range.max, Math.max(range.min, current ?? range.min)) - range.min) / (range.max - range.min)) * 100}%` }}\n              transition={reduced ? { duration: 0 } : spring.move}\n              style={{ scaleY: meterPush }}\n            />\n          </span>\n        )}\n      </div>\n      {hydrated &&\n        createPortal(\n          <AnimatePresence>\n            {hint && (\n              <motion.span\n                key={hint.edge}\n                role=\"status\"\n                className=\"pointer-events-none fixed z-[60] -translate-y-full pb-1\"\n                style={{ top: hint.top, ...(hint.rtl ? { left: hint.side } : { right: hint.side }) }}\n                initial={reduced ? { opacity: 0 } : { opacity: 0, y: 4, scale: 0.9 }}\n                animate={{ opacity: 1, y: 0, scale: 1 }}\n                exit={{ opacity: 0, transition: { duration: duration.fast, ease: ease.exit } }}\n                transition={reduced ? { duration: duration.fast } : spring.appear}\n              >\n                <span className=\"block rounded-md bg-foreground px-1.5 py-0.5 text-xs font-medium whitespace-nowrap text-background tabular-nums shadow-sm\">\n                  {t(hint.edge > 0 ? \"atMax\" : \"atMin\", { value: format.format(hint.edge > 0 ? max! : min!) })}\n                </span>\n              </motion.span>\n            )}\n          </AnimatePresence>,\n          portalContainer ?? document.body\n        )}\n    </div>\n  )\n}\n\n/** The unit beside the number in a pill; a new word (\"seat\" to \"seats\") crosses over, softly blurred. */\nfunction Unit({ text, reduced }: { text: React.ReactNode; reduced: boolean }) {\n  const key = typeof text === \"string\" || typeof text === \"number\" ? String(text) : \"node\"\n  return (\n    <span className=\"relative ms-1.5 inline-grid text-muted-foreground\">\n      <AnimatePresence mode=\"popLayout\" initial={false}>\n        <motion.span\n          key={key}\n          className=\"col-start-1 row-start-1 whitespace-nowrap\"\n          initial={reduced ? { opacity: 0 } : { opacity: 0, y: \"40%\", filter: \"blur(2px)\" }}\n          animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n          exit={{ opacity: 0, transition: { duration: 0.1, ease: ease.exit } }}\n          transition={{ duration: duration.standard, ease: ease.enter }}\n        >\n          {text}\n        </motion.span>\n      </AnimatePresence>\n    </span>\n  )\n}\n\n/**\n * An amount of money with motion: the currency's symbol in front and its usual decimals, its digits on wheels as\n * NumberInput's are. Takes the same props; never goes below 0 unless you set `min`.\n */\nexport function MoneyInput({\n  currency,\n  locale: localeProp,\n  min = 0,\n  step,\n  ...props\n}: Omit<NumberInputProps, \"prefix\" | \"formatOptions\"> & {\n  /** ISO 4217 code: \"USD\", \"IDR\", \"EUR\", … */\n  currency: string\n}) {\n  const { locale: contextLocale } = useLocale()\n  const locale = localeProp ?? contextLocale\n  const { symbol, digits } = React.useMemo(() => {\n    const format = new Intl.NumberFormat(locale, { style: \"currency\", currency })\n    return {\n      symbol: format.formatToParts(0).find((p) => p.type === \"currency\")?.value ?? currency,\n      digits: format.resolvedOptions().maximumFractionDigits ?? 2,\n    }\n  }, [locale, currency])\n  return (\n    <NumberInput\n      locale={locale}\n      min={min}\n      step={step ?? (digits === 0 ? 1000 : 1)}\n      stepper={false}\n      prefix={symbol}\n      formatOptions={{ minimumFractionDigits: digits, maximumFractionDigits: digits }}\n      {...props}\n    />\n  )\n}\n"}],"cssVars":{"theme":{"color-warning":"var(--warning)","color-warning-foreground":"var(--warning-foreground)","color-warning-text":"var(--warning-text)"},"light":{"warning":"#f59200","warning-foreground":"oklch(0.3 0.07 60)","warning-text":"#964e00"},"dark":{"warning":"#f59200","warning-foreground":"oklch(0.25 0.06 60)","warning-text":"#ffba42"}}}