{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dot-matrix",
  "type": "registry:ui",
  "title": "Dot Matrix",
  "description": "5x5 dot matrix indicator with state specific blink patterns.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/dot-matrix.tsx",
      "sourcePath": "packages/ui/src/components/react/ui/radix/dot-matrix.tsx",
      "content": "import type { ComponentProps, CSSProperties } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst GRID = 5;\nconst CENTER = (GRID - 1) / 2;\nconst DOT_INDEXES = Array.from({ length: GRID * GRID }, (_, i) => i);\n\n/* Deterministic bit-mixing hash so server and client render identical markup; takes a range in milliseconds and returns seconds. A plain (i * prime) % range correlates indexes a grid-stride apart and renders as column-synchronized waves instead of a twinkle. */\nconst hash = (n: number, salt: number, range: number) => {\n  let h = (Math.imul(n, 374761393) + Math.imul(salt, 668265263)) >>> 0;\n  h = Math.imul(h ^ (h >>> 13), 1274126177) >>> 0;\n  return ((h ^ (h >>> 16)) % range) / 1000;\n};\n\nconst glyph = (dots: [number, number][]) =>\n  new Set(dots.map(([row, col]) => row * GRID + col));\n\nconst CHECK = glyph([\n  [1, 4],\n  [2, 3],\n  [3, 0],\n  [3, 2],\n  [4, 1],\n]);\nconst CROSS = glyph([\n  [0, 0],\n  [0, 4],\n  [1, 1],\n  [1, 3],\n  [2, 2],\n  [3, 1],\n  [3, 3],\n  [4, 0],\n  [4, 4],\n]);\nconst BANG = glyph([\n  [0, 2],\n  [1, 2],\n  [2, 2],\n  [4, 2],\n]);\nconst INFO = glyph([\n  [0, 2],\n  [2, 2],\n  [3, 2],\n  [4, 2],\n]);\nconst PAUSE = glyph([\n  [1, 1],\n  [2, 1],\n  [3, 1],\n  [1, 3],\n  [2, 3],\n  [3, 3],\n]);\nconst STOP = glyph([\n  [1, 1],\n  [1, 2],\n  [1, 3],\n  [2, 1],\n  [2, 2],\n  [2, 3],\n  [3, 1],\n  [3, 2],\n  [3, 3],\n]);\nconst RECORD = glyph([\n  [1, 2],\n  [2, 1],\n  [2, 2],\n  [2, 3],\n  [3, 2],\n]);\nconst ELLIPSIS = glyph([\n  [2, 0],\n  [2, 2],\n  [2, 4],\n]);\n\ntype Blink = { duration: number; delay: number; lo: number };\n\ntype StateConfig = {\n  /** Text color class; dots inherit the surrounding color when omitted. */\n  color?: string;\n  /** Dots that render at full opacity; all others rest at `dim`. Omit for the full grid. */\n  glyph?: Set<number>;\n  /** Resting opacity of on dots. */\n  base?: number;\n  /** Resting opacity of off dots when a glyph is set. */\n  dim?: number;\n  /** Blink parameters per on dot, keyed by index and grid position. */\n  blink?: (i: number, row: number, col: number) => Blink;\n};\n\nconst STATES = {\n  idle: { color: \"text-muted-foreground\", base: 0.3 },\n  loading: {\n    blink: (i) => ({\n      duration: 0.9 + hash(i, 2, 700),\n      delay: -hash(i, 1, 1200),\n      lo: 0.15,\n    }),\n  },\n  thinking: {\n    blink: (_i, row, col) => ({\n      duration: 1.2,\n      delay: -(row + col) * 0.09,\n      lo: 0.2,\n    }),\n  },\n  streaming: {\n    blink: (_i, row, col) => ({\n      duration: 0.9,\n      delay: -(row * 0.12 + hash(col, 3, 900)),\n      lo: 0.15,\n    }),\n  },\n  searching: {\n    blink: (_i, _row, col) => ({ duration: 1.1, delay: -col * 0.12, lo: 0.2 }),\n  },\n  syncing: {\n    blink: (_i, row, col) => {\n      const turn =\n        (Math.atan2(row - CENTER, col - CENTER) + Math.PI) / (2 * Math.PI);\n      return { duration: 1.3, delay: -turn * 1.3, lo: 0.2 };\n    },\n  },\n  connecting: {\n    blink: (_i, row, col) => ({\n      duration: 1.4,\n      delay: -Math.max(Math.abs(row - CENTER), Math.abs(col - CENTER)) * 0.18,\n      lo: 0.15,\n    }),\n  },\n  waiting: {\n    glyph: ELLIPSIS,\n    blink: (_i, _row, col) => ({\n      duration: 1.2,\n      delay: -col * 0.09,\n      lo: 0.2,\n    }),\n  },\n  uploading: {\n    blink: (_i, row) => ({\n      duration: 1,\n      delay: -(GRID - 1 - row) * 0.12,\n      lo: 0.2,\n    }),\n  },\n  downloading: {\n    blink: (_i, row) => ({ duration: 1, delay: -row * 0.12, lo: 0.2 }),\n  },\n  listening: {\n    blink: (_i, _row, col) => ({\n      duration: 0.7 + hash(col, 4, 500),\n      delay: -hash(col, 5, 900),\n      lo: 0.25,\n    }),\n  },\n  speaking: {\n    blink: (_i, _row, col) => ({\n      duration: 0.4 + hash(col, 6, 350),\n      delay: -hash(col, 7, 700),\n      lo: 0.2,\n    }),\n  },\n  recording: {\n    color: \"text-red-500\",\n    glyph: RECORD,\n    dim: 0.12,\n    blink: () => ({ duration: 1.4, delay: 0, lo: 0.3 }),\n  },\n  success: { color: \"text-emerald-500\", glyph: CHECK },\n  error: {\n    color: \"text-red-500\",\n    glyph: CROSS,\n    blink: () => ({ duration: 1.1, delay: 0, lo: 0.4 }),\n  },\n  warning: {\n    color: \"text-amber-500\",\n    glyph: BANG,\n    blink: () => ({ duration: 1.6, delay: 0, lo: 0.45 }),\n  },\n  info: { color: \"text-blue-500\", glyph: INFO },\n  paused: { color: \"text-muted-foreground\", glyph: PAUSE },\n  stopped: { color: \"text-muted-foreground\", glyph: STOP },\n  offline: { color: \"text-muted-foreground\", base: 0.15 },\n} satisfies Record<string, StateConfig>;\n\nexport type DotMatrixState = keyof typeof STATES;\n\nconst dotMatrixStates = Object.keys(STATES) as readonly DotMatrixState[];\n\nexport type DotMatrixProps = Omit<ComponentProps<\"span\">, \"children\"> & {\n  state?: DotMatrixState;\n  label?: string;\n};\n\n/* The blink animation runs on every dot in every state (static states set hi = lo) and the registered hi/lo custom properties carry a transition, because removing or adding an animation never triggers a CSS transition on the animated property itself; transitioning the amplitude bounds is what makes state changes cross-fade. */\nconst DOT_MATRIX_CSS =\n  '@property --aui-dot-matrix-hi{syntax:\"<number>\";inherits:false;initial-value:1}@property --aui-dot-matrix-lo{syntax:\"<number>\";inherits:false;initial-value:0.15}@keyframes aui-dot-matrix-blink{0%,100%{opacity:var(--aui-dot-matrix-hi,1)}50%{opacity:var(--aui-dot-matrix-lo,0.15)}}';\n\n/**\n * Tiny 5x5 dot-matrix status indicator with 20 built-in states. Dots inherit the text color and animate in state-specific patterns: twinkle, waves, ripples, sweeps, equalizer columns, and check/cross/bang glyphs. State changes cross-fade per dot.\n *\n * ```tsx\n * <DotMatrix state={isRunning ? \"loading\" : \"success\"} />\n * ```\n */\nfunction DotMatrix({\n  className,\n  state = \"loading\",\n  label,\n  ...props\n}: DotMatrixProps) {\n  const config: StateConfig = STATES[state];\n  return (\n    <span\n      data-slot=\"dot-matrix\"\n      data-state={state}\n      role=\"status\"\n      className={cn(\"inline-block size-4 shrink-0\", config.color, className)}\n      {...props}\n    >\n      <span className=\"sr-only\">{label ?? state}</span>\n      {/* Hoisted and deduplicated across instances by React; must live in HTML scope, inside the SVG it would be an SVG-namespace element React does not hoist. */}\n      <style href=\"aui-dot-matrix\" precedence=\"low\">\n        {DOT_MATRIX_CSS}\n      </style>\n      <svg\n        aria-hidden\n        viewBox=\"0 0 20 20\"\n        fill=\"currentColor\"\n        className=\"size-full\"\n      >\n        {DOT_INDEXES.map((i) => {\n          const row = Math.floor(i / GRID);\n          const col = i % GRID;\n          const on = !config.glyph || config.glyph.has(i);\n          const hi = on ? (config.base ?? 1) : (config.dim ?? 0.15);\n          const blink = on ? config.blink?.(i, row, col) : undefined;\n          return (\n            <circle\n              key={i}\n              data-slot=\"dot-matrix-dot\"\n              cx={2 + col * 4}\n              cy={2 + row * 4}\n              r={1.3}\n              className=\"[transition-property:--aui-dot-matrix-hi,--aui-dot-matrix-lo,opacity] duration-300 [animation-iteration-count:infinite] [animation-name:aui-dot-matrix-blink] [animation-timing-function:ease-in-out] motion-reduce:[animation-name:none]\"\n              style={\n                {\n                  opacity: hi,\n                  animationDuration: `${blink?.duration ?? 1}s`,\n                  animationDelay: `${blink?.delay ?? 0}s`,\n                  \"--aui-dot-matrix-hi\": hi,\n                  \"--aui-dot-matrix-lo\": blink?.lo ?? hi,\n                } as CSSProperties\n              }\n            />\n          );\n        })}\n      </svg>\n    </span>\n  );\n}\n\nexport { DotMatrix, dotMatrixStates };\n"
    }
  ]
}