{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"spatial-command","type":"registry:component","title":"Spatial Command Menu","description":"A ⌘K menu where results regroup by kind and selection previews its effect.","dependencies":[],"categories":["navigation","command-menu","search","keyboard","preview"],"files":[{"path":"components/lab/command-menu/command-menu.tsx","target":"components/lab/command-menu/command-menu.tsx","type":"registry:component","content":"\"use client\";\n\nimport {\n  useEffect,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n  type KeyboardEvent as ReactKeyboardEvent,\n  type ReactNode,\n} from \"react\";\nimport \"./command-menu.css\";\n\nexport type CommandItem = {\n  id: string;\n  label: string;\n  group: string;\n  keywords?: string[];\n  description?: string;\n  shortcut?: string;\n  preview?: ReactNode;\n  run?: () => void;\n  disabled?: boolean;\n};\n\nexport type CommandMenuProps = {\n  /** Controlled visibility. The menu renders nothing while closed. */\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  items: CommandItem[];\n  /**\n   * `true` binds ⌘K / Ctrl+K while mounted. A string rebinds the key\n   * (`\"p\"` → ⌘P), keeping the same modifiers.\n   */\n  hotkey?: boolean | string;\n  /** Shows the selected command’s preview/description pane. */\n  previewPanel?: boolean;\n  /** Fixed group order; otherwise first-seen order is kept. */\n  groups?: string[];\n  density?: \"compact\" | \"roomy\";\n  /** Called before the menu closes, after `item.run()`. */\n  onRun?: (item: CommandItem) => void;\n  className?: string;\n};\n\ntype RankedItem = { item: CommandItem; index: number; rank: number };\n\ntype CommandGroup = { group: string; items: CommandItem[] };\n\nconst HOTKEY_FALLBACK = \"k\";\n\n/**\n * Match a query against one command. Lower is better; -1 means no\n * match. Labels outrank metadata, and exact/prefix hits float up.\n */\nfunction rankMatch(item: CommandItem, query: string): number {\n  const label = item.label.toLowerCase();\n  if (label === query) return 0;\n  if (label.startsWith(query)) return 1;\n  if (label.includes(query)) return 2;\n  const metadata = [item.group, ...(item.keywords ?? []), item.description ?? \"\"];\n  if (metadata.join(\" \\u0000 \").toLowerCase().includes(query)) return 3;\n  return -1;\n}\n\n/**\n * Filter and regroup. Group order follows the `groups` prop when\n * provided, otherwise the order groups first appear in `items` — a\n * stable order, so the list never reshuffles between keystrokes.\n */\nfunction buildGroups(\n  items: CommandItem[],\n  query: string,\n  groups?: string[],\n): CommandGroup[] {\n  const q = query.trim().toLowerCase();\n  const ranked: RankedItem[] = items.map((item, index) => ({\n    item,\n    index,\n    rank: q ? rankMatch(item, q) : 0,\n  }));\n  const matched = q\n    ? ranked\n        .filter((entry) => entry.rank >= 0)\n        .sort((a, b) => a.rank - b.rank || a.index - b.index)\n    : ranked;\n\n  const seen: string[] = [];\n  for (const item of items) {\n    if (!seen.includes(item.group)) seen.push(item.group);\n  }\n  const order = groups && groups.length > 0\n    ? [\n        ...groups.filter((group) => seen.includes(group)),\n        ...seen.filter((group) => !groups.includes(group)),\n      ]\n    : seen;\n\n  return order\n    .map((group) => ({\n      group,\n      items: matched\n        .filter((entry) => entry.item.group === group)\n        .map((entry) => entry.item),\n    }))\n    .filter((entry) => entry.items.length > 0);\n}\n\nfunction firstEnabledId(items: CommandItem[]): string | null {\n  return items.find((item) => !item.disabled)?.id ?? null;\n}\n\n/**\n * COMMAND MENU — a ⌘K palette that keeps its spatial structure.\n *\n * Results stay grouped instead of collapsing into one flat list, and\n * the highlighted command previews its effect before it runs. Filtering\n * is synchronous and untransitioned: the list is a plain re-render, so\n * keystrokes never queue behind animation. The listbox follows the\n * combobox pattern — focus stays in the search input and the active\n * option is tracked with aria-activedescendant — so ↑↓/Home/End and\n * screen-reader announcements read the same structure.\n */\nexport function CommandMenu({\n  open,\n  onOpenChange,\n  items,\n  hotkey = true,\n  previewPanel = true,\n  groups,\n  density = \"compact\",\n  onRun,\n  className,\n}: CommandMenuProps) {\n  const listId = useId();\n  const panelRef = useRef<HTMLDivElement>(null);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const optionRefs = useRef<Map<string, HTMLDivElement>>(new Map());\n  const [query, setQuery] = useState(\"\");\n  // Opening directly (mount with `open`) still starts with a\n  // highlighted command on the very first painted frame.\n  const [activeId, setActiveId] = useState<string | null>(() =>\n    open ? firstEnabledId(items) : null,\n  );\n\n  const results = useMemo(\n    () => buildGroups(items, query, groups),\n    [items, query, groups],\n  );\n  const flatItems = useMemo(\n    () => results.flatMap((entry) => entry.items),\n    [results],\n  );\n  const activeItem = activeId\n    ? flatItems.find((item) => item.id === activeId) ?? null\n    : null;\n\n  // Reset the search the moment the menu opens, during the render\n  // itself, so the first painted frame never shows the old query.\n  const [lastOpen, setLastOpen] = useState(open);\n  if (open !== lastOpen) {\n    setLastOpen(open);\n    if (open) {\n      setQuery(\"\");\n      setActiveId(firstEnabledId(items));\n    }\n  }\n\n  // Open: capture whatever was focused, move focus into the input.\n  // Close: hand focus back — but never steal it from a new target.\n  useEffect(() => {\n    if (!open) return;\n    const panel = panelRef.current;\n    const previous =\n      document.activeElement instanceof HTMLElement ? document.activeElement : null;\n    inputRef.current?.focus({ preventScroll: true });\n    return () => {\n      if (!previous || !previous.isConnected) return;\n      const current = document.activeElement;\n      const inside = panel ? panel.contains(current) : false;\n      if (!current || current === document.body || inside) {\n        previous.focus({ preventScroll: true });\n      }\n    };\n  }, [open]);\n\n  // Keep exactly one highlighted, enabled option as results change.\n  const activeIsValid =\n    activeId !== null &&\n    flatItems.some((item) => item.id === activeId && !item.disabled);\n  if (open && open === lastOpen && !activeIsValid) {\n    const first = firstEnabledId(flatItems);\n    if (first !== activeId) setActiveId(first);\n  }\n\n  // Hotkey: one stable window listener; the handler itself lives in a\n  // ref so it always sees the current props without re-subscribing.\n  const hotkeyHandlerRef = useRef<(event: KeyboardEvent) => void>(() => {});\n  useEffect(() => {\n    hotkeyHandlerRef.current = (event: KeyboardEvent) => {\n      if (!hotkey) return;\n      if (!(event.metaKey || event.ctrlKey)) return;\n      const char =\n        typeof hotkey === \"string\" && hotkey.length > 0\n          ? hotkey.slice(0, 1).toLowerCase()\n          : HOTKEY_FALLBACK;\n      if (event.key.toLowerCase() !== char) return;\n      event.preventDefault();\n      onOpenChange(!open);\n    };\n  });\n  useEffect(() => {\n    const listener = (event: KeyboardEvent) => hotkeyHandlerRef.current(event);\n    window.addEventListener(\"keydown\", listener);\n    return () => window.removeEventListener(\"keydown\", listener);\n  }, []);\n\n  const optionDomId = (id: string) =>\n    `${listId}-opt-${id.replace(/[^a-zA-Z0-9_-]/g, \"-\")}`;\n\n  const scrollActiveIntoView = (id: string) => {\n    optionRefs.current.get(id)?.scrollIntoView({ block: \"nearest\" });\n  };\n\n  const moveActive = (delta: number, wrap: boolean) => {\n    const enabled = flatItems.filter((item) => !item.disabled);\n    if (enabled.length === 0) return;\n    const current = enabled.findIndex((item) => item.id === activeId);\n    const from = current === -1 ? (delta > 0 ? -1 : enabled.length) : current;\n    let next = from + delta;\n    next = wrap\n      ? ((next % enabled.length) + enabled.length) % enabled.length\n      : Math.max(0, Math.min(enabled.length - 1, next));\n    const target = enabled[next];\n    setActiveId(target.id);\n    scrollActiveIntoView(target.id);\n  };\n\n  const moveActiveTo = (position: \"first\" | \"last\") => {\n    const enabled = flatItems.filter((item) => !item.disabled);\n    if (enabled.length === 0) return;\n    const target = position === \"first\" ? enabled[0] : enabled[enabled.length - 1];\n    setActiveId(target.id);\n    scrollActiveIntoView(target.id);\n  };\n\n  const runItem = (item: CommandItem) => {\n    if (item.disabled) return;\n    item.run?.();\n    onRun?.(item);\n    onOpenChange(false);\n  };\n\n  const handleInputKeyDown = (event: ReactKeyboardEvent<HTMLInputElement>) => {\n    switch (event.key) {\n      case \"ArrowDown\":\n        event.preventDefault();\n        moveActive(1, true);\n        break;\n      case \"ArrowUp\":\n        event.preventDefault();\n        moveActive(-1, true);\n        break;\n      case \"PageDown\":\n        event.preventDefault();\n        moveActive(5, false);\n        break;\n      case \"PageUp\":\n        event.preventDefault();\n        moveActive(-5, false);\n        break;\n      case \"Home\":\n        event.preventDefault();\n        moveActiveTo(\"first\");\n        break;\n      case \"End\":\n        event.preventDefault();\n        moveActiveTo(\"last\");\n        break;\n      case \"Enter\":\n        event.preventDefault();\n        if (activeItem) runItem(activeItem);\n        break;\n      default:\n        break;\n    }\n  };\n\n  // Escape closes, and Tab cycles inside the dialog: the input and the\n  // close button are the only tab stops (options follow the combobox\n  // pattern and are never focusable).\n  const handlePanelKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n    if (event.key === \"Escape\") {\n      event.preventDefault();\n      onOpenChange(false);\n      return;\n    }\n    if (event.key !== \"Tab\") return;\n    const root = panelRef.current;\n    if (!root) return;\n    const focusables = Array.from(\n      root.querySelectorAll<HTMLElement>(\n        'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n      ),\n    ).filter((element) => element.tabIndex !== -1);\n    if (focusables.length === 0) return;\n    const first = focusables[0];\n    const last = focusables[focusables.length - 1];\n    const active = document.activeElement as HTMLElement | null;\n    const activeInside = active ? root.contains(active) : false;\n    if (event.shiftKey && (active === first || !activeInside)) {\n      event.preventDefault();\n      last.focus();\n    } else if (!event.shiftKey && (active === last || !activeInside)) {\n      event.preventDefault();\n      first.focus();\n    }\n  };\n\n  if (!open) return null;\n\n  return (\n    <div className=\"cm-root\">\n      <div\n        className=\"cm-backdrop\"\n        onClick={() => onOpenChange(false)}\n        aria-hidden=\"true\"\n      />\n\n      <div className=\"cm-positioner\">\n        <div\n          ref={panelRef}\n          className={`cm-panel${className ? ` ${className}` : \"\"}`}\n          data-density={density}\n          data-preview={previewPanel ? \"true\" : \"false\"}\n          role=\"dialog\"\n          aria-modal=\"true\"\n          aria-label=\"Command menu\"\n          onKeyDown={handlePanelKeyDown}\n        >\n          <div className=\"cm-search\">\n            <svg\n              className=\"cm-search-icon\"\n              viewBox=\"0 0 16 16\"\n              width=\"14\"\n              height=\"14\"\n              aria-hidden=\"true\"\n            >\n              <circle\n                cx=\"7\"\n                cy=\"7\"\n                r=\"4.5\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n              />\n              <path\n                d=\"M10.5 10.5 14 14\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"1.5\"\n                strokeLinecap=\"round\"\n              />\n            </svg>\n            <input\n              ref={inputRef}\n              className=\"cm-input\"\n              type=\"text\"\n              value={query}\n              onChange={(event) => {\n                const value = event.target.value;\n                setQuery(value);\n                setActiveId(\n                  firstEnabledId(buildGroups(items, value, groups).flatMap((g) => g.items)),\n                );\n              }}\n              onKeyDown={handleInputKeyDown}\n              role=\"combobox\"\n              aria-expanded=\"true\"\n              aria-controls={listId}\n              aria-activedescendant={activeId ? optionDomId(activeId) : undefined}\n              aria-autocomplete=\"list\"\n              aria-haspopup=\"listbox\"\n              aria-label=\"Search commands\"\n              placeholder=\"Search commands…\"\n              autoComplete=\"off\"\n              spellCheck={false}\n            />\n            <button\n              type=\"button\"\n              className=\"cm-close\"\n              onClick={() => onOpenChange(false)}\n              aria-label=\"Close command menu\"\n            >\n              <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n                <path\n                  d=\"M4 4l8 8M12 4l-8 8\"\n                  fill=\"none\"\n                  stroke=\"currentColor\"\n                  strokeWidth=\"1.5\"\n                  strokeLinecap=\"round\"\n                />\n              </svg>\n            </button>\n          </div>\n\n          <div className=\"cm-body\">\n            <div className=\"cm-list-wrap\">\n              <div\n                className=\"cm-list\"\n                id={listId}\n                role=\"listbox\"\n                aria-label=\"Commands\"\n              >\n                {results.map((entry) => (\n                  <div\n                    key={entry.group}\n                    className=\"cm-group\"\n                    role=\"group\"\n                    aria-label={entry.group}\n                  >\n                    <div className=\"cm-group-label\" role=\"presentation\">\n                      {entry.group}\n                    </div>\n                    {entry.items.map((item) => (\n                      <div\n                        key={item.id}\n                        id={optionDomId(item.id)}\n                        ref={(element) => {\n                          if (element) optionRefs.current.set(item.id, element);\n                          else optionRefs.current.delete(item.id);\n                        }}\n                        className=\"cm-option\"\n                        role=\"option\"\n                        aria-selected={item.id === activeId}\n                        aria-disabled={item.disabled ? \"true\" : undefined}\n                        data-active={item.id === activeId ? \"true\" : undefined}\n                        data-disabled={item.disabled ? \"true\" : undefined}\n                        onMouseDown={(event) => event.preventDefault()}\n                        onClick={() => runItem(item)}\n                        onMouseMove={() => {\n                          if (!item.disabled && item.id !== activeId) {\n                            setActiveId(item.id);\n                          }\n                        }}\n                      >\n                        <span className=\"cm-option-label\">{item.label}</span>\n                        {item.description ? (\n                          <span className=\"cm-option-desc\">{item.description}</span>\n                        ) : null}\n                        {item.shortcut ? (\n                          <kbd className=\"cm-kbd\">{item.shortcut}</kbd>\n                        ) : null}\n                      </div>\n                    ))}\n                  </div>\n                ))}\n              </div>\n              {results.length === 0 ? (\n                <p className=\"cm-empty\" role=\"status\">\n                  No commands match “{query.trim()}”.\n                </p>\n              ) : null}\n            </div>\n\n            {previewPanel ? (\n              <aside className=\"cm-preview\" aria-label=\"Command preview\">\n                <div className=\"cm-preview-head\">\n                  <span className=\"cm-preview-title\">\n                    {activeItem ? activeItem.label : \"Preview\"}\n                  </span>\n                  {activeItem?.shortcut ? (\n                    <kbd className=\"cm-kbd\">{activeItem.shortcut}</kbd>\n                  ) : null}\n                </div>\n                <div className=\"cm-preview-body\" aria-live=\"polite\">\n                  {activeItem?.preview ??\n                    activeItem?.description ?? (\n                      <p className=\"cm-preview-quiet\">No preview</p>\n                    )}\n                </div>\n              </aside>\n            ) : null}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"},{"path":"components/lab/command-menu/command-menu.css","target":"components/lab/command-menu/command-menu.css","type":"registry:file","content":"/* ============================================================\n   COMMAND MENU — self-contained styles.\n   Every token is scoped on .cm-root with a fallback, so the\n   component works in any host codebase.\n   ============================================================ */\n\n.cm-root {\n  --cm-ink: #141418;\n  --cm-muted: #5f5f68;\n  --cm-faint: #8c8c94;\n  --cm-line: rgba(20, 20, 24, 0.1);\n  --cm-line-soft: rgba(20, 20, 24, 0.06);\n  --cm-surface: #ffffff;\n  --cm-surface-soft: #f7f7f6;\n  --cm-accent: #4f27e0;\n  --cm-accent-soft: rgba(79, 39, 224, 0.08);\n  --cm-shadow: 0 24px 64px -24px rgba(20, 20, 24, 0.35),\n    0 4px 14px -6px rgba(20, 20, 24, 0.18);\n  --cm-radius: 14px;\n  --cm-row-h: 34px;\n\n  position: fixed;\n  inset: 0;\n  z-index: 1000;\n  font-family: inherit;\n  color: var(--cm-ink);\n}\n\n.cm-backdrop {\n  position: absolute;\n  inset: 0;\n  background: rgba(20, 20, 24, 0.42);\n  animation: cm-fade-in 120ms ease-out both;\n}\n\n.cm-positioner {\n  position: absolute;\n  inset: 0;\n  display: flex;\n  justify-content: center;\n  overflow-y: auto;\n  padding: 24px 16px;\n  pointer-events: none;\n}\n\n.cm-panel {\n  pointer-events: auto;\n  display: flex;\n  flex-direction: column;\n  margin: auto 0;\n  width: min(100%, 760px);\n  max-height: min(560px, calc(100dvh - 48px));\n  background: var(--cm-surface);\n  border: 1px solid var(--cm-line);\n  border-radius: var(--cm-radius);\n  box-shadow: var(--cm-shadow);\n  overflow: hidden;\n  animation: cm-panel-in 120ms cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n\n.cm-panel[data-preview=\"false\"] {\n  width: min(100%, 620px);\n}\n\n.cm-panel[data-density=\"roomy\"] {\n  --cm-row-h: 44px;\n}\n\n/* ---------- search ---------- */\n\n.cm-search {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  flex: none;\n  padding: 10px 12px;\n  border-bottom: 1px solid var(--cm-line-soft);\n}\n\n.cm-search:focus-within {\n  box-shadow: inset 0 0 0 2px var(--cm-accent);\n  border-radius: var(--cm-radius) var(--cm-radius) 0 0;\n}\n\n.cm-search-icon {\n  flex: none;\n  color: var(--cm-faint);\n}\n\n.cm-input {\n  flex: 1;\n  min-width: 0;\n  border: 0;\n  outline: none;\n  background: transparent;\n  font: inherit;\n  font-size: 0.9375rem;\n  color: var(--cm-ink);\n  padding: 4px 0;\n}\n\n.cm-input::placeholder {\n  color: var(--cm-faint);\n}\n\n.cm-close {\n  flex: none;\n  display: grid;\n  place-items: center;\n  width: 28px;\n  height: 28px;\n  border: 1px solid transparent;\n  border-radius: 8px;\n  background: transparent;\n  color: var(--cm-muted);\n  cursor: pointer;\n}\n\n.cm-close:hover {\n  background: var(--cm-surface-soft);\n  color: var(--cm-ink);\n}\n\n.cm-close:focus-visible {\n  outline: 2px solid var(--cm-accent);\n  outline-offset: 1px;\n}\n\n/* ---------- body: list + preview ---------- */\n\n.cm-body {\n  display: flex;\n  flex: 1;\n  min-height: 0;\n}\n\n.cm-list-wrap {\n  display: flex;\n  flex-direction: column;\n  flex: 1;\n  min-width: 0;\n  min-height: 0;\n}\n\n/*\n * Filtering must be instant: nothing inside the list declares a\n * transition, so a keystroke repaints synchronously.\n */\n.cm-list,\n.cm-group,\n.cm-group-label,\n.cm-option {\n  transition: none;\n}\n\n.cm-list {\n  flex: 1;\n  overflow-y: auto;\n  padding: 4px 6px 8px;\n  overscroll-behavior: contain;\n}\n\n/* With no results the list collapses so the empty note sits under\n   the search row instead of at the bottom of the pane. */\n.cm-list:empty {\n  flex: 0 0 auto;\n  padding: 0;\n}\n\n.cm-group-label {\n  padding: 10px 8px 4px;\n  font-size: 0.6563rem;\n  font-weight: 600;\n  letter-spacing: 0.1em;\n  text-transform: uppercase;\n  color: var(--cm-faint);\n}\n\n.cm-option {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  min-height: var(--cm-row-h);\n  padding: 5px 8px;\n  border-radius: 9px;\n  cursor: pointer;\n}\n\n.cm-option[data-active=\"true\"] {\n  background: var(--cm-accent-soft);\n  box-shadow: inset 2px 0 0 var(--cm-accent);\n}\n\n.cm-option[data-disabled=\"true\"] {\n  opacity: 0.45;\n  cursor: default;\n}\n\n.cm-option-label {\n  flex: none;\n  font-size: 0.875rem;\n  font-weight: 500;\n  letter-spacing: -0.005em;\n  white-space: nowrap;\n}\n\n.cm-option-desc {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  font-size: 0.75rem;\n  color: var(--cm-muted);\n}\n\n.cm-kbd {\n  flex: none;\n  font-family: ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;\n  font-size: 0.6563rem;\n  color: var(--cm-muted);\n  background: var(--cm-surface-soft);\n  border: 1px solid var(--cm-line-soft);\n  border-radius: 5px;\n  padding: 2px 6px;\n  white-space: nowrap;\n}\n\n.cm-empty {\n  margin: 0;\n  padding: 28px 16px;\n  font-size: 0.8125rem;\n  color: var(--cm-muted);\n}\n\n/* ---------- preview pane ---------- */\n\n.cm-preview {\n  flex: 0 0 268px;\n  min-height: 0;\n  overflow-y: auto;\n  padding: 12px 14px;\n  border-left: 1px solid var(--cm-line-soft);\n  background: var(--cm-surface);\n  overscroll-behavior: contain;\n}\n\n.cm-preview-head {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 8px;\n  padding-bottom: 10px;\n  border-bottom: 1px solid var(--cm-line-soft);\n}\n\n.cm-preview-title {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  font-size: 0.75rem;\n  font-weight: 600;\n  letter-spacing: 0.01em;\n}\n\n.cm-preview-body {\n  padding-top: 12px;\n  font-size: 0.8125rem;\n  line-height: 1.55;\n  color: var(--cm-muted);\n}\n\n.cm-preview-quiet {\n  margin: 0;\n  color: var(--cm-faint);\n  font-size: 0.75rem;\n}\n\n/* ---------- animation ---------- */\n\n@keyframes cm-fade-in {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n@keyframes cm-panel-in {\n  from {\n    opacity: 0;\n    transform: translateY(4px) scale(0.985);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0) scale(1);\n  }\n}\n\n/* ---------- narrow: the preview moves under the list ---------- */\n\n@media (max-width: 719px) {\n  .cm-positioner {\n    padding: 16px 12px;\n  }\n\n  .cm-body {\n    flex-direction: column;\n  }\n\n  .cm-preview {\n    flex: none;\n    width: 100%;\n    max-height: 132px;\n    border-left: 0;\n    border-top: 1px solid var(--cm-line-soft);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .cm-backdrop,\n  .cm-panel {\n    animation: none;\n  }\n}\n"}]}