{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"dialog","type":"registry:ui","title":"Dialog","description":"A modal window on a dimmed backdrop – title and quiet line, content that scrolls under a fixed header and footer, Escape and backdrop close, the focus kept inside and handed back, a sheet on phones, and an AlertDialog to confirm with a red button","dependencies":[],"files":[{"path":"registry/dialog/dialog.tsx","type":"registry:ui","content":"\"use client\";\n\nimport {\n  cloneElement,\n  isValidElement,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n  type ComponentPropsWithoutRef,\n  type CSSProperties,\n  type MouseEvent as ReactMouseEvent,\n  type ReactElement,\n  type ReactNode,\n  type RefObject,\n} from \"react\";\n\n/*\n * Dialog – a single self-contained file.\n * A modal window on a dimmed backdrop: a white card with a title, a quiet line, any content and a close ×, its actions\n * on the gray tray below.\n * It is a native <dialog> opened with showModal(), so it sits in the top layer, the page behind it is inert and the\n * browser keeps the focus inside. Escape and a press on the backdrop close it, the focus goes to the first control\n * (or one you pick) and back to what opened it, and the page under it stops scrolling. Long content scrolls inside\n * while the title and the actions stay put. On phones it becomes a sheet from the bottom.\n * AlertDialog, exported from the same file, asks to confirm one thing: no backdrop close, the focus on the safe\n * action, a red confirm button for things that can't be undone.\n * The styles are injected below, so no extra files are needed.\n */\n\ntype TriggerProps = {\n  onClick?: (e: ReactMouseEvent<HTMLElement>) => void;\n  \"aria-haspopup\"?: \"dialog\";\n  \"aria-expanded\"?: boolean;\n};\n\ntype Shared = Omit<ComponentPropsWithoutRef<\"dialog\">, \"title\" | \"open\" | \"children\" | \"onCancel\" | \"onClose\"> & {\n  /** Shows it – pass it with onOpenChange to control it, or leave it out and use trigger */\n  open?: boolean;\n  /** Open from the start when it isn't controlled */\n  defaultOpen?: boolean;\n  /** Called with false on Escape, a backdrop press, the × or an action – and with true from the trigger */\n  onOpenChange?: (open: boolean) => void;\n  /** A button that opens it – it gets the click, aria-haspopup and aria-expanded, and the focus back on close */\n  trigger?: ReactElement<TriggerProps>;\n  /** The heading – it also names the dialog */\n  title: ReactNode;\n  /** A quiet line under the title – read out with it */\n  description?: ReactNode;\n  /** sm: 400px · md: 480px · lg: 720px – never wider than the window */\n  size?: \"sm\" | \"md\" | \"lg\";\n  /** Any width instead of a size, e.g. 560 or \"40rem\" */\n  width?: number | string;\n  /** The control that gets the focus on open – by default the first one in the content, then in the footer */\n  initialFocus?: RefObject<HTMLElement | null>;\n  /** Stays inside its nearest positioned parent (an app frame, a preview) instead of covering the window */\n  contained?: boolean;\n};\n\nexport type DialogProps = Shared & {\n  /** The content – it scrolls when it is taller than the window */\n  children?: ReactNode;\n  /** The actions, aligned to the end – they stay in view while the content scrolls */\n  footer?: ReactNode;\n  /** A press on the backdrop closes it */\n  closeOnBackdrop?: boolean;\n  /** Shows the close × in the corner */\n  closeButton?: boolean;\n  /** The ×'s accessible name and tooltip */\n  closeLabel?: string;\n};\n\nexport function Dialog({\n  open,\n  defaultOpen,\n  onOpenChange,\n  trigger,\n  title,\n  description,\n  size = \"md\",\n  width,\n  initialFocus,\n  contained,\n  children,\n  footer,\n  closeOnBackdrop = true,\n  closeButton = true,\n  closeLabel = \"Close\",\n  ...rest\n}: DialogProps) {\n  const [isOpen, setOpen, opener] = useOpenState(open, defaultOpen, onOpenChange);\n  return (\n    <>\n      {withTrigger(trigger, isOpen, setOpen, opener)}\n      <Layer\n        {...rest}\n        open={isOpen}\n        onDismiss={() => setOpen(false)}\n        title={title}\n        description={description}\n        size={size}\n        width={width}\n        initialFocus={initialFocus}\n        contained={contained}\n        closeOnBackdrop={closeOnBackdrop}\n        opener={opener}\n        close={closeButton ? { label: closeLabel, onClick: () => setOpen(false) } : undefined}\n        footer={footer}\n      >\n        {children}\n      </Layer>\n    </>\n  );\n}\n\nexport type AlertDialogProps = Shared & {\n  /** More than the description – a list of what goes, a checkbox */\n  children?: ReactNode;\n  /** The confirm button's label – say what happens, e.g. \"Delete agent\" */\n  confirmLabel?: ReactNode;\n  /** The safe button's label – it gets the focus */\n  cancelLabel?: ReactNode;\n  /** Confirmed – return a promise to keep it open with a busy button until it settles (it stays open if it fails) */\n  onConfirm?: () => void | Promise<unknown>;\n  /** Cancelled – by the button, Escape or the trigger again */\n  onCancel?: () => void;\n  /** A red confirm button – for things that can't be undone */\n  danger?: boolean;\n};\n\nexport function AlertDialog({\n  open,\n  defaultOpen,\n  onOpenChange,\n  trigger,\n  title,\n  description,\n  size = \"sm\",\n  width,\n  initialFocus,\n  contained,\n  children,\n  confirmLabel = \"Confirm\",\n  cancelLabel = \"Cancel\",\n  onConfirm,\n  onCancel,\n  danger,\n  ...rest\n}: AlertDialogProps) {\n  const [isOpen, setOpen, opener] = useOpenState(open, defaultOpen, onOpenChange);\n  const [busy, setBusy] = useState(false);\n  const safe = useRef<HTMLButtonElement>(null);\n  const cancel = () => {\n    if (busy) return;\n    onCancel?.();\n    setOpen(false);\n  };\n  const confirm = async () => {\n    if (busy) return;\n    const result = onConfirm?.();\n    if (!result || typeof (result as Promise<unknown>).then !== \"function\") return setOpen(false);\n    setBusy(true);\n    try {\n      await result;\n      setOpen(false);\n    } catch {\n      // Stays open, so it can be tried again\n    } finally {\n      setBusy(false);\n    }\n  };\n\n  return (\n    <>\n      {withTrigger(trigger, isOpen, setOpen, opener)}\n      <Layer\n        {...rest}\n        alert\n        open={isOpen}\n        onDismiss={cancel}\n        title={title}\n        description={description}\n        size={size}\n        width={width}\n        initialFocus={initialFocus ?? safe}\n        contained={contained}\n        closeOnBackdrop={false}\n        busy={busy}\n        opener={opener}\n        footer={\n          <>\n            <button ref={safe} type=\"button\" className=\"ui-dlg__btn\" disabled={busy} onClick={cancel}>\n              {cancelLabel}\n            </button>\n            <button\n              type=\"button\"\n              className=\"ui-dlg__btn\"\n              data-tone={danger ? \"danger\" : \"primary\"}\n              data-busy={busy || undefined}\n              aria-disabled={busy || undefined}\n              onClick={confirm}\n            >\n              <span className=\"ui-dlg__btn-label\">{confirmLabel}</span>\n              {busy && <span className=\"ui-dlg__spin\" aria-hidden=\"true\" />}\n            </button>\n          </>\n        }\n      >\n        {children}\n      </Layer>\n    </>\n  );\n}\n\n/* ---------- The layer both build on ---------- */\n\ntype LayerProps = Omit<Shared, \"defaultOpen\" | \"onOpenChange\" | \"trigger\"> & {\n  open: boolean;\n  onDismiss: () => void;\n  closeOnBackdrop: boolean;\n  opener: RefObject<HTMLElement | null>;\n  alert?: boolean;\n  busy?: boolean;\n  close?: { label: string; onClick: () => void };\n  footer?: ReactNode;\n  children?: ReactNode;\n};\n\n/** How long the way out takes – the backdrop and the card fade, a sheet slides down */\nconst EXIT_MS = 200;\n\nfunction Layer({\n  open,\n  onDismiss,\n  title,\n  description,\n  size = \"md\",\n  width,\n  initialFocus,\n  contained,\n  closeOnBackdrop,\n  opener,\n  alert,\n  busy,\n  close,\n  footer,\n  children,\n  className,\n  style,\n  ...rest\n}: LayerProps) {\n  const uid = useId();\n  const titleId = `${uid}-title`;\n  const descId = `${uid}-desc`;\n  const layer = useRef<HTMLDialogElement>(null);\n  const panel = useRef<HTMLDivElement>(null);\n  const dismiss = useRef(onDismiss);\n  dismiss.current = onDismiss;\n  const blocked = useRef(busy);\n  blocked.current = busy;\n  // Contained and open from the first render (a demo, a restored view) leaves the focus where it is\n  const firstOpen = useRef(open);\n\n  // Shown while open and through the way out; a new open during the way out picks it up again\n  const [shown, setShown] = useState(open);\n  const [leaving, setLeaving] = useState(false);\n  const [was, setWas] = useState(open);\n  if (open !== was) {\n    setWas(open);\n    if (open) {\n      setShown(true);\n      setLeaving(false);\n    } else if (shown) setLeaving(true);\n  }\n  useEffect(() => {\n    if (!leaving) return;\n    const t = window.setTimeout(() => {\n      setShown(false);\n      setLeaving(false);\n    }, reduced() ? 0 : EXIT_MS);\n    return () => window.clearTimeout(t);\n  }, [leaving]);\n\n  // Open: into the top layer (or in place when contained), the page locked, the focus inside; closed: all undone\n  useLayoutEffect(() => {\n    const el = layer.current;\n    if (!shown || !el) return;\n    const back = opener.current ?? (document.activeElement as HTMLElement | null);\n    opener.current = null;\n    const modal = !contained;\n    if (!el.open) {\n      if (modal) el.showModal();\n      else el.show();\n    }\n    if (modal) lockScroll();\n    if (modal || !firstOpen.current) {\n      const target =\n        initialFocus?.current ??\n        el.querySelector<HTMLElement>(\"[data-autofocus]\") ??\n        firstControl(el.querySelector(\".ui-dlg__body\")) ??\n        firstControl(el.querySelector(\".ui-dlg__foot\")) ??\n        firstControl(el) ??\n        panel.current;\n      target?.focus({ preventScroll: true });\n    }\n    firstOpen.current = false;\n\n    // Escape: the native cancel when modal, a key listener when contained\n    const cancel = (e: Event) => {\n      e.preventDefault();\n      if (!blocked.current) dismiss.current();\n    };\n    // Closed by the browser itself (a second Escape) – tell the owner; while busy it opens again\n    const closed = () => {\n      // A close from an earlier run of this effect (React re-runs it, e.g. in Strict Mode) lands while it's open again\n      if (el.open) return;\n      if (!blocked.current) return dismiss.current();\n      if (modal) el.showModal();\n      else el.show();\n    };\n    const key = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") {\n        // Keeps a page's own Escape (closing a detail view) from firing as well\n        e.stopPropagation();\n        if (!modal && !e.defaultPrevented) cancel(e);\n      }\n      if (e.key === \"Tab\" && !modal) trapTab(e, el);\n    };\n    el.addEventListener(\"cancel\", cancel);\n    el.addEventListener(\"close\", closed);\n    el.addEventListener(\"keydown\", key);\n    return () => {\n      el.removeEventListener(\"cancel\", cancel);\n      el.removeEventListener(\"close\", closed);\n      el.removeEventListener(\"keydown\", key);\n      const inside = el.contains(document.activeElement);\n      if (el.open) el.close();\n      if (modal) unlockScroll();\n      const at = document.activeElement;\n      if (back?.isConnected && (inside || !at || at === document.body)) back.focus({ preventScroll: true });\n    };\n  }, [shown]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  // A press that starts and ends on the backdrop closes it; when it can't, the card gives a small nudge instead\n  const downOnBackdrop = useRef(false);\n  const onBackdrop = (e: ReactMouseEvent<HTMLDialogElement>) => {\n    const hit = downOnBackdrop.current && e.target === e.currentTarget;\n    downOnBackdrop.current = false;\n    if (!hit || leaving) return;\n    if (closeOnBackdrop && !busy) return dismiss.current();\n    if (!reduced()) panel.current?.animate([{ transform: \"scale(1)\" }, { transform: \"scale(1.012)\" }, { transform: \"scale(1)\" }], { duration: 240, easing: \"ease-out\" });\n  };\n\n  if (!shown) return null;\n  const panelStyle = width != null ? ({ \"--ui-dlg-w\": typeof width === \"number\" ? `${width}px` : width } as CSSProperties) : undefined;\n\n  return (\n    <dialog\n      {...rest}\n      ref={layer}\n      className={[\"ui-dlg\", className].filter(Boolean).join(\" \")}\n      style={style}\n      role={alert ? \"alertdialog\" : rest.role}\n      aria-modal={contained ? true : undefined}\n      aria-labelledby={titleId}\n      aria-describedby={shows(description) ? descId : rest[\"aria-describedby\"]}\n      aria-busy={busy || undefined}\n      data-contained={contained || undefined}\n      data-leaving={leaving || undefined}\n      onPointerDown={(e) => (downOnBackdrop.current = e.target === e.currentTarget)}\n      onClick={onBackdrop}\n    >\n      {/* React 19 hoists this into <head> once, no matter how many dialogs render */}\n      <style href=\"ui-dialog\" precedence=\"default\">\n        {css}\n      </style>\n      <div ref={panel} className=\"ui-dlg__panel\" data-size={size} data-alert={alert || undefined} tabIndex={-1} style={panelStyle}>\n        <div className=\"ui-dlg__card\">\n          <div className=\"ui-dlg__head\">\n            <div className=\"ui-dlg__heading\">\n              <h2 className=\"ui-dlg__title\" id={titleId}>\n                {title}\n              </h2>\n              {shows(description) && (\n                <p className=\"ui-dlg__desc\" id={descId}>\n                  {description}\n                </p>\n              )}\n            </div>\n            {close && (\n              <button type=\"button\" className=\"ui-dlg__close\" aria-label={close.label} title={close.label} onClick={close.onClick}>\n                {/* Cancel01 – Hugeicons Free (MIT, © Hugeicons, hugeicons.com) */}\n                <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" focusable=\"false\">\n                  <path d=\"M18 6L6.00081 17.9992M17.9992 18L6 6.00085\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n                </svg>\n              </button>\n            )}\n          </div>\n          {shows(children) && <div className=\"ui-dlg__body\">{children}</div>}\n        </div>\n        {shows(footer) && <div className=\"ui-dlg__foot\">{footer}</div>}\n      </div>\n    </dialog>\n  );\n}\n\n/* ---------- Helpers ---------- */\n\n/** Controlled when open is passed, its own state otherwise; the third value remembers the trigger for the way back */\nfunction useOpenState(open: boolean | undefined, defaultOpen: boolean | undefined, onOpenChange: ((open: boolean) => void) | undefined) {\n  const [own, setOwn] = useState(defaultOpen ?? false);\n  const opener = useRef<HTMLElement | null>(null);\n  const controlled = open !== undefined;\n  const value = controlled ? open : own;\n  const set = (next: boolean) => {\n    if (next === value) return;\n    if (!controlled) setOwn(next);\n    onOpenChange?.(next);\n  };\n  return [value, set, opener] as const;\n}\n\n/** The trigger with the click that opens it – it is remembered, so the focus comes back to it even in Safari */\nfunction withTrigger(trigger: ReactElement<TriggerProps> | undefined, open: boolean, setOpen: (open: boolean) => void, opener: RefObject<HTMLElement | null>) {\n  if (!isValidElement(trigger)) return null;\n  return cloneElement(trigger, {\n    \"aria-haspopup\": \"dialog\",\n    \"aria-expanded\": open,\n    onClick: (e: ReactMouseEvent<HTMLElement>) => {\n      trigger.props.onClick?.(e);\n      if (e.defaultPrevented) return;\n      opener.current = e.currentTarget;\n      setOpen(true);\n    },\n  });\n}\n\n/** Text or an element – not true, false, null or an empty string */\nconst shows = (v: ReactNode) => v != null && typeof v !== \"boolean\" && v !== \"\";\n\nconst reduced = () => typeof window !== \"undefined\" && window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n\nconst FOCUSABLE = \"input, textarea, select, button, [href], [tabindex], [contenteditable='true']\";\n\n/** The controls inside that are shown and reachable by Tab */\nconst controls = (root: Element | null) =>\n  root\n    ? [...root.querySelectorAll<HTMLElement>(FOCUSABLE)].filter(\n        (el) => el.tabIndex >= 0 && !(el as HTMLButtonElement).disabled && el.getClientRects().length > 0,\n      )\n    : [];\nconst firstControl = (root: Element | null) => controls(root)[0] ?? null;\n\n/** Contained: Tab and Shift+Tab go round inside, like the browser does for a modal */\nfunction trapTab(e: KeyboardEvent, root: HTMLElement) {\n  const list = controls(root);\n  if (!list.length) return e.preventDefault();\n  const first = list[0];\n  const last = list[list.length - 1];\n  const at = document.activeElement;\n  const outside = !at || !list.includes(at as HTMLElement);\n  if (e.shiftKey && (at === first || outside)) {\n    e.preventDefault();\n    last.focus();\n  } else if (!e.shiftKey && (at === last || outside)) {\n    e.preventDefault();\n    first.focus();\n  }\n}\n\n/** The page stops scrolling while a dialog is open – counted, so stacked dialogs unlock only once the last closes */\nlet locks = 0;\nlet saved: { overflow: string; gutter: string } | null = null;\nfunction lockScroll() {\n  if (locks++ > 0) return;\n  const html = document.documentElement;\n  // The scrollbar's room is kept, so the page doesn't shift sideways\n  const bar = window.innerWidth - html.clientWidth;\n  saved = { overflow: html.style.overflow, gutter: html.style.scrollbarGutter };\n  html.style.overflow = \"hidden\";\n  if (bar > 0) html.style.scrollbarGutter = \"stable\";\n}\nfunction unlockScroll() {\n  if (--locks > 0 || !saved) return;\n  locks = 0;\n  const html = document.documentElement;\n  html.style.overflow = saved.overflow;\n  html.style.scrollbarGutter = saved.gutter;\n  saved = null;\n}\n\n/* ---------- Styles ---------- */\n\nconst css = /* css */ `\n/* Colors and motion as variables – override them on .ui-dlg (see Theming in the docs) */\n.ui-dlg {\n  --ui-dlg-scrim: rgba(0, 0, 0, 0.18);\n  --ui-dlg-tray: var(--aui-subtle, #f0f0f0);\n  --ui-dlg-surface: var(--aui-surface, #ffffff);\n  --ui-dlg-strong: var(--aui-ink, #171717);\n  --ui-dlg-ink: var(--aui-ink, #171717);\n  --ui-dlg-text: var(--aui-muted, #6b6b6b);\n  --ui-dlg-icon: var(--aui-muted, #737373);\n  --ui-dlg-line: var(--aui-border, #ececec);\n  --ui-dlg-wash: #f0f0f0;\n  --ui-dlg-shadow: var(--aui-shadow-popover, 0 0 0 1px rgba(0, 0, 0, 0.04), 0 24px 56px -12px rgba(0, 0, 0, 0.28), 0 6px 16px -6px rgba(0, 0, 0, 0.08));\n  --ui-dlg-btn: var(--aui-surface, #ffffff);\n  --ui-dlg-btn-hover: #fafafa;\n  --ui-dlg-btn-line: var(--aui-border, #e5e5e5);\n  --ui-dlg-on-dark: #fafafa;\n  --ui-dlg-dark-top: #323137;\n  --ui-dlg-dark-bottom: #201e25;\n  --ui-dlg-dark-hover-top: #3c3b42;\n  --ui-dlg-dark-hover-bottom: #29272f;\n  --ui-dlg-edge-top: #4b4951;\n  --ui-dlg-edge-bottom: #18171b;\n  --ui-dlg-ring: #0d0d0d;\n  --ui-dlg-danger: var(--aui-danger, #c4320a);\n  --ui-dlg-danger-bg: #ffe9e6;\n  --ui-dlg-danger-bg-hover: #ffdcd6;\n  --ui-dlg-focus: var(--aui-focus, rgba(0, 96, 226, 0.7));\n  --ui-dlg-ease: var(--aui-ease, cubic-bezier(0.22, 1, 0.36, 1));\n\n  /* The layer: the whole window (or its frame when contained), dimmed, the card in the middle */\n  position: fixed;\n  inset: 0;\n  z-index: 1000;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  box-sizing: border-box;\n  width: 100%;\n  height: 100%;\n  max-width: none;\n  max-height: none;\n  margin: 0;\n  padding: 24px 16px;\n  border: 0;\n  background: var(--ui-dlg-scrim);\n  color: var(--ui-dlg-ink);\n  overflow: hidden;\n  overscroll-behavior: contain;\n  container: ui-dlg / size;\n  font-family: var(--aui-font, \"Inter\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif);\n  font-size: 13.5px;\n  line-height: 20px;\n  font-weight: 400;\n  letter-spacing: -0.006em;\n  text-align: start;\n  -webkit-font-smoothing: antialiased;\n  animation: ui-dlg-scrim 180ms ease both;\n}\n.ui-dlg:not([open]) { display: none; }\n.ui-dlg::backdrop { background: transparent; }\n.ui-dlg[data-contained] { position: absolute; z-index: 70; }\n.ui-dlg *, .ui-dlg *::before, .ui-dlg *::after { box-sizing: border-box; }\n\n/* The gray tray: the white card on top, the actions on the tray below it */\n.ui-dlg__panel {\n  --ui-dlg-w: 480px;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n  width: var(--ui-dlg-w);\n  max-width: 100%;\n  max-height: 100%;\n  min-height: 0;\n  padding: 4px;\n  border-radius: calc(var(--aui-radius-lg, 16px) + 4px);\n  background: var(--ui-dlg-tray);\n  box-shadow: var(--ui-dlg-shadow);\n  animation: ui-dlg-in 260ms var(--ui-dlg-ease) both;\n}\n.ui-dlg__panel[data-size=\"sm\"] { --ui-dlg-w: 400px; }\n.ui-dlg__panel[data-size=\"lg\"] { --ui-dlg-w: 720px; }\n.ui-dlg__panel:focus { outline: none; }\n\n/* The white card: header and content; the content scrolls, the header stays */\n.ui-dlg__card {\n  display: flex;\n  flex: 1 1 auto;\n  flex-direction: column;\n  min-height: 0;\n  border-radius: var(--aui-radius-lg, 16px);\n  background: var(--ui-dlg-surface);\n  box-shadow: var(--aui-shadow-card, 0 0 0 1px var(--ui-dlg-line), 0 1px 2px rgba(0, 0, 0, 0.04));\n  /* Normally only the content scrolls; when even the title doesn't fit (a short frame, a phone on its side) the card\n     scrolls as a whole – the actions sit on the tray below it and stay in view */\n  overflow: hidden auto;\n}\n\n/* Title and quiet line; the × sits on the title's first line */\n.ui-dlg__head {\n  display: flex;\n  flex: none;\n  align-items: flex-start;\n  gap: 12px;\n  padding-block: 18px 0;\n  padding-inline: 20px 13px;\n}\n.ui-dlg__head:not(:has(.ui-dlg__close)) { padding-inline-end: 20px; }\n.ui-dlg__head:last-child { padding-bottom: 20px; }\n.ui-dlg__panel[data-alert] .ui-dlg__head { padding-top: 20px; }\n.ui-dlg__heading { display: grid; flex: 1; gap: 4px; min-width: 0; }\n.ui-dlg__title { margin: 0; font-size: 15px; line-height: 22px; font-weight: 500; letter-spacing: -0.01em; color: var(--ui-dlg-strong); overflow-wrap: anywhere; }\n.ui-dlg__desc { margin: 0; color: var(--ui-dlg-text); overflow-wrap: anywhere; }\n\n.ui-dlg__close {\n  position: relative;\n  display: grid;\n  flex: none;\n  place-items: center;\n  width: 32px;\n  height: 32px;\n  margin: -5px 0 0;\n  padding: 0;\n  border: 0;\n  border-radius: calc(var(--aui-radius, 12px) - 2px);\n  background: transparent;\n  color: var(--ui-dlg-icon);\n  cursor: pointer;\n  -webkit-tap-highlight-color: transparent;\n  transition: background-color 160ms ease, color 160ms ease, transform 160ms var(--ui-dlg-ease);\n}\n.ui-dlg__close svg { display: block; width: 16px; height: 16px; }\n.ui-dlg__close:hover { background: var(--ui-dlg-wash); color: var(--ui-dlg-ink); }\n.ui-dlg__close:active { transform: scale(0.94); }\n\n/*\n * The content scrolls under the fixed header. A hairline shows at the top once it is scrolled and at the bottom\n * while there is more – drawn by covers that scroll along with the content, so no script is needed\n */\n.ui-dlg__body {\n  flex: 1 1 auto;\n  min-height: 0;\n  padding: 12px 20px 20px;\n  overflow: auto;\n  overscroll-behavior: contain;\n  overflow-wrap: anywhere;\n  background:\n    linear-gradient(var(--ui-dlg-surface) 0 0) top / 100% 2px no-repeat local,\n    linear-gradient(var(--ui-dlg-surface) 0 0) bottom / 100% 2px no-repeat local,\n    linear-gradient(var(--ui-dlg-line) 0 0) top / 100% 1px no-repeat scroll,\n    linear-gradient(var(--ui-dlg-line) 0 0) bottom / 100% 1px no-repeat scroll,\n    var(--ui-dlg-surface);\n}\n\n/* The actions on the tray, aligned to the end – as far from the card as from the tray's edge */\n.ui-dlg__foot {\n  display: flex;\n  flex: none;\n  flex-wrap: wrap;\n  align-items: center;\n  justify-content: flex-end;\n  gap: 8px;\n  padding: 8px 4px 4px;\n}\n\n/* AlertDialog's own two buttons – the classic Button's secondary, dark gradient and red fill, at its small size */\n.ui-dlg__btn {\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  height: 32px;\n  min-width: 0;\n  max-width: 100%;\n  margin: 0;\n  padding: 0 12px;\n  border: 1px solid var(--ui-dlg-btn-line);\n  border-radius: calc(var(--aui-radius, 12px) - 2px);\n  background: var(--ui-dlg-btn);\n  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);\n  color: var(--ui-dlg-ink);\n  font: inherit;\n  font-size: 13px;\n  font-weight: 500;\n  line-height: 1;\n  white-space: nowrap;\n  cursor: pointer;\n  -webkit-tap-highlight-color: transparent;\n  transition: background-color 160ms ease, box-shadow 160ms ease, color 160ms ease, transform 160ms var(--ui-dlg-ease), opacity 160ms ease;\n}\n.ui-dlg__btn:hover:not(:disabled) { background: var(--ui-dlg-btn-hover); }\n.ui-dlg__btn[data-tone=\"primary\"] {\n  border-color: transparent;\n  background:\n    linear-gradient(var(--ui-dlg-dark-top), var(--ui-dlg-dark-bottom)) padding-box,\n    linear-gradient(var(--ui-dlg-edge-top), var(--ui-dlg-edge-bottom)) border-box;\n  box-shadow: 0 0 0 1px var(--ui-dlg-ring), 0 2px 4px rgba(0, 0, 0, 0.1);\n  color: var(--ui-dlg-on-dark);\n}\n.ui-dlg__btn[data-tone=\"primary\"]:hover {\n  background:\n    linear-gradient(var(--ui-dlg-dark-hover-top), var(--ui-dlg-dark-hover-bottom)) padding-box,\n    linear-gradient(var(--ui-dlg-edge-top), var(--ui-dlg-edge-bottom)) border-box;\n}\n.ui-dlg__btn[data-tone=\"danger\"] { border-color: transparent; background: var(--ui-dlg-danger-bg); box-shadow: none; color: var(--ui-dlg-danger); }\n.ui-dlg__btn[data-tone=\"danger\"]:hover { background: var(--ui-dlg-danger-bg-hover); }\n.ui-dlg__btn:active:not(:disabled):not([data-busy]) { transform: scale(0.97); }\n.ui-dlg__btn:disabled { opacity: 0.4; cursor: default; }\n.ui-dlg__btn-label { overflow: hidden; text-overflow: ellipsis; line-height: 20px; }\n/* Busy: the label keeps the width, a small arc turns in its place */\n.ui-dlg__btn[data-busy] { cursor: progress; }\n.ui-dlg__btn[data-busy] .ui-dlg__btn-label { visibility: hidden; }\n.ui-dlg__spin {\n  position: absolute;\n  inset: 0;\n  width: 14px;\n  height: 14px;\n  margin: auto;\n  border: 1.5px solid currentColor;\n  border-inline-end-color: transparent;\n  border-radius: 50%;\n  animation: ui-dlg-turn 700ms linear infinite;\n}\n\n.ui-dlg :is(.ui-dlg__close, .ui-dlg__btn):focus-visible {\n  outline: 2px solid var(--ui-dlg-focus);\n  outline-offset: 2px;\n}\n\n/* The way out: the backdrop fades, the card shrinks a little as it goes */\n.ui-dlg[data-leaving] { pointer-events: none; animation: ui-dlg-scrim-out ${EXIT_MS}ms ease both; }\n.ui-dlg[data-leaving] .ui-dlg__panel { animation: ui-dlg-out 150ms ease-in both; }\n\n@keyframes ui-dlg-scrim { from { background-color: transparent; } }\n@keyframes ui-dlg-scrim-out { to { background-color: transparent; } }\n@keyframes ui-dlg-in { from { opacity: 0; transform: translateY(8px) scale(0.97); } }\n@keyframes ui-dlg-out { to { opacity: 0; transform: scale(0.98); } }\n@keyframes ui-dlg-sheet-in { from { transform: translateY(100%); } }\n@keyframes ui-dlg-sheet-out { to { transform: translateY(100%); } }\n@keyframes ui-dlg-turn { to { transform: rotate(1turn); } }\n\n/*\n * Phones (or a narrow frame): a sheet from the bottom – full width, the actions within the thumb's reach and above\n * the home bar, the content scrolling under the title\n */\n@container ui-dlg (max-width: 520px) {\n  .ui-dlg__panel {\n    /* Out over the layer's padding to the edges and the bottom; at least 40px of backdrop stay above it */\n    width: calc(100% + 32px);\n    max-width: none;\n    max-height: calc(100% + 8px);\n    margin: auto -16px -24px;\n    padding-bottom: max(4px, env(safe-area-inset-bottom));\n    border-end-start-radius: 0;\n    border-end-end-radius: 0;\n    animation: ui-dlg-sheet-in 320ms var(--ui-dlg-ease) both;\n  }\n  .ui-dlg__panel[data-alert] .ui-dlg__btn { flex: 1 1 0; }\n  .ui-dlg[data-leaving] .ui-dlg__panel { animation: ui-dlg-sheet-out ${EXIT_MS}ms cubic-bezier(0.4, 0, 1, 1) both; }\n}\n\n/* Touch screens: bigger targets – the look stays */\n@media (pointer: coarse) {\n  .ui-dlg__btn { height: 40px; padding: 0 16px; }\n  .ui-dlg__close { width: 36px; height: 36px; margin: -7px -2px 0 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .ui-dlg, .ui-dlg__panel, .ui-dlg[data-leaving], .ui-dlg[data-leaving] .ui-dlg__panel { animation-duration: 1ms; }\n  .ui-dlg__spin { animation-duration: 1.6s; }\n}\n\n/* Dark mode: follows a .dark class or data-theme=\"dark\" on any ancestor, e.g. <html> */\n:where(.dark, [data-theme=\"dark\"]) .ui-dlg {\n  --ui-dlg-scrim: rgba(0, 0, 0, 0.5);\n  --ui-dlg-tray: var(--aui-subtle, #111113);\n  --ui-dlg-surface: var(--aui-surface, #19191b);\n  --ui-dlg-strong: var(--aui-ink, #ededed);\n  --ui-dlg-ink: var(--aui-ink, #ededed);\n  --ui-dlg-text: var(--aui-muted, #a1a1a1);\n  --ui-dlg-icon: var(--aui-muted, #8b8b92);\n  --ui-dlg-line: var(--aui-border, #2a2a2e);\n  --ui-dlg-wash: rgba(255, 255, 255, 0.06);\n  --ui-dlg-shadow: var(--aui-shadow-popover, 0 0 0 1px rgba(255, 255, 255, 0.06), 0 24px 56px -12px rgba(0, 0, 0, 0.7));\n  --ui-dlg-btn: #232326;\n  --ui-dlg-btn-hover: #2a2a2d;\n  --ui-dlg-btn-line: var(--aui-border, rgba(255, 255, 255, 0.1));\n  /* The dark gradient turns light, like the Button's primary */\n  --ui-dlg-on-dark: #121213;\n  --ui-dlg-dark-top: #f4f4f5;\n  --ui-dlg-dark-bottom: #e4e4e7;\n  --ui-dlg-dark-hover-top: #ffffff;\n  --ui-dlg-dark-hover-bottom: #ececef;\n  --ui-dlg-edge-top: #ffffff;\n  --ui-dlg-edge-bottom: #d4d4d8;\n  --ui-dlg-ring: rgba(0, 0, 0, 0.6);\n  --ui-dlg-danger: var(--aui-danger, #ff8a7a);\n  --ui-dlg-danger-bg: rgba(255, 107, 95, 0.16);\n  --ui-dlg-danger-bg-hover: rgba(255, 107, 95, 0.22);\n  --ui-dlg-focus: var(--aui-focus, rgba(77, 141, 255, 0.8));\n  color-scheme: dark;\n}\n`;\n"}]}