{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/qr-code",
  "title": "QR Code",
  "description": "Unicode block QR code generator",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/accessibility.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/qr-code.tsx",
      "content": "import { Box, Text } from \"ink\";\nimport React from \"react\";\n\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\nimport type { VisualAccessibilityProps } from \"@/lib/accessibility\";\n\ninterface QRCodeBaseProps {\n  value: string;\n  size?: \"sm\" | \"md\" | \"lg\";\n  color?: string;\n  label?: string;\n}\n\nexport type QRCodeProps = QRCodeBaseProps & VisualAccessibilityProps;\n\nconst GF_EXP = new Uint8Array(512);\nconst GF_LOG = new Uint8Array(256);\n(function initGF() {\n  let x = 1;\n  for (let i = 0; i < 255; i += 1) {\n    GF_EXP[i] = x;\n    GF_LOG[x] = i;\n    x <<= 1;\n    if (x & 0x1_00) {\n      x ^= 0x1_1d;\n    }\n  }\n  for (let i = 255; i < 512; i += 1) {\n    GF_EXP[i] = GF_EXP[i - 255];\n  }\n})();\n\nconst gfMul = (a: number, b: number): number => {\n  if (a === 0 || b === 0) {\n    return 0;\n  }\n  return GF_EXP[(GF_LOG[a] + GF_LOG[b]) % 255] ?? 0;\n};\n\nconst gfPoly = (degree: number): Uint8Array => {\n  let p = new Uint8Array([1]);\n  for (let i = 0; i < degree; i += 1) {\n    const q = new Uint8Array(p.length + 1);\n    const alpha = GF_EXP[i];\n    for (let j = 0; j < p.length; j += 1) {\n      q[j] ^= p[j];\n      q[j + 1] ^= gfMul(p[j], alpha);\n    }\n    p = q;\n  }\n  return p;\n};\n\nconst rsEncode = (data: Uint8Array, ecCount: number): Uint8Array => {\n  const gen = gfPoly(ecCount);\n  const msg = new Uint8Array(data.length + ecCount);\n  msg.set(data);\n  for (let i = 0; i < data.length; i += 1) {\n    const coef = msg[i];\n    if (coef !== 0) {\n      for (let j = 1; j <= ecCount; j += 1) {\n        msg[i + j] ^= gfMul(gen[j], coef);\n      }\n    }\n  }\n  return msg.slice(data.length);\n};\n\nconst VERSION = 1;\nconst SIZE = 17 + VERSION * 4;\nconst DATA_CODEWORDS = 14;\nconst EC_CODEWORDS = 10;\n\nconst encodeData = (text: string): Uint8Array => {\n  const ALNUM_CHARS = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:\";\n\n  const isNumeric = /^[0-9]*$/.test(text);\n  const isAlphanumeric = [...text].every((c) => ALNUM_CHARS.includes(c));\n\n  const bits: number[] = [];\n\n  const pushBits = (value: number, count: number) => {\n    for (let i = count - 1; i >= 0; i -= 1) {\n      bits.push((value >> i) & 1);\n    }\n  };\n\n  if (isNumeric) {\n    pushBits(0b0001, 4);\n    pushBits(text.length, 10);\n    for (let i = 0; i < text.length; i += 3) {\n      const group = text.slice(i, i + 3);\n      const val = Number.parseInt(group, 10);\n      let bitCount = 4;\n      if (group.length === 3) {\n        bitCount = 10;\n      } else if (group.length === 2) {\n        bitCount = 7;\n      }\n      pushBits(val, bitCount);\n    }\n  } else if (isAlphanumeric) {\n    pushBits(0b0010, 4);\n    pushBits(text.length, 9);\n    for (let i = 0; i < text.length; i += 2) {\n      if (i + 1 < text.length) {\n        const v =\n          ALNUM_CHARS.indexOf(text[i]) * 45 +\n          ALNUM_CHARS.indexOf(text[i + 1] ?? \"\");\n        pushBits(v, 11);\n      } else {\n        pushBits(ALNUM_CHARS.indexOf(text[i]), 6);\n      }\n    }\n  } else {\n    const bytes = [...text].map((c) => (c.codePointAt(0) ?? 0) & 0xff);\n    pushBits(0b0100, 4);\n    pushBits(bytes.length, 8);\n    for (const b of bytes) {\n      pushBits(b, 8);\n    }\n  }\n\n  for (let i = 0; i < 4 && bits.length < DATA_CODEWORDS * 8; i += 1) {\n    bits.push(0);\n  }\n\n  while (bits.length % 8 !== 0) {\n    bits.push(0);\n  }\n\n  const padBytes = [0xec, 0x11];\n  let padIdx = 0;\n  while (bits.length < DATA_CODEWORDS * 8) {\n    const b = padBytes[(padIdx += 1 % 2)] ?? 0;\n    pushBits(b, 8);\n  }\n\n  const codewords = new Uint8Array(DATA_CODEWORDS);\n  for (let i = 0; i < DATA_CODEWORDS; i += 1) {\n    let byte = 0;\n    for (let j = 0; j < 8; j += 1) {\n      byte = (byte << 1) | (bits[i * 8 + j] ?? 0);\n    }\n    codewords[i] = byte;\n  }\n  return codewords;\n};\n\ntype Matrix = boolean[][];\n\nconst makeMatrix = (): Matrix =>\n  Array.from({ length: SIZE }, () =>\n    Array.from<boolean>({ length: SIZE }).fill(false)\n  );\n\nconst placeFinderPattern = (matrix: Matrix, row: number, col: number) => {\n  const finder = [\n    [1, 1, 1, 1, 1, 1, 1],\n    [1, 0, 0, 0, 0, 0, 1],\n    [1, 0, 1, 1, 1, 0, 1],\n    [1, 0, 1, 1, 1, 0, 1],\n    [1, 0, 1, 1, 1, 0, 1],\n    [1, 0, 0, 0, 0, 0, 1],\n    [1, 1, 1, 1, 1, 1, 1],\n  ];\n  for (let r = 0; r < 7; r += 1) {\n    for (let c = 0; c < 7; c += 1) {\n      const mr = row + r;\n      const mc = col + c;\n      if (mr >= 0 && mr < SIZE && mc >= 0 && mc < SIZE) {\n        matrix[mr][mc] = finder[r][c] === 1;\n      }\n    }\n  }\n};\n\nconst placeTimingPatterns = (matrix: Matrix) => {\n  for (let i = 8; i < SIZE - 8; i += 1) {\n    matrix[6][i] = i % 2 === 0;\n    matrix[i][6] = i % 2 === 0;\n  }\n};\n\nconst placeDarkModule = (matrix: Matrix) => {\n  const row = matrix[SIZE - 8];\n  if (row) {\n    row[8] = true;\n  }\n};\n\nconst buildFunctionMask = (_matrix: Matrix): boolean[][] => {\n  const mask = makeMatrix();\n\n  const setRegion = (r: number, c: number, h: number, w: number) => {\n    for (let dr = 0; dr < h; dr += 1) {\n      for (let dc = 0; dc < w; dc += 1) {\n        if (r + dr >= 0 && r + dr < SIZE && c + dc >= 0 && c + dc < SIZE) {\n          const maskRow = mask[r + dr];\n          if (maskRow) {\n            maskRow[c + dc] = true;\n          }\n        }\n      }\n    }\n  };\n\n  setRegion(0, 0, 8, 8);\n  setRegion(0, SIZE - 8, 8, 8);\n  setRegion(SIZE - 8, 0, 8, 8);\n\n  for (let i = 0; i < SIZE; i += 1) {\n    mask[6][i] = true;\n    mask[i][6] = true;\n  }\n\n  for (let i = 0; i < 9; i += 1) {\n    mask[i][8] = true;\n    mask[8][i] = true;\n  }\n  for (let i = SIZE - 8; i < SIZE; i += 1) {\n    mask[8][i] = true;\n    mask[i][8] = true;\n  }\n\n  return mask;\n};\n\nconst FORMAT_STRINGS: Record<number, number> = {\n  0: 0b101_0100_0001_0010,\n  1: 0b101_0001_0010_0101,\n  2: 0b101_1110_0111_1100,\n  3: 0b101_1011_0100_1011,\n  4: 0b100_0101_1111_1001,\n  5: 0b100_0000_1100_1110,\n  6: 0b100_1111_1001_0111,\n  7: 0b100_1010_1010_0000,\n};\n\nconst placeFormatInfo = (matrix: Matrix, maskPattern: number) => {\n  const fmt = FORMAT_STRINGS[maskPattern] ?? FORMAT_STRINGS[0];\n\n  const bits15 = Array.from({ length: 15 }, (_, i) => (fmt >> (14 - i)) & 1);\n  const positions = [\n    [0, 8],\n    [1, 8],\n    [2, 8],\n    [3, 8],\n    [4, 8],\n    [5, 8],\n    [7, 8],\n    [8, 8],\n    [8, 7],\n    [8, 5],\n    [8, 4],\n    [8, 3],\n    [8, 2],\n    [8, 1],\n    [8, 0],\n  ];\n  for (let i = 0; i < positions.length; i += 1) {\n    const pos = positions[i];\n    if (pos) {\n      const matrixRow = matrix[pos[0]];\n      if (matrixRow) {\n        matrixRow[pos[1]] = (bits15[i] ?? 0) === 1;\n      }\n    }\n  }\n\n  const positions2 = [\n    [8, SIZE - 1],\n    [8, SIZE - 2],\n    [8, SIZE - 3],\n    [8, SIZE - 4],\n    [8, SIZE - 5],\n    [8, SIZE - 6],\n    [8, SIZE - 7],\n    [8, SIZE - 8],\n  ];\n  for (let i = 0; i < positions2.length; i += 1) {\n    const pos = positions2[i];\n    if (pos) {\n      const matrixRow = matrix[pos[0]];\n      if (matrixRow) {\n        matrixRow[pos[1]] = (bits15[i] ?? 0) === 1;\n      }\n    }\n  }\n\n  const positions3 = [\n    [SIZE - 7, 8],\n    [SIZE - 6, 8],\n    [SIZE - 5, 8],\n    [SIZE - 4, 8],\n    [SIZE - 3, 8],\n    [SIZE - 2, 8],\n    [SIZE - 1, 8],\n  ];\n  for (let i = 0; i < positions3.length; i += 1) {\n    const pos = positions3[i];\n    if (pos) {\n      const matrixRow = matrix[pos[0]];\n      if (matrixRow) {\n        matrixRow[pos[1]] = (bits15[8 + i] ?? 0) === 1;\n      }\n    }\n  }\n};\n\nconst placeData = (matrix: Matrix, funcMask: boolean[][], bits: number[]) => {\n  let idx = 0;\n  let goingUp = true;\n  for (let col = SIZE - 1; col >= 1; col -= 2) {\n    if (col === 6) {\n      col = 5;\n    }\n    for (let rowStep = 0; rowStep < SIZE; rowStep += 1) {\n      const row = goingUp ? SIZE - 1 - rowStep : rowStep;\n      for (let dc = 0; dc <= 1; dc += 1) {\n        const c = col - dc;\n        if (funcMask[row][c]) {\n          continue;\n        }\n        matrix[row][c] = (bits[idx] ?? 0) === 1;\n        idx += 1;\n      }\n    }\n    goingUp = !goingUp;\n  }\n};\n\nconst applyMask = (matrix: Matrix, funcMask: boolean[][], pattern: number) => {\n  for (let r = 0; r < SIZE; r += 1) {\n    for (let c = 0; c < SIZE; c += 1) {\n      if (funcMask[r][c]) {\n        continue;\n      }\n      let invert = false;\n      switch (pattern) {\n        case 0: {\n          invert = (r + c) % 2 === 0;\n          break;\n        }\n        case 1: {\n          invert = r % 2 === 0;\n          break;\n        }\n        case 2: {\n          invert = c % 3 === 0;\n          break;\n        }\n        case 3: {\n          invert = (r + c) % 3 === 0;\n          break;\n        }\n        case 4: {\n          invert = (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0;\n          break;\n        }\n        case 5: {\n          invert = ((r * c) % 2) + ((r * c) % 3) === 0;\n          break;\n        }\n        case 6: {\n          invert = (((r * c) % 2) + ((r * c) % 3)) % 2 === 0;\n          break;\n        }\n        case 7: {\n          invert = (((r + c) % 2) + ((r * c) % 3)) % 2 === 0;\n          break;\n        }\n        default: {\n          break;\n        }\n      }\n      if (invert) {\n        matrix[r][c] = !matrix[r][c];\n      }\n    }\n  }\n};\n\nconst scorePenalty = (matrix: Matrix): number => {\n  let penalty = 0;\n\n  for (let r = 0; r < SIZE; r += 1) {\n    for (const isRow of [true, false]) {\n      let run = 1;\n      for (let i = 1; i < SIZE; i += 1) {\n        const prev = isRow ? matrix[r]?.[i - 1] : matrix[i - 1]?.[r];\n        const cur = isRow ? matrix[r][i] : matrix[i][r];\n        if (cur === prev) {\n          run += 1;\n          if (run === 5) {\n            penalty += 3;\n          } else if (run > 5) {\n            penalty += 1;\n          }\n        } else {\n          run = 1;\n        }\n      }\n    }\n  }\n\n  for (let r = 0; r < SIZE - 1; r += 1) {\n    for (let c = 0; c < SIZE - 1; c += 1) {\n      const v = matrix[r][c];\n      if (\n        v === matrix[r]?.[c + 1] &&\n        v === matrix[r + 1]?.[c] &&\n        v === matrix[r + 1]?.[c + 1]\n      ) {\n        penalty += 3;\n      }\n    }\n  }\n\n  return penalty;\n};\n\nconst generateQR = (text: string): Matrix => {\n  const capped = text.slice(0, 17);\n\n  const dataBytes = encodeData(capped);\n  const ecBytes = rsEncode(dataBytes, EC_CODEWORDS);\n\n  const allBytes = new Uint8Array([...dataBytes, ...ecBytes]);\n  const bits: number[] = [];\n  for (const b of allBytes) {\n    for (let i = 7; i >= 0; i -= 1) {\n      bits.push((b >> i) & 1);\n    }\n  }\n\n  let bestMatrix: Matrix | null = null;\n  let bestPenalty = Infinity;\n\n  for (let maskPattern = 0; maskPattern < 8; maskPattern += 1) {\n    const matrix = makeMatrix();\n    placeFinderPattern(matrix, 0, 0);\n    placeFinderPattern(matrix, 0, SIZE - 7);\n    placeFinderPattern(matrix, SIZE - 7, 0);\n    placeTimingPatterns(matrix);\n    placeDarkModule(matrix);\n\n    const funcMask = buildFunctionMask(matrix);\n    placeData(matrix, funcMask, bits);\n    applyMask(matrix, funcMask, maskPattern);\n    placeFormatInfo(matrix, maskPattern);\n\n    const p = scorePenalty(matrix);\n    if (p < bestPenalty) {\n      bestPenalty = p;\n      bestMatrix = matrix;\n    }\n  }\n\n  return bestMatrix ?? makeMatrix();\n};\n\nconst QUIET_ZONE = 2;\n\nexport const QRCode = ({\n  value,\n  size = \"md\",\n  color,\n  label,\n  alt,\n  \"aria-hidden\": ariaHidden,\n}: QRCodeProps) => {\n  const theme = useTheme();\n  const unicode = useUnicode();\n  const resolvedColor = color ?? theme.colors.foreground;\n\n  let matrix: Matrix;\n  try {\n    matrix = generateQR(value);\n  } catch {\n    return (\n      <Box\n        flexDirection=\"column\"\n        gap={0}\n        aria-hidden={ariaHidden}\n        aria-label={ariaHidden ? undefined : `QR code: ${alt}`}\n      >\n        <Text color=\"red\">QR Error: value too long or unsupported</Text>\n        {label && <Text color={theme.colors.mutedForeground}>{label}</Text>}\n      </Box>\n    );\n  }\n\n  const qzMatrix: Matrix = [];\n  const totalSize = SIZE + QUIET_ZONE * 2;\n  for (let r = 0; r < totalSize; r += 1) {\n    const row: boolean[] = Array.from<boolean>({ length: totalSize }).fill(\n      false\n    );\n    if (r >= QUIET_ZONE && r < SIZE + QUIET_ZONE) {\n      for (let c = 0; c < SIZE; c += 1) {\n        row[c + QUIET_ZONE] = matrix[r - QUIET_ZONE]?.[c] ?? false;\n      }\n    }\n    qzMatrix.push(row);\n  }\n\n  const scale = size === \"lg\" ? 2 : 1;\n  const darkCell = unicode ? \"█\" : \"##\";\n  const lightCell = unicode ? \" \" : \"  \";\n\n  if (size === \"sm\" && unicode) {\n    const lines: React.ReactElement[] = [];\n    for (let r = 0; r < totalSize; r += 2) {\n      const chars: string[] = [];\n      for (let c = 0; c < totalSize; c += 1) {\n        const top = qzMatrix[r][c] ?? false;\n        const bottom = qzMatrix[r + 1]?.[c] ?? false;\n        if (top && bottom) {\n          chars.push(\"█\");\n        } else if (top) {\n          chars.push(\"▀\");\n        } else if (bottom) {\n          chars.push(\"▄\");\n        } else {\n          chars.push(\" \");\n        }\n      }\n      lines.push(\n        <Text key={r} color={resolvedColor}>\n          {chars.join(\"\")}\n        </Text>\n      );\n    }\n    return (\n      <Box\n        flexDirection=\"column\"\n        gap={0}\n        aria-hidden={ariaHidden}\n        aria-label={ariaHidden ? undefined : `QR code: ${alt}`}\n      >\n        {lines}\n        {label && <Text color={theme.colors.mutedForeground}>{label}</Text>}\n      </Box>\n    );\n  }\n\n  const lines: React.ReactElement[] = [];\n  for (let r = 0; r < totalSize; r += 1) {\n    const chars: string[] = [];\n    for (let c = 0; c < totalSize; c += 1) {\n      const on = qzMatrix[r][c] ?? false;\n      chars.push((on ? darkCell : lightCell).repeat(scale));\n    }\n    for (let s = 0; s < scale; s += 1) {\n      lines.push(\n        <Text key={`${r}-${s}`} color={resolvedColor}>\n          {chars.join(\"\")}\n        </Text>\n      );\n    }\n  }\n\n  return (\n    <Box\n      flexDirection=\"column\"\n      gap={0}\n      aria-hidden={ariaHidden}\n      aria-label={ariaHidden ? undefined : `QR code: ${alt}`}\n    >\n      {lines}\n      {label && <Text color={theme.colors.mutedForeground}>{label}</Text>}\n    </Box>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "utility"
  ],
  "type": "registry:ui"
}