{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "install-command",
  "title": "Install Command",
  "description": "npm / pnpm / yarn / bun tabs where only the words that differ change. Every block on the page follows your pick, in a cascade, and remembers it.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "src/components/install-command/install-command.tsx",
      "content": "// Brio UI · install-command · MIT License · Copyright (c) 2026 Harjot Singh Rana · https://brioui.com/docs/components/install-command\n'use client'\n/**\n * Install Command — the npm / pnpm / yarn / bun block, done properly.\n *\n * Switch package manager and only the words that differ change: `install`\n * rolls out, `add` rolls in, the package name glides into place. The tab pill\n * stretches toward where it's going. Pick pnpm once and every install block\n * on the page follows, in a cascade outward from the one you clicked, and\n * the choice sticks across visits and tabs.\n *\n * Functional:\n *   - presets: add (with dev / global), exec (npx / dlx / bunx), create, run\n *   - or pass exact `commands` per manager\n *   - copy button (with fallback), ⌘/Ctrl+C on the focused block copies too\n *   - one click selects the whole command; the prompt is never copied\n *\n * Accessible: real tablist (arrow keys, Home/End), tabpanel, copy result\n * (success or failure) announced, the command read as one plain string\n * while the animated letters stay hidden from assistive tech.\n * Reduced motion: words crossfade, nothing slides.\n *\n * Corners follow --ui-radius, --ui-radius-sm (see Theming → Corners).\n * Theme via library tokens (--ui-ink, --ui-surface, --ui-accent) or\n * --install-command-* overrides; font via --install-command-font.\n * Copy: every UI string can be replaced through `labels`.\n *\n * Worst case: multi-line exact commands keep their line breaks, a long prompt\n * ellipsizes, a long copy label ellipsizes into the free space, emoji and\n * shaped scripts never get split, and `managers={[]}` falls back to all four.\n *\n * Requires: react >= 19, motion >= 11.\n */\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useMotionValue,\n  type Variants,\n} from 'motion/react'\nimport {\n  createElement,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  type CSSProperties,\n  type KeyboardEvent,\n} from 'react'\n\n/* Icons: Iconoir (MIT, © 2021 Luca Burgio, iconoir.com), inlined so the component stays dependency-free. */\nconst UI_ICONS = {\n  'check': [['path', { d: 'M5 13L9 17L19 7' }]],\n  'copy': [['path', { d: 'M19.4 20H9.6C9.26863 20 9 19.7314 9 19.4V9.6C9 9.26863 9.26863 9 9.6 9H19.4C19.7314 9 20 9.26863 20 9.6V19.4C20 19.7314 19.7314 20 19.4 20Z' }], ['path', { d: 'M15 9V4.6C15 4.26863 14.7314 4 14.4 4H4.6C4.26863 4 4 4.26863 4 4.6V14.4C4 14.7314 4.26863 15 4.6 15H9' }]],\n} as const satisfies Record<string, readonly (readonly [string, Record<string, string>])[]>\n\ntype UiIconName = keyof typeof UI_ICONS\n\nfunction UiIcon({ name, size = 16, className }: { name: UiIconName; size?: number; className?: string }) {\n  return (\n    <svg className={['ui-icon', className].filter(Boolean).join(' ')} viewBox=\"0 0 24 24\" width={size} height={size} fill=\"none\" stroke=\"currentColor\" strokeWidth={1.6} strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n      {UI_ICONS[name].map(([tag, attrs], i) => createElement(tag, { key: i, ...attrs }))}\n    </svg>\n  )\n}\n\n/* Brand marks: Simple Icons (CC0, simpleicons.org). */\nconst BRAND_PATHS = {\n  npm: 'M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z',\n  pnpm: 'M0 0v7.5h7.5V0zm8.25 0v7.5h7.498V0zm8.25 0v7.5H24V0zM2 2h3.5v3.5H2zm8.25 0h3.498v3.5H10.25zm8.25 0H22v3.5h-3.5zM8.25 8.25v7.5h7.498v-7.5zm8.25 0v7.5H24v-7.5zm2 2H22v3.5h-3.5zM0 16.5V24h7.5v-7.5zm8.25 0V24h7.498v-7.5zm8.25 0V24H24v-7.5z',\n  yarn: 'M12 0C5.375 0 0 5.375 0 12s5.375 12 12 12 12-5.375 12-12S18.625 0 12 0zm.768 4.105c.183 0 .363.053.525.157.125.083.287.185.755 1.154.31-.088.468-.042.551-.019.204.056.366.19.463.375.477.917.542 2.553.334 3.605-.241 1.232-.755 2.029-1.131 2.576.324.329.778.899 1.117 1.825.278.774.31 1.478.273 2.015a5.51 5.51 0 0 0 .602-.329c.593-.366 1.487-.917 2.553-.931.714-.009 1.269.445 1.353 1.103a1.23 1.23 0 0 1-.945 1.362c-.649.158-.95.278-1.821.843-1.232.797-2.539 1.242-3.012 1.39a1.686 1.686 0 0 1-.704.343c-.737.181-3.266.315-3.466.315h-.046c-.783 0-1.214-.241-1.45-.491-.658.329-1.51.19-2.122-.134a1.078 1.078 0 0 1-.58-1.153 1.243 1.243 0 0 1-.153-.195c-.162-.25-.528-.936-.454-1.946.056-.723.556-1.367.88-1.71a5.522 5.522 0 0 1 .408-2.256c.306-.727.885-1.348 1.32-1.737-.32-.537-.644-1.367-.329-2.21.227-.602.412-.936.82-1.08h-.005c.199-.074.389-.153.486-.259a3.418 3.418 0 0 1 2.298-1.103c.037-.093.079-.185.125-.283.31-.658.639-1.029 1.024-1.168a.94.94 0 0 1 .328-.06zm.006.7c-.507.016-1.001 1.519-1.001 1.519s-1.27-.204-2.266.871c-.199.218-.468.334-.746.44-.079.028-.176.023-.417.672-.371.991.625 2.094.625 2.094s-1.186.839-1.626 1.881c-.486 1.144-.338 2.261-.338 2.261s-.843.732-.899 1.487c-.051.663.139 1.2.343 1.515.227.343.51.176.51.176s-.561.653-.037.931c.477.25 1.283.394 1.71-.037.31-.31.371-1.001.486-1.283.028-.065.12.111.209.199.097.093.264.195.264.195s-.755.324-.445 1.066c.102.246.468.403 1.066.398.222-.005 2.664-.139 3.313-.296.375-.088.505-.283.505-.283s1.566-.431 2.998-1.357c.917-.598 1.293-.76 2.034-.936.612-.148.57-1.098-.241-1.084-.839.009-1.575.44-2.196.825-1.163.718-1.742.672-1.742.672l-.018-.032c-.079-.13.371-1.293-.134-2.678-.547-1.515-1.413-1.881-1.344-1.997.297-.5 1.038-1.297 1.334-2.78.176-.899.13-2.377-.269-3.151-.074-.144-.732.241-.732.241s-.616-1.371-.788-1.483a.271.271 0 0 0-.157-.046z',\n  bun: 'M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z',\n} as const\n\n/* ────────────────────────────────────────────────────────────────────────── */\n/* Shared package-manager preference (all blocks on the page, all tabs)      */\n/* ────────────────────────────────────────────────────────────────────────── */\n\n/** Every supported package manager, in default tab order. */\n// oxlint-disable-next-line react/only-export-components -- public constant; the component stays one copy-paste file\nexport const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'bun'] as const\n/** A supported package manager: `'npm' | 'pnpm' | 'yarn' | 'bun'`. */\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number]\n\n/**\n * Module-level store read through useSyncExternalStore. `origin` is the id of the\n * block that made the pick (null for storage / other tabs), used for the cascade.\n * Note: one store per module, so every synced block shares it whatever its storageKey.\n */\ntype PmState = { pm: PackageManager | null; origin: string | null }\nconst SERVER_PM: PmState = { pm: null, origin: null }\nlet pmState: PmState = SERVER_PM\nconst pmSubs = new Set<() => void>()\nconst mountOrder: string[] = []\n\nconst isPm = (v: unknown): v is PackageManager => PACKAGE_MANAGERS.includes(v as PackageManager)\nconst subscribePm = (fn: () => void) => {\n  pmSubs.add(fn)\n  return () => {\n    pmSubs.delete(fn)\n  }\n}\nconst getPm = () => pmState\nconst getServerPm = () => SERVER_PM\n\nfunction setPm(pm: PackageManager, origin: string | null, key: string | null) {\n  pmState = { pm, origin }\n  if (key) {\n    try {\n      localStorage.setItem(key, pm)\n    } catch {\n      /* storage blocked */\n    }\n  }\n  pmSubs.forEach(fn => fn())\n}\n\n/* ────────────────────────────────────────────────────────────────────────── */\n/* Commands                                                                  */\n/* ────────────────────────────────────────────────────────────────────────── */\n\n/** Every UI string. Pass any subset through `labels` to translate or reword. */\nexport type InstallCommandLabels = {\n  /**\n   * Accessible name of the tab list.\n   *\n   * @default `'Package manager'` (accessible name of the tab list)\n   */\n  tabs: string\n  /**\n   * Accessible name of the copy button.\n   *\n   * @default `'Copy command'` (copy button's accessible name)\n   */\n  copy: string\n  /**\n   * Shown beside the copy button after a copy; also the button's accessible name meanwhile.\n   *\n   * @default `'Copied'` (shown beside the button after a copy, and the button's name meanwhile)\n   */\n  copied: string\n  /**\n   * Shown beside the copy button when copying fails.\n   *\n   * @default `'Copy failed'` (shown beside the button when copying fails)\n   */\n  copyFailed: string\n  /**\n   * Announced to screen readers after a copy.\n   *\n   * @default `'Copied to clipboard'` (announced)\n   */\n  copiedSpoken: string\n  /**\n   * Announced to screen readers when copying fails.\n   *\n   * @default `'Copy failed. Select the command and copy it manually.'` (announced)\n   */\n  copyFailedSpoken: string\n}\n\nconst DEFAULT_LABELS: InstallCommandLabels = {\n  tabs: 'Package manager',\n  copy: 'Copy command',\n  copied: 'Copied',\n  copyFailed: 'Copy failed',\n  copiedSpoken: 'Copied to clipboard',\n  copyFailedSpoken: 'Copy failed. Select the command and copy it manually.',\n}\n\n/** Bundler-agnostic dev check: only a literal production NODE_ENV silences warnings. */\nconst isDev = () => (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.NODE_ENV !== 'production'\n\n/**\n * Props for {@link InstallCommand}. Pick one preset (`add`, `exec`, `create`, `run`)\n * or pass exact `commands`. Precedence: `commands` > `exec` > `create` > `run` > `add`.\n */\nexport type InstallCommandProps = {\n  /**\n   * Package(s) to add: `add=\"motion\"` → npm install motion / pnpm add motion … Arrays are joined\n   * with spaces. Used when no other preset or exact command applies.\n   */\n  add?: string | string[]\n  /**\n   * With `add`: install as a dev dependency (`-D`). Ignored when `global` is set (warns in\n   * development). @default false Ignored when `global` is set, with a console warning in\n   * development.\n   *\n   * @default `false`\n   */\n  dev?: boolean\n  /**\n   * With `add`: install globally (`npm install -g`, `pnpm add -g`, `yarn global add`, `bun add\n   * -g`). @default false\n   *\n   * @default `false`\n   */\n  global?: boolean\n  /** One-off binary: `exec=\"shadcn@latest add button\"` → npx / pnpm dlx / yarn dlx / bunx */\n  exec?: string\n  /**\n   * Scaffolder: `create=\"vite@latest my-app\"` → npm create / pnpm create … Scaffolder: `<manager>\n   * create <value>`.\n   */\n  create?: string\n  /** package.json script: `run=\"dev\"` → npm run dev / pnpm dev / yarn dev / bun run dev */\n  run?: string\n  /**\n   * Exact command per manager. Wins over every preset. Line breaks are kept (e.g. a `\\` + newline\n   * continuation shows on two lines, exactly as it copies). Exact commands per manager; wins over\n   * presets. Managers left out fall back to the preset.\n   */\n  commands?: Partial<Record<PackageManager, string>>\n  /**\n   * Which tabs to show, in order. Unknown names are dropped; an empty list falls back to all four\n   * (dev warning). @default PACKAGE_MANAGERS (npm, pnpm, yarn, bun)\n   *\n   * @default `PACKAGE_MANAGERS`\n   */\n  managers?: readonly PackageManager[]\n  /**\n   * Tab selected before the user picks one (or nothing is remembered). Falls back to the first of\n   * `managers` when not listed there. @default 'npm'\n   *\n   * @default `'npm'`\n   */\n  defaultManager?: PackageManager\n  /**\n   * Share the choice with every synced block on the page, save it to `localStorage`, and follow\n   * changes from other browser tabs. Share the choice with every block on the page and remember it\n   * in localStorage (also follows other tabs). @default true\n   *\n   * @default `true`\n   */\n  sync?: boolean\n  /**\n   * localStorage key for the remembered choice when `sync` is on. @default 'install-command:pm'\n   *\n   * @default `'install-command:pm'`\n   */\n  storageKey?: string\n  /**\n   * Shown before the command, never copied. Pass `''` to hide it. At most 40% of the row: a long\n   * shell prompt ends in “…” (full text on hover). At most 40% of the row (ends in \"…\"). @default\n   * '$'\n   *\n   * @default `'$'`\n   */\n  prompt?: string\n  /** Called after a successful copy with the exact command text and the active manager. Not called when copying fails. */\n  onCopy?: (command: string, manager: PackageManager) => void\n  /**\n   * How the package-manager tabs read. `'icons'` shows only the logos (the name stays\n   * as the tab's accessible name and tooltip), for tight rows.\n   * @default 'labels'\n   */\n  tabStyle?: 'labels' | 'icons'\n  /**\n   * Override any of the UI strings (tab list name, copy button, copy feedback). Override any UI\n   * string. See [InstallCommandLabels](#installcommandlabels).\n   */\n  labels?: Partial<InstallCommandLabels>\n  /** Extra class on the root element (`.ic-root`). */\n  className?: string\n  /** Inline styles on the root element; handy for `--install-command-*` variables. */\n  style?: CSSProperties\n}\n\nfunction buildCommand(pm: PackageManager, p: InstallCommandProps): string {\n  const exact = p.commands?.[pm]\n  if (exact) return exact\n  if (p.exec) return { npm: `npx ${p.exec}`, pnpm: `pnpm dlx ${p.exec}`, yarn: `yarn dlx ${p.exec}`, bun: `bunx ${p.exec}` }[pm]\n  if (p.create) return `${pm} create ${p.create}`\n  if (p.run) return { npm: `npm run ${p.run}`, pnpm: `pnpm ${p.run}`, yarn: `yarn ${p.run}`, bun: `bun run ${p.run}` }[pm]\n  const pkgs = (Array.isArray(p.add) ? p.add : [p.add ?? '']).filter(Boolean).join(' ')\n  const d = p.dev ? ' -D' : ''\n  if (p.global) {\n    return { npm: `npm install -g ${pkgs}`, pnpm: `pnpm add -g ${pkgs}`, yarn: `yarn global add ${pkgs}`, bun: `bun add -g ${pkgs}` }[pm]\n  }\n  return { npm: `npm install${d} ${pkgs}`, pnpm: `pnpm add${d} ${pkgs}`, yarn: `yarn add${d} ${pkgs}`, bun: `bun add${d} ${pkgs}` }[pm].trim()\n}\n\ntype Token = { id: number; text: string }\nlet tokenId = 0\n\n/**\n * Words of a command, split on spaces and tabs only. A line break (with the next line's\n * indentation) is its own token, so a multi-line command (`\\` + newline) shows on the lines it\n * copies as.\n */\nconst wordsOf = (command: string) =>\n  command\n    .split(/(\\n[ \\t]*)/)\n    .flatMap(part => (part.startsWith('\\n') ? [part] : part.split(/[ \\t]+/)))\n    .filter(Boolean)\nconst isBreak = (text: string) => text.startsWith('\\n')\n\n/*\n * Letters animate one by one, split into graphemes (emoji and combining sequences stay whole).\n * Scripts that need shaping or run right to left (Arabic, Hebrew, Indic, Thai…) animate as a\n * whole word instead: splitting them would break the letterforms.\n */\nconst SHAPED = /[\\u0590-\\u08FF\\u0900-\\u0DFF\\u0E00-\\u0FFF\\u1000-\\u109F\\u1780-\\u18AF\\uA8E0-\\uA8FF\\uFB1D-\\uFDFF\\uFE70-\\uFEFF]/\nlet segmenter: { segment: (s: string) => Iterable<{ segment: string }> } | null | undefined\nfunction glyphsOf(text: string): string[] {\n  if (SHAPED.test(text)) return [text]\n  if (segmenter === undefined) {\n    try {\n      segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })\n    } catch {\n      segmenter = null\n    }\n  }\n  return segmenter ? Array.from(segmenter.segment(text), g => g.segment) : Array.from(text)\n}\n\n/** An ellipsized one-line text gets its full text as a tooltip, set on hover so short text stays quiet. */\nconst tipIfClipped = (e: { currentTarget: HTMLElement }) => {\n  const el = e.currentTarget\n  if (el.scrollWidth > el.clientWidth + 1) el.title = el.textContent ?? ''\n  else el.removeAttribute('title')\n}\n\n/** Keep identity for words shared between commands (LCS), so they glide instead of re-rendering. */\nfunction diffTokens(prev: Token[], words: string[]): Token[] {\n  const a = prev.map(t => t.text)\n  const m = a.length\n  const n = words.length\n  const dp = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0))\n  for (let i = m - 1; i >= 0; i--)\n    for (let j = n - 1; j >= 0; j--) dp[i][j] = a[i] === words[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1])\n  const out: Token[] = []\n  let i = 0\n  let j = 0\n  while (j < n) {\n    if (i < m && a[i] === words[j]) {\n      out.push(prev[i])\n      i++\n      j++\n    } else if (i < m && dp[i + 1][j] >= dp[i][j + 1]) i++\n    else {\n      out.push({ id: ++tokenId, text: words[j] })\n      j++\n    }\n  }\n  return out\n}\n\n/** Async Clipboard API first; falls back to a hidden textarea + execCommand for insecure contexts. Resolves false on failure. */\nasync function writeClipboard(text: string) {\n  try {\n    await navigator.clipboard.writeText(text)\n    return true\n  } catch {\n    try {\n      const ta = document.createElement('textarea')\n      ta.value = text\n      ta.setAttribute('readonly', '')\n      ta.style.cssText = 'position:fixed;opacity:0;pointer-events:none'\n      document.body.appendChild(ta)\n      ta.select()\n      const ok = document.execCommand('copy')\n      ta.remove()\n      return ok\n    } catch {\n      return false\n    }\n  }\n}\n\n/* ────────────────────────────────────────────────────────────────────────── */\n/* Motion                                                                    */\n/* ────────────────────────────────────────────────────────────────────────── */\n\nconst EASE_OUT = [0.23, 1, 0.32, 1] as const\ntype LetterCustom = { i: number; d: number; reduced: boolean }\n\n/* a long command rolls its first characters letter by letter and fades the rest in whole words;\n   so does any very long token (a URL, a scoped canary version) */\nconst LETTER_BUDGET = 48\nconst far = (tokens: readonly { text: string }[], ti: number) => {\n  if (tokens[ti].text.length > 32) return true\n  let n = 0\n  for (let k = 0; k < ti; k++) n += tokens[k].text.length + 1\n  return n > LETTER_BUDGET\n}\nconst word: Variants = {\n  in: { opacity: 0 },\n  show: ({ d, reduced }: LetterCustom) => ({ opacity: 1, transition: { duration: reduced ? 0.15 : 0.3, ease: EASE_OUT, delay: d + (reduced ? 0 : 0.2) } }),\n  out: ({ d }: LetterCustom) => ({ opacity: 0, transition: { duration: 0.12, ease: EASE_OUT, delay: d } }),\n}\n\nconst letter: Variants = {\n  in: ({ reduced }: LetterCustom) => (reduced ? { opacity: 0 } : { y: '105%', opacity: 0 }),\n  show: ({ i, d, reduced }: LetterCustom) => ({\n    y: 0,\n    opacity: 1,\n    transition: reduced ? { duration: 0.15, delay: d } : { type: 'spring', visualDuration: 0.32, bounce: 0.22, delay: d + Math.min(i * 0.016, 0.5) },\n  }),\n  out: ({ i, d, reduced }: LetterCustom) =>\n    reduced\n      ? { opacity: 0, transition: { duration: 0.1, delay: d } }\n      : { y: '-105%', opacity: 0, transition: { duration: 0.16, ease: EASE_OUT, delay: d + Math.min(i * 0.008, 0.25) } },\n}\n\n/* ────────────────────────────────────────────────────────────────────────── */\n/* Component                                                                 */\n/* ────────────────────────────────────────────────────────────────────────── */\n\n/*\n * Reduced-motion preference, hydration-safe: the server and the hydrating render\n * both read `false`, then React re-renders with the real value. motion's\n * useReducedMotion reads matchMedia on the first client render, which made the\n * server HTML and the first client render disagree for reduced-motion users.\n */\nconst REDUCED_QUERY = '(prefers-reduced-motion: reduce)'\nfunction subscribeReduced(onChange: () => void) {\n  const mq = window.matchMedia(REDUCED_QUERY)\n  mq.addEventListener('change', onChange)\n  return () => mq.removeEventListener('change', onChange)\n}\nconst getReduced = () => window.matchMedia(REDUCED_QUERY).matches\nconst getServerReduced = () => false\n\n/**\n * A package-manager install block with npm / pnpm / yarn / bun tabs and a copy button.\n * Only the words that differ animate when switching; synced blocks follow in a cascade.\n */\nexport function InstallCommand(props: InstallCommandProps) {\n  const {\n    managers: managersProp = PACKAGE_MANAGERS,\n    defaultManager = 'npm',\n    sync = true,\n    storageKey = 'install-command:pm',\n    prompt = '$',\n    onCopy,\n    labels: labelsProp,\n    tabStyle = 'labels',\n    className,\n    style,\n  } = props\n  const labels = { ...DEFAULT_LABELS, ...labelsProp }\n  // unknown names are dropped; an empty list falls back to every manager (dev warning below)\n  const validManagers = managersProp.filter(isPm)\n  const managers: readonly PackageManager[] = validManagers.length ? validManagers : PACKAGE_MANAGERS\n  const reduced = useSyncExternalStore(subscribeReduced, getReduced, getServerReduced)\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, '')\n  const shared = useSyncExternalStore(subscribePm, getPm, getServerPm)\n  const [local, setLocal] = useState<PackageManager>(defaultManager)\n\n  const fallback = managers.includes(defaultManager) ? defaultManager : managers[0]\n  const pm: PackageManager = sync\n    ? shared.pm && managers.includes(shared.pm)\n      ? shared.pm\n      : fallback\n    : managers.includes(local)\n      ? local\n      : fallback\n\n  const devIgnored = Boolean(props.dev && props.global)\n  useEffect(() => {\n    if (devIgnored && isDev()) console.warn('InstallCommand: `dev` is ignored when `global` is set; global installs have no dev flag.')\n  }, [devIgnored])\n  const noManagers = !validManagers.length\n  useEffect(() => {\n    if (noManagers && isDev()) console.warn('InstallCommand: `managers` has no known package manager; showing npm, pnpm, yarn and bun.')\n  }, [noManagers])\n  // a preset that is set but blank renders a bare `npm install` / `npm run`: say so in development\n  const blankPreset = !props.commands && !props.exec && !props.create\n    ? props.run != null\n      ? !props.run.trim() && 'run'\n      : props.add != null && !(Array.isArray(props.add) ? props.add : [props.add]).some(a => a.trim()) && 'add'\n    : (props.exec != null && !props.exec.trim() && 'exec') || (props.create != null && !props.create.trim() && 'create')\n  useEffect(() => {\n    if (blankPreset && isDev()) console.warn(`InstallCommand: \\`${blankPreset}\\` is empty, so the block shows the bare manager command.`)\n  }, [blankPreset])\n\n  const [copied, setCopied] = useState(false)\n  const [failed, setFailed] = useState(false)\n  const [sweep, setSweep] = useState(0)\n  const [overflow, setOverflow] = useState(false)\n  const tabRefs = useRef<Partial<Record<PackageManager, HTMLButtonElement | null>>>({})\n  const listRef = useRef<HTMLDivElement>(null)\n  const lineRef = useRef<HTMLSpanElement>(null)\n  const copyTimer = useRef(0)\n  const measured = useRef(false)\n  const pmRef = useRef(pm)\n  useLayoutEffect(() => {\n    pmRef.current = pm\n  }, [pm])\n\n  /* cascade: blocks further from the one you clicked follow a beat later */\n  useEffect(() => {\n    mountOrder.push(uid)\n    return () => {\n      const i = mountOrder.indexOf(uid)\n      if (i >= 0) mountOrder.splice(i, 1)\n    }\n  }, [uid])\n  // 70ms per block of mount-order distance from the block that was clicked\n  const delay =\n    sync && shared.origin && shared.origin !== uid && !reduced\n      ? Math.abs(mountOrder.indexOf(uid) - mountOrder.indexOf(shared.origin)) * 0.07\n      : 0\n\n  /* read the remembered choice once, and follow other tabs */\n  useEffect(() => {\n    if (!sync) return\n    if (!pmState.pm) {\n      try {\n        const saved = localStorage.getItem(storageKey)\n        if (isPm(saved)) setPm(saved, null, null)\n      } catch {\n        /* storage blocked */\n      }\n    }\n    const onStorage = (e: StorageEvent) => {\n      if (e.key === storageKey && isPm(e.newValue)) setPm(e.newValue, null, null)\n    }\n    window.addEventListener('storage', onStorage)\n    return () => window.removeEventListener('storage', onStorage)\n  }, [sync, storageKey])\n\n  useEffect(() => () => clearTimeout(copyTimer.current), [])\n\n  const command = buildCommand(pm, props)\n  // diff against the previous command's tokens, derived during render (no ref reads)\n  const [diffed, setDiffed] = useState(() => ({ command, tokens: diffTokens([], wordsOf(command)) }))\n  let tokens = diffed.tokens\n  if (diffed.command !== command) {\n    tokens = diffTokens(diffed.tokens, wordsOf(command))\n    setDiffed({ command, tokens })\n  }\n\n  /* tab pill: leading edge springs fast, trailing edge lags, so it stretches */\n  const edgeL = useMotionValue(0)\n  const edgeR = useMotionValue(0)\n  // Width is its own value, synced in an effect: a derived value created at\n  // render misses edges set in the layout effect on remount and stays 0.\n  const pillW = useMotionValue(0)\n  const pillX = useMotionValue(0)\n  useEffect(() => {\n    // the springs overshoot past the first and last tab; the row scrolls (it clips), so the pill is held\n    // inside the tabs' own span and its ring is never cut off at either end\n    const sync = () => {\n      const tabs = Object.values(tabRefs.current).filter(Boolean) as HTMLElement[]\n      const min = tabs.length ? Math.min(...tabs.map((t) => t.offsetLeft)) : -Infinity\n      const max = tabs.length ? Math.max(...tabs.map((t) => t.offsetLeft + t.offsetWidth)) : Infinity\n      const l = Math.max(min, edgeL.get()), r = Math.min(max, edgeR.get())\n      pillX.set(l)\n      pillW.set(Math.max(0, r - l))\n    }\n    sync()\n    const offL = edgeL.on('change', sync)\n    const offR = edgeR.on('change', sync)\n    return () => {\n      offL()\n      offR()\n    }\n  }, [edgeL, edgeR, pillW, pillX])\n  useLayoutEffect(() => {\n    const el = tabRefs.current[pm]\n    if (!el) return\n    const L = el.offsetLeft\n    const R = L + el.offsetWidth\n    if (!measured.current || reduced) {\n      measured.current = true\n      edgeL.set(L)\n      edgeR.set(R)\n      return\n    }\n    // already there (a re-render, a remount): nothing to animate. Setting the edges on mount reads\n    // as a huge speed to motion, so a spring started now would overshoot onto the next tab and back.\n    if (Math.abs(L - edgeL.get()) < 0.5 && Math.abs(R - edgeR.get()) < 0.5) return\n    const right = L > edgeL.get()\n    const lead = { type: 'spring' as const, stiffness: 620, damping: 38, delay, velocity: 0 }\n    const trail = { type: 'spring' as const, stiffness: 260, damping: 28, delay: delay + 0.03, velocity: 0 }\n    animate(edgeL, L, right ? trail : lead)\n    animate(edgeR, R, right ? lead : trail)\n  }, [pm, delay, reduced, edgeL, edgeR])\n\n  /* re-measure on resize (fonts loading, container width); one observer, reads the live pick */\n  useEffect(() => {\n    const list = listRef.current\n    if (!list) return\n    const ro = new ResizeObserver(() => {\n      const el = tabRefs.current[pmRef.current]\n      if (!el) return\n      edgeL.set(el.offsetLeft)\n      edgeR.set(el.offsetLeft + el.offsetWidth)\n    })\n    ro.observe(list)\n    return () => ro.disconnect()\n  }, [edgeL, edgeR])\n\n  /* fade the right edge only when the command overflows */\n  useEffect(() => {\n    const el = lineRef.current\n    if (!el) return\n    const check = () => setOverflow(el.scrollWidth > el.clientWidth + 1)\n    check()\n    const ro = new ResizeObserver(check)\n    ro.observe(el)\n    return () => ro.disconnect()\n  }, [command])\n\n  const choose = (next: PackageManager) => {\n    if (next === pm) return\n    if (sync) setPm(next, uid, storageKey)\n    else setLocal(next)\n  }\n\n  const onTabsKey = (e: KeyboardEvent<HTMLDivElement>) => {\n    const i = managers.indexOf(pm)\n    let next = -1\n    if (e.key === 'ArrowRight') next = (i + 1) % managers.length\n    else if (e.key === 'ArrowLeft') next = (i - 1 + managers.length) % managers.length\n    else if (e.key === 'Home') next = 0\n    else if (e.key === 'End') next = managers.length - 1\n    if (next < 0) return\n    e.preventDefault()\n    choose(managers[next])\n    tabRefs.current[managers[next]]?.focus()\n  }\n\n  const copy = async () => {\n    const ok = await writeClipboard(command)\n    clearTimeout(copyTimer.current)\n    if (!ok) {\n      setCopied(false)\n      setFailed(true)\n      copyTimer.current = window.setTimeout(() => setFailed(false), 2600)\n      return\n    }\n    setFailed(false)\n    setCopied(true)\n    setSweep(s => s + 1)\n    onCopy?.(command, pm)\n    copyTimer.current = window.setTimeout(() => setCopied(false), 1700)\n  }\n\n  // ⌘/Ctrl+C copies the command, unless the user has a text selection to copy themselves\n  const onPanelKey = (e: KeyboardEvent<HTMLDivElement>) => {\n    if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'c' && !window.getSelection()?.toString()) {\n      e.preventDefault()\n      copy()\n    }\n  }\n\n  const panelId = `ic-${uid}-panel`\n\n  return (\n    <div className={['ic-root', className].filter(Boolean).join(' ')} style={style}>\n      <InstallCommandStyles />\n      <div className=\"ic-head\">\n        <div ref={listRef} className=\"ic-tabs\" role=\"tablist\" aria-label={labels.tabs} onKeyDown={onTabsKey}>\n          <motion.span className=\"ic-pill\" aria-hidden style={{ x: pillX, width: pillW }} />\n          {managers.map(m => (\n            <motion.button\n              key={m}\n              ref={el => {\n                tabRefs.current[m] = el\n              }}\n              id={`ic-${uid}-tab-${m}`}\n              type=\"button\"\n              role=\"tab\"\n              aria-selected={m === pm}\n              aria-controls={panelId}\n              tabIndex={m === pm ? 0 : -1}\n              className=\"ic-tab\"\n              data-icon-only={tabStyle === 'icons' || undefined}\n              aria-label={tabStyle === 'icons' ? m : undefined}\n              title={tabStyle === 'icons' ? m : undefined}\n              onClick={() => choose(m)}\n              whileTap={reduced ? undefined : { scale: 0.94 }}\n            >\n              <svg className=\"ic-logo\" viewBox=\"0 0 24 24\" aria-hidden>\n                <path d={BRAND_PATHS[m]} />\n              </svg>\n              {tabStyle === 'icons' ? null : m}\n            </motion.button>\n          ))}\n        </div>\n\n        <div className=\"ic-copy-wrap\">\n          {/* label lives outside the button's box, so nothing reflows on copy */}\n          <motion.span\n            className=\"ic-copy-label\"\n            aria-hidden\n            initial={false}\n            data-failed={failed || undefined}\n            animate={copied || failed ? { opacity: 1, x: 0 } : { opacity: 0, x: reduced ? 0 : 6 }}\n            transition={{ duration: 0.2, ease: EASE_OUT }}\n          >\n            {failed ? labels.copyFailed : labels.copied}\n          </motion.span>\n          <motion.button\n            type=\"button\"\n            className=\"ic-copy\"\n            data-copied={copied || undefined}\n            onClick={copy}\n            aria-label={copied ? labels.copied : labels.copy}\n            whileTap={reduced ? undefined : { scale: 0.9 }}\n          >\n            <motion.span\n              className=\"ic-copy-icon\"\n              aria-hidden\n              initial={false}\n              animate={copied ? { opacity: 0, scale: 0.5, rotate: reduced ? 0 : 20 } : { opacity: 1, scale: 1, rotate: 0 }}\n              transition={{ type: 'spring', visualDuration: 0.26, bounce: 0.3 }}\n            >\n              <UiIcon name=\"copy\" size={16} />\n            </motion.span>\n            <motion.span\n              className=\"ic-copy-check\"\n              aria-hidden\n              initial={false}\n              animate={copied ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 0.5 }}\n              transition={{ type: 'spring', visualDuration: 0.26, bounce: 0.4 }}\n            >\n              {/* a left-to-right wipe follows the stroke, so the check still draws in */}\n              <motion.span\n                className=\"ic-copy-check-draw\"\n                initial={false}\n                animate={{ clipPath: copied || reduced ? 'inset(0 0% 0 0)' : 'inset(0 100% 0 0)' }}\n                transition={{ duration: copied ? 0.28 : 0, ease: EASE_OUT, delay: copied ? 0.05 : 0 }}\n              >\n                <UiIcon name=\"check\" size={16} />\n              </motion.span>\n            </motion.span>\n          </motion.button>\n        </div>\n      </div>\n\n      <div\n        id={panelId}\n        className=\"ic-panel\"\n        role=\"tabpanel\"\n        aria-labelledby={`ic-${uid}-tab-${pm}`}\n        tabIndex={0}\n        onKeyDown={onPanelKey}\n      >\n        {prompt && (\n          <span className=\"ic-prompt\" aria-hidden onPointerEnter={tipIfClipped}>\n            {prompt}\n          </span>\n        )}\n        {/* what assistive tech reads: the whole command, once, as plain text */}\n        <span className=\"ic-sr ic-sr-cmd\">{command}</span>\n        <span ref={lineRef} className=\"ic-line\" data-overflow={overflow || undefined}>\n          <code className=\"ic-code\" aria-hidden>\n            <AnimatePresence mode=\"popLayout\" initial={false}>\n              {tokens.map((t, ti, all) =>\n                isBreak(t.text) ? (\n                  // a plain line break (and the next line's indent) inside the pre-formatted code\n                  <span key={t.id} className=\"ic-br\">{t.text}</span>\n                ) : (\n                <motion.span\n                  key={t.id}\n                  layout={reduced ? false : 'position'}\n                  className=\"ic-tok\"\n                  initial=\"in\"\n                  animate=\"show\"\n                  exit=\"out\"\n                  // letters roll one by one near the start; past LETTER_BUDGET characters a token fades as one word\n                  variants={far(all, ti) ? word : undefined}\n                  custom={{ i: 0, d: delay, reduced }}\n                  transition={{ layout: { type: 'spring', visualDuration: 0.4, bounce: 0.18, delay } }}\n                >\n                  {far(all, ti)\n                    ? t.text + (ti < tokens.length - 1 && !isBreak(tokens[ti + 1].text) ? ' ' : '')\n                    : glyphsOf(t.text + (ti < tokens.length - 1 && !isBreak(tokens[ti + 1].text) ? ' ' : '')).map((ch, i) => (\n                        <motion.span key={i} className=\"ic-ch\" variants={letter} custom={{ i, d: delay, reduced }}>\n                          {ch}\n                        </motion.span>\n                      ))}\n                </motion.span>\n                ),\n              )}\n            </AnimatePresence>\n            <AnimatePresence>\n              {sweep > 0 && copied && !reduced && (\n                <motion.span\n                  key={sweep}\n                  className=\"ic-sweep\"\n                  aria-hidden\n                  initial={{ scaleX: 0, opacity: 1 }}\n                  animate={{ scaleX: 1, opacity: [1, 1, 0] }}\n                  transition={{ scaleX: { duration: 0.32, ease: EASE_OUT }, opacity: { duration: 0.9, times: [0, 0.45, 1] } }}\n                />\n              )}\n            </AnimatePresence>\n          </code>\n        </span>\n      </div>\n\n      <span className=\"ic-sr\" role=\"status\" aria-live=\"polite\">\n        {copied ? labels.copiedSpoken : failed ? labels.copyFailedSpoken : ''}\n      </span>\n    </div>\n  )\n}\n\n/* ────────────────────────────────────────────────────────────────────────── */\n/* Styles — injected once, hoisted by React 19                               */\n/* ────────────────────────────────────────────────────────────────────────── */\n\nfunction InstallCommandStyles() {\n  return (\n    <style href=\"install-command\" precedence=\"medium\">\n      {CSS}\n    </style>\n  )\n}\n\nconst CSS = /* css */ `\n.ic-root {\n  --_ink: var(--install-command-ink, var(--ui-ink, #17140f));\n  --_radius: var(--install-command-radius, calc(var(--ui-radius, 16px) * 0.875));\n  --_radius-sm: var(--ui-radius-sm, 8px);\n  --_surface: var(--install-command-surface, var(--ui-surface, #fbf8f1));\n  --_accent: var(--install-command-accent, var(--ui-accent, #d4452b));\n  --_font: var(--install-command-font, var(--ui-font-mono, ui-monospace, 'SF Mono', Menlo, Consolas, monospace));\n  --_line: color-mix(in oklab, var(--_ink) 13%, transparent);\n  --_muted: color-mix(in oklab, var(--_ink) 52%, transparent);\n  position: relative;\n  width: 100%;\n  border: 1px solid var(--_line);\n  border-radius: var(--_radius);\n  background: var(--_surface);\n  color: var(--_ink);\n  overflow: hidden;\n  -webkit-tap-highlight-color: transparent;\n}\n.ic-sr {\n  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;\n  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;\n}\n.ic-head {\n  display: flex; align-items: center; justify-content: space-between; gap: 8px;\n  padding: 5px 5px 5px 6px;\n  background: color-mix(in oklab, var(--_ink) 3.5%, var(--_surface));\n  border-bottom: 1px solid var(--_line);\n}\n/* narrow blocks: the tab row scrolls instead of pushing the copy button off the card */\n.ic-tabs { position: relative; display: flex; gap: 2px; min-width: 0; padding-inline: 1px; overflow-x: auto; scrollbar-width: none; }\n/* (the 1px each side: a fractional row width otherwise cuts the end tabs' pill ring by a hair) */\n.ic-tabs::-webkit-scrollbar { display: none; }\n.ic-pill {\n  position: absolute; top: 0; left: 0; height: 100%; border-radius: var(--_radius-sm);\n  background: var(--_surface);\n  /* the ring is drawn inside the pill: the tab row scrolls sideways, so it clips anything outside */\n  box-shadow: inset 0 0 0 1px var(--_line), 0 1px 2px light-dark(rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.03));\n}\n.ic-root .ic-tab {\n  position: relative; display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; margin: 0; border: 0; border-radius: var(--_radius-sm);\n  background: none; cursor: pointer;\n  font-family: var(--_font); font-size: 12.5px; font-weight: 500; line-height: 1;\n  color: var(--_muted); transition: color 150ms ease;\n}\n.ic-root .ic-tab:hover, .ic-root .ic-tab[aria-selected=\"true\"] { color: var(--_ink); }\n.ic-root .ic-tab[data-icon-only] { width: 30px; padding: 0; justify-content: center; }\n.ic-root .ic-tab[data-icon-only] .ic-logo { width: 14px; height: 14px; }\n.ic-logo { width: 12px; height: 12px; flex: none; fill: currentColor; opacity: 0.8; }\n.ic-tab[aria-selected=\"true\"] .ic-logo { opacity: 1; }\n.ic-tab:focus-visible, .ic-copy:focus-visible { outline: 2px solid var(--_accent); outline-offset: 1px; }\n\n/* takes the free space between the tabs and the button, so the copy label can measure it */\n.ic-copy-wrap { position: relative; flex: 1 0 28px; min-width: 28px; display: flex; justify-content: flex-end; }\n.ic-copy-label {\n  position: absolute; right: 30px; top: 50%; margin-top: -9px;\n  /* the free space, or at least ~10 characters (may cover the last tab); longer ends in \"…\" */\n  max-width: max(100% - 30px, 10ch); overflow: hidden; text-overflow: ellipsis;\n  font-size: 12.5px; line-height: 18px; color: var(--_ink); white-space: nowrap; pointer-events: none;\n  /* on narrow blocks it can sit over the last tab: cover it cleanly instead of overlapping text */\n  padding-left: 8px; background: color-mix(in oklab, var(--_ink) 3.5%, var(--_surface));\n}\n.ic-root .ic-copy {\n  position: relative; display: grid; place-items: center; width: 28px; height: 28px; padding: 0; margin: 0;\n  border: 0; border-radius: var(--_radius-sm); background: none; cursor: pointer; color: var(--_muted);\n  transition: color 150ms ease, background-color 150ms ease;\n}\n.ic-root .ic-copy:hover { color: var(--_ink); background: color-mix(in oklab, var(--_ink) 6%, transparent); }\n.ic-root .ic-copy[data-copied] { color: var(--_ink); }\n.ic-copy-label[data-failed] { color: var(--install-command-danger, var(--ui-danger, #b42318)); }\n/* touch: 44px tabs and copy button */\n@media (pointer: coarse) {\n  .ic-root :is(.ic-tab, .ic-copy) { height: 44px; }\n  .ic-root .ic-copy { width: 44px; }\n  .ic-copy-wrap { flex-basis: 44px; min-width: 44px; }\n  .ic-copy-label { right: 46px; }\n}\n.ic-copy-icon, .ic-copy-check, .ic-copy-check-draw { grid-area: 1 / 1; display: grid; place-items: center; }\n.ic-copy-icon svg, .ic-copy-check svg { fill: none; }\n.ic-sr-cmd { user-select: none; -webkit-user-select: none; }\n\n.ic-panel {\n  /* top, not baseline: a multi-line command's baseline is its last line, the prompt belongs on the first */\n  display: flex; align-items: flex-start; gap: 10px;\n  padding: 14px 16px; outline: none;\n  font-family: var(--_font); font-size: 13.5px; line-height: 20px;\n}\n.ic-panel:focus-visible { box-shadow: inset 0 0 0 2px color-mix(in oklab, var(--_accent) 55%, transparent); }\n/* one line, never more than 40% of the row: a long shell prompt ends in \"…\" (full text on hover) */\n.ic-prompt {\n  flex: 0 1 auto; min-width: 0; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n  color: color-mix(in oklab, var(--_ink) 64%, transparent); user-select: none; -webkit-user-select: none;\n}\n.ic-line {\n  position: relative; flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: none;\n}\n.ic-line::-webkit-scrollbar { display: none; }\n/* a long command scrolls sideways: show a thin scrollbar while hovered or focused so it's discoverable */\n.ic-line[data-overflow]:hover, .ic-panel:focus-visible .ic-line[data-overflow] { scrollbar-width: thin; }\n.ic-line[data-overflow]:hover::-webkit-scrollbar, .ic-panel:focus-visible .ic-line[data-overflow]::-webkit-scrollbar { display: block; height: 4px; }\n.ic-line[data-overflow] { mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent); }\n.ic-root .ic-code {\n  /* inline, not flex: flex children serialise with newlines when selected */\n  position: relative; display: inline-block; white-space: pre; padding: 0; margin: 0;\n  background: none; border: 0; font: inherit; color: inherit;\n  user-select: all; -webkit-user-select: all;\n}\n.ic-tok { position: relative; display: inline-block; vertical-align: top; overflow: hidden; }\n.ic-ch { display: inline-block; vertical-align: top; white-space: pre; }\n.ic-sweep {\n  position: absolute; inset: -1px -5px; border-radius: calc(var(--_radius-sm) * 0.625); pointer-events: none;\n  transform-origin: 0 50%;\n  background: color-mix(in oklab, var(--_accent) 16%, transparent);\n}\n/* touch: 44px targets (WCAG AAA) where they fit; visual sizes on mouse and trackpad are unchanged */\n@media (pointer: coarse) {\n  .ic-root .ic-tab { min-inline-size: 44px; }\n}\n`\n\nexport default InstallCommand\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "tier": "free",
    "version": "1.0.0",
    "docs": "https://brioui.com/docs/components/install-command",
    "license": "MIT"
  },
  "docs": "Docs: https://brioui.com/docs/components/install-command",
  "categories": [
    "Text"
  ],
  "type": "registry:ui"
}