{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "qr-code",
  "type": "registry:ui",
  "title": "Qr code",
  "description": "A dependency free QR code rendered as crisp SVG, with error correction levels, a logo well and a one click SVG download.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "https://ui.minidev.pro/r/button.json",
    "utils"
  ],
  "files": [
    {
      "path": "registry/ui/qr-code.tsx",
      "type": "registry:ui",
      "target": "components/ui/qr-code.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { CheckIcon, DownloadIcon } from \"lucide-react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\n\n/** A dependency free QR code rendered as crisp SVG, with error correction levels, a logo well and a one click SVG download. */\ntype QrLevel = \"L\" | \"M\" | \"Q\" | \"H\"\n\ntype QrCodeProps = {\n  /** Text to encode, as UTF-8 bytes. URLs, Wi-Fi strings, payment payloads, anything. */\n  value?: string\n  /** Rendered width and height in px. The SVG scales without blur. */\n  size?: number\n  /** Error correction level. L 7%, M 15%, Q 25%, H 30% of codewords recoverable. The logo well needs H. */\n  level?: QrLevel\n  /** Module colour. Any CSS colour; defaults to the current text colour so it follows your tokens. */\n  fg?: string\n  /** Background colour behind the modules and quiet zone. */\n  bg?: string\n  /** Quiet zone in modules on each side. The spec asks for 4; 2 is fine on a plain surface. */\n  margin?: number\n  /** Width of the cleared centre well as a fraction of the symbol, used when a logo is passed. */\n  logoScale?: number\n  /** Force a mask pattern (0 to 7) instead of picking the lowest penalty one. */\n  mask?: number\n  /** Caption under the code. Defaults to the host of a URL value or the value itself. Pass an empty string to hide. */\n  caption?: string\n  /** Show the Download SVG button. */\n  download?: boolean\n  /** File name for the download. */\n  fileName?: string\n  /** Render the bare SVG with no card, caption or button. */\n  bare?: boolean\n  /** Accessible name. Defaults to \"QR code for <value>\". */\n  title?: string\n  /** Logo or icon placed in a cleared centre well. Only rendered when `level` is \"H\". */\n  children?: React.ReactNode\n  className?: string\n}\n\n/* ── Encoder ──────────────────────────────────────────────────────────────── */\n\n/** Per version, per level (L, M, Q, H): [ec codewords per block, group 1 blocks, group 1 data codewords, group 2 blocks, group 2 data codewords]. */\nconst EC_TABLE: number[][][] = [\n  [[7, 1, 19, 0, 0], [10, 1, 16, 0, 0], [13, 1, 13, 0, 0], [17, 1, 9, 0, 0]],\n  [[10, 1, 34, 0, 0], [16, 1, 28, 0, 0], [22, 1, 22, 0, 0], [28, 1, 16, 0, 0]],\n  [[15, 1, 55, 0, 0], [26, 1, 44, 0, 0], [18, 2, 17, 0, 0], [22, 2, 13, 0, 0]],\n  [[20, 1, 80, 0, 0], [18, 2, 32, 0, 0], [26, 2, 24, 0, 0], [16, 4, 9, 0, 0]],\n  [[26, 1, 108, 0, 0], [24, 2, 43, 0, 0], [18, 2, 15, 2, 16], [22, 2, 11, 2, 12]],\n  [[18, 2, 68, 0, 0], [16, 4, 27, 0, 0], [24, 4, 19, 0, 0], [28, 4, 15, 0, 0]],\n  [[20, 2, 78, 0, 0], [18, 4, 31, 0, 0], [18, 2, 14, 4, 15], [26, 4, 13, 1, 14]],\n  [[24, 2, 97, 0, 0], [22, 2, 38, 2, 39], [22, 4, 18, 2, 19], [26, 4, 14, 2, 15]],\n  [[30, 2, 116, 0, 0], [22, 3, 36, 2, 37], [20, 4, 16, 4, 17], [24, 4, 12, 4, 13]],\n  [[18, 2, 68, 2, 69], [26, 4, 43, 1, 44], [24, 6, 19, 2, 20], [28, 6, 15, 2, 16]],\n  [[20, 4, 81, 0, 0], [30, 1, 50, 4, 51], [28, 4, 22, 4, 23], [24, 3, 12, 8, 13]],\n  [[24, 2, 92, 2, 93], [22, 6, 36, 2, 37], [26, 4, 20, 6, 21], [28, 7, 14, 4, 15]],\n  [[26, 4, 107, 0, 0], [22, 8, 37, 1, 38], [24, 8, 20, 4, 21], [22, 12, 11, 4, 12]],\n  [[30, 3, 115, 1, 116], [24, 4, 40, 5, 41], [20, 11, 16, 5, 17], [24, 11, 12, 5, 13]],\n  [[22, 5, 87, 1, 88], [24, 5, 41, 5, 42], [30, 5, 24, 7, 25], [24, 11, 12, 7, 13]],\n  [[24, 5, 98, 1, 99], [28, 7, 45, 3, 46], [24, 15, 19, 2, 20], [30, 3, 15, 13, 16]],\n  [[28, 1, 107, 5, 108], [28, 10, 46, 1, 47], [28, 1, 22, 15, 23], [28, 2, 14, 17, 15]],\n  [[30, 5, 120, 1, 121], [26, 9, 43, 4, 44], [28, 17, 22, 1, 23], [28, 2, 14, 19, 15]],\n  [[28, 3, 113, 4, 114], [26, 3, 44, 11, 45], [26, 17, 21, 4, 22], [26, 9, 13, 16, 14]],\n  [[28, 3, 107, 5, 108], [26, 3, 41, 13, 42], [30, 15, 24, 5, 25], [28, 15, 15, 10, 16]],\n  [[28, 4, 116, 4, 117], [26, 17, 42, 0, 0], [28, 17, 22, 6, 23], [30, 19, 16, 6, 17]],\n  [[28, 2, 111, 7, 112], [28, 17, 46, 0, 0], [30, 7, 24, 16, 25], [24, 34, 13, 0, 0]],\n  [[30, 4, 121, 5, 122], [28, 4, 47, 14, 48], [30, 11, 24, 14, 25], [30, 16, 15, 14, 16]],\n  [[30, 6, 117, 4, 118], [28, 6, 45, 14, 46], [30, 11, 24, 16, 25], [30, 30, 16, 2, 17]],\n  [[26, 8, 106, 4, 107], [28, 8, 47, 13, 48], [30, 7, 24, 22, 25], [30, 22, 15, 13, 16]],\n  [[28, 10, 114, 2, 115], [28, 19, 46, 4, 47], [28, 28, 22, 6, 23], [30, 33, 16, 4, 17]],\n  [[30, 8, 122, 4, 123], [28, 22, 45, 3, 46], [30, 8, 23, 26, 24], [30, 12, 15, 28, 16]],\n  [[30, 3, 117, 10, 118], [28, 3, 45, 23, 46], [30, 4, 24, 31, 25], [30, 11, 15, 31, 16]],\n  [[30, 7, 116, 7, 117], [28, 21, 45, 7, 46], [30, 1, 23, 37, 24], [30, 19, 15, 26, 16]],\n  [[30, 5, 115, 10, 116], [28, 19, 47, 10, 48], [30, 15, 24, 25, 25], [30, 23, 15, 25, 16]],\n  [[30, 13, 115, 3, 116], [28, 2, 46, 29, 47], [30, 42, 24, 1, 25], [30, 23, 15, 28, 16]],\n  [[30, 17, 115, 0, 0], [28, 10, 46, 23, 47], [30, 10, 24, 35, 25], [30, 19, 15, 35, 16]],\n  [[30, 17, 115, 1, 116], [28, 14, 46, 21, 47], [30, 29, 24, 19, 25], [30, 11, 15, 46, 16]],\n  [[30, 13, 115, 6, 116], [28, 14, 46, 23, 47], [30, 44, 24, 7, 25], [30, 59, 16, 1, 17]],\n  [[30, 12, 121, 7, 122], [28, 12, 47, 26, 48], [30, 39, 24, 14, 25], [30, 22, 15, 41, 16]],\n  [[30, 6, 121, 14, 122], [28, 6, 47, 34, 48], [30, 46, 24, 10, 25], [30, 2, 15, 64, 16]],\n  [[30, 17, 122, 4, 123], [28, 29, 46, 14, 47], [30, 49, 24, 10, 25], [30, 24, 15, 46, 16]],\n  [[30, 4, 122, 18, 123], [28, 13, 46, 32, 47], [30, 48, 24, 14, 25], [30, 42, 15, 32, 16]],\n  [[30, 20, 117, 4, 118], [28, 40, 47, 7, 48], [30, 43, 24, 22, 25], [30, 10, 15, 67, 16]],\n  [[30, 19, 118, 6, 119], [28, 18, 47, 31, 48], [30, 34, 24, 34, 25], [30, 20, 15, 61, 16]],\n]\n\n/** Alignment pattern centre coordinates per version (index 0 = version 1). */\nconst ALIGN: number[][] = [\n  [], [6, 18], [6, 22], [6, 26], [6, 30], [6, 34], [6, 22, 38], [6, 24, 42], [6, 26, 46], [6, 28, 50],\n  [6, 30, 54], [6, 32, 58], [6, 34, 62], [6, 26, 46, 66], [6, 26, 48, 70], [6, 26, 50, 74], [6, 30, 54, 78], [6, 30, 56, 82], [6, 30, 58, 86], [6, 34, 62, 90],\n  [6, 28, 50, 72, 94], [6, 26, 50, 74, 98], [6, 30, 54, 78, 102], [6, 28, 54, 80, 106], [6, 32, 58, 84, 110], [6, 30, 58, 86, 114], [6, 34, 62, 90, 118],\n  [6, 26, 50, 74, 98, 122], [6, 30, 54, 78, 102, 126], [6, 26, 52, 78, 104, 130], [6, 30, 56, 82, 108, 134], [6, 34, 60, 86, 112, 138], [6, 30, 58, 86, 114, 142], [6, 34, 62, 90, 118, 146],\n  [6, 30, 54, 78, 102, 126, 150], [6, 24, 50, 76, 102, 128, 154], [6, 28, 54, 80, 106, 132, 158], [6, 32, 58, 84, 110, 136, 162], [6, 26, 54, 82, 110, 138, 166], [6, 30, 58, 86, 114, 142, 170],\n]\n\nconst LEVEL_INDEX: Record<QrLevel, number> = { L: 0, M: 1, Q: 2, H: 3 }\n/** Two bit level indicator used in the format information. */\nconst LEVEL_BITS: Record<QrLevel, number> = { L: 1, M: 0, Q: 3, H: 2 }\n\n/* GF(256) with the QR primitive polynomial x^8 + x^4 + x^3 + x^2 + 1. */\nconst EXP = new Uint8Array(512)\nconst LOG = new Uint8Array(256)\n{\n  let x = 1\n  for (let i = 0; i < 255; i++) {\n    EXP[i] = x\n    LOG[x] = i\n    x <<= 1\n    if (x & 0x100) x ^= 0x11d\n  }\n  for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255]\n}\nconst gfMul = (a: number, b: number) => (a === 0 || b === 0 ? 0 : EXP[LOG[a] + LOG[b]])\n\n/** Reed-Solomon generator polynomial of the given degree, highest power first, leading coefficient 1. */\nfunction rsGenerator(degree: number): Uint8Array {\n  let poly = new Uint8Array([1])\n  for (let i = 0; i < degree; i++) {\n    const next = new Uint8Array(poly.length + 1)\n    for (let j = 0; j < poly.length; j++) {\n      next[j] ^= poly[j]\n      next[j + 1] ^= gfMul(poly[j], EXP[i])\n    }\n    poly = next\n  }\n  return poly\n}\n\n/** Remainder of data · x^degree divided by the generator: the EC codewords for one block. */\nfunction rsRemainder(data: Uint8Array, gen: Uint8Array): Uint8Array {\n  const n = gen.length - 1\n  const res = new Uint8Array(n)\n  for (const b of data) {\n    const factor = b ^ res[0]\n    res.copyWithin(0, 1)\n    res[n - 1] = 0\n    if (factor) for (let j = 0; j < n; j++) res[j] ^= gfMul(gen[j + 1], factor)\n  }\n  return res\n}\n\nfunction utf8(text: string): Uint8Array {\n  if (typeof TextEncoder !== \"undefined\") return new TextEncoder().encode(text)\n  const out: number[] = []\n  for (let i = 0; i < text.length; i++) {\n    let c = text.codePointAt(i)!\n    if (c > 0xffff) i++\n    if (c < 0x80) out.push(c)\n    else if (c < 0x800) out.push(0xc0 | (c >> 6), 0x80 | (c & 63))\n    else if (c < 0x10000) out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 63), 0x80 | (c & 63))\n    else out.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 63), 0x80 | ((c >> 6) & 63), 0x80 | (c & 63))\n  }\n  return Uint8Array.from(out)\n}\n\nconst countBits = (version: number) => (version < 10 ? 8 : 16)\nconst dataCodewords = (version: number, level: QrLevel) => {\n  const [, g1, d1, g2, d2] = EC_TABLE[version - 1][LEVEL_INDEX[level]]\n  return g1 * d1 + g2 * d2\n}\n\n/** BCH(15,5) format information for a level and mask, already XOR masked. */\nfunction formatBits(level: QrLevel, mask: number): number {\n  const data = (LEVEL_BITS[level] << 3) | mask\n  let rem = data\n  for (let i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537)\n  return ((data << 10) | rem) ^ 0x5412\n}\n\n/** BCH(18,6) version information for versions 7 and up. */\nfunction versionBits(version: number): number {\n  let rem = version\n  for (let i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25)\n  return (version << 12) | rem\n}\n\nconst MASKS: ((r: number, c: number) => boolean)[] = [\n  (r, c) => (r + c) % 2 === 0,\n  (r) => r % 2 === 0,\n  (_, c) => c % 3 === 0,\n  (r, c) => (r + c) % 3 === 0,\n  (r, c) => (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0,\n  (r, c) => ((r * c) % 2) + ((r * c) % 3) === 0,\n  (r, c) => (((r * c) % 2) + ((r * c) % 3)) % 2 === 0,\n  (r, c) => (((r + c) % 2) + ((r * c) % 3)) % 2 === 0,\n]\n\ntype QrMatrix = {\n  version: number\n  size: number\n  mask: number\n  level: QrLevel\n  /** Row major, 1 = dark. */\n  modules: Uint8Array\n  /** Row major, 1 = function pattern (finder, timing, alignment, format, version). */\n  isFunction: Uint8Array\n}\n\n/** Penalty score of a masked symbol, per ISO 18004 section 8.8.2. Lower is better. */\nfunction penalty(m: Uint8Array, size: number): number {\n  let score = 0\n  const at = (r: number, c: number) => m[r * size + c]\n  for (let line = 0; line < size; line++) {\n    for (let axis = 0; axis < 2; axis++) {\n      const get = (i: number) => (axis === 0 ? at(line, i) : at(i, line))\n      // N1: runs of five or more.\n      let run = 1\n      for (let i = 1; i <= size; i++) {\n        if (i < size && get(i) === get(i - 1)) run++\n        else {\n          if (run >= 5) score += 3 + run - 5\n          run = 1\n        }\n      }\n      // N3: finder like 1011101 with four light modules on one side.\n      for (let i = 0; i + 6 < size; i++) {\n        if (get(i) && !get(i + 1) && get(i + 2) && get(i + 3) && get(i + 4) && !get(i + 5) && get(i + 6)) {\n          let before = i >= 4\n          for (let k = 1; before && k <= 4; k++) if (get(i - k)) before = false\n          let after = i + 10 < size\n          for (let k = 7; after && k <= 10; k++) if (get(i + k)) after = false\n          if (before) score += 40\n          if (after) score += 40\n        }\n      }\n    }\n  }\n  // N2: 2x2 blocks of one colour.\n  for (let r = 0; r + 1 < size; r++)\n    for (let c = 0; c + 1 < size; c++) {\n      const v = at(r, c)\n      if (v === at(r, c + 1) && v === at(r + 1, c) && v === at(r + 1, c + 1)) score += 3\n    }\n  // N4: deviation from 50% dark in 5% steps.\n  let dark = 0\n  for (let i = 0; i < m.length; i++) dark += m[i]\n  score += Math.floor(Math.abs((dark * 100) / m.length - 50) / 5) * 10\n  return score\n}\n\n/**\n * Encode text in byte mode. Picks the smallest version (1 to 40) that fits,\n * or returns null when the text is too long for the level.\n */\nfunction encodeQr(text: string, level: QrLevel = \"M\", forcedMask?: number): QrMatrix | null {\n  const bytes = utf8(text)\n  let version = 0\n  for (let v = 1; v <= 40; v++) {\n    if (4 + countBits(v) + bytes.length * 8 <= dataCodewords(v, level) * 8) {\n      version = v\n      break\n    }\n  }\n  if (!version) return null\n\n  /* Data bit stream: mode, count, bytes, terminator, byte alignment, pad codewords. */\n  const capacity = dataCodewords(version, level)\n  const bits: number[] = []\n  const push = (val: number, len: number) => {\n    for (let i = len - 1; i >= 0; i--) bits.push((val >>> i) & 1)\n  }\n  push(4, 4)\n  push(bytes.length, countBits(version))\n  for (const b of bytes) push(b, 8)\n  push(0, Math.min(4, capacity * 8 - bits.length))\n  while (bits.length % 8) bits.push(0)\n  for (let pad = 0xec; bits.length < capacity * 8; pad ^= 0xec ^ 0x11) push(pad, 8)\n  const data = new Uint8Array(capacity)\n  for (let i = 0; i < bits.length; i++) data[i >> 3] |= bits[i] << (7 - (i & 7))\n\n  /* Split into blocks, add EC, interleave. */\n  const [ecLen, g1, d1, g2, d2] = EC_TABLE[version - 1][LEVEL_INDEX[level]]\n  const gen = rsGenerator(ecLen)\n  const blocks: { data: Uint8Array; ec: Uint8Array }[] = []\n  let k = 0\n  for (let i = 0; i < g1 + g2; i++) {\n    const len = i < g1 ? d1 : d2\n    const chunk = data.subarray(k, k + len)\n    k += len\n    blocks.push({ data: chunk, ec: rsRemainder(chunk, gen) })\n  }\n  const stream: number[] = []\n  const longest = Math.max(d1, d2)\n  for (let i = 0; i < longest; i++) for (const b of blocks) if (i < b.data.length) stream.push(b.data[i])\n  for (let i = 0; i < ecLen; i++) for (const b of blocks) stream.push(b.ec[i])\n\n  /* Function patterns. */\n  const size = version * 4 + 17\n  const modules = new Uint8Array(size * size)\n  const isFunction = new Uint8Array(size * size)\n  const setFn = (r: number, c: number, dark: boolean) => {\n    modules[r * size + c] = dark ? 1 : 0\n    isFunction[r * size + c] = 1\n  }\n  const finder = (r0: number, c0: number) => {\n    for (let dr = -1; dr <= 7; dr++)\n      for (let dc = -1; dc <= 7; dc++) {\n        const r = r0 + dr\n        const c = c0 + dc\n        if (r < 0 || c < 0 || r >= size || c >= size) continue\n        const inside = dr >= 0 && dr <= 6 && dc >= 0 && dc <= 6\n        setFn(r, c, inside && Math.max(Math.abs(dr - 3), Math.abs(dc - 3)) !== 2)\n      }\n  }\n  finder(0, 0)\n  finder(0, size - 7)\n  finder(size - 7, 0)\n  for (let i = 8; i < size - 8; i++) {\n    setFn(6, i, i % 2 === 0)\n    setFn(i, 6, i % 2 === 0)\n  }\n  const align = ALIGN[version - 1]\n  for (const r of align)\n    for (const c of align) {\n      const last = align[align.length - 1]\n      if ((r === 6 && c === 6) || (r === 6 && c === last) || (r === last && c === 6)) continue\n      for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) setFn(r + dr, c + dc, Math.max(Math.abs(dr), Math.abs(dc)) !== 1)\n    }\n  /* Reserve the format areas (filled per mask below) and the dark module. Index 6 is the timing pattern. */\n  for (let i = 0; i < 9; i++) {\n    if (i === 6) continue\n    setFn(8, i, false)\n    setFn(i, 8, false)\n  }\n  for (let i = 0; i < 8; i++) {\n    setFn(8, size - 1 - i, false)\n    setFn(size - 1 - i, 8, false)\n  }\n  setFn(size - 8, 8, true)\n  if (version >= 7) {\n    const vb = versionBits(version)\n    for (let i = 0; i < 18; i++) {\n      const dark = ((vb >>> i) & 1) === 1\n      const a = Math.floor(i / 3)\n      const b = size - 11 + (i % 3)\n      setFn(a, b, dark)\n      setFn(b, a, dark)\n    }\n  }\n\n  /* Place the codeword bits in the zigzag, two columns at a time, skipping the vertical timing column. */\n  const totalBits = stream.length * 8\n  let bit = 0\n  let upward = true\n  for (let right = size - 1; right >= 1; right -= 2) {\n    if (right === 6) right = 5\n    for (let i = 0; i < size; i++) {\n      const r = upward ? size - 1 - i : i\n      for (let j = 0; j < 2; j++) {\n        const c = right - j\n        if (isFunction[r * size + c]) continue\n        if (bit < totalBits) modules[r * size + c] = (stream[bit >> 3] >> (7 - (bit & 7))) & 1\n        bit++\n      }\n    }\n    upward = !upward\n  }\n\n  const drawFormat = (m: Uint8Array, mask: number) => {\n    const fb = formatBits(level, mask)\n    const get = (i: number) => (fb >>> i) & 1\n    for (let i = 0; i <= 5; i++) m[i * size + 8] = get(i)\n    m[7 * size + 8] = get(6)\n    m[8 * size + 8] = get(7)\n    m[8 * size + 7] = get(8)\n    for (let i = 9; i < 15; i++) m[8 * size + (14 - i)] = get(i)\n    for (let i = 0; i < 8; i++) m[8 * size + (size - 1 - i)] = get(i)\n    for (let i = 8; i < 15; i++) m[(size - 15 + i) * size + 8] = get(i)\n    m[(size - 8) * size + 8] = 1\n  }\n  const applyMask = (mask: number) => {\n    const m = modules.slice()\n    const fn = MASKS[mask]\n    for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (!isFunction[r * size + c] && fn(r, c)) m[r * size + c] ^= 1\n    drawFormat(m, mask)\n    return m\n  }\n\n  let best = 0\n  let bestModules: Uint8Array\n  if (forcedMask !== undefined && forcedMask >= 0 && forcedMask <= 7) {\n    best = Math.floor(forcedMask)\n    bestModules = applyMask(best)\n  } else {\n    let bestScore = Infinity\n    bestModules = applyMask(0)\n    for (let mask = 0; mask < 8; mask++) {\n      const m = applyMask(mask)\n      const score = penalty(m, size)\n      if (score < bestScore) {\n        bestScore = score\n        best = mask\n        bestModules = m\n      }\n    }\n  }\n  return { version, size, mask: best, level, modules: bestModules, isFunction }\n}\n\n/** SVG path for the dark modules, merging horizontal runs. `skip` returns true for modules to leave out. */\nfunction modulePath(q: QrMatrix, offset: number, skip?: (r: number, c: number) => boolean): string {\n  let d = \"\"\n  for (let r = 0; r < q.size; r++) {\n    let c = 0\n    while (c < q.size) {\n      if (!q.modules[r * q.size + c] || skip?.(r, c)) {\n        c++\n        continue\n      }\n      let end = c\n      while (end < q.size && q.modules[r * q.size + end] && !skip?.(r, end)) end++\n      d += `M${c + offset} ${r + offset}h${end - c}v1h-${end - c}z`\n      c = end\n    }\n  }\n  return d\n}\n\n/* ── Component ────────────────────────────────────────────────────────────── */\n\nfunction prettyValue(value: string) {\n  try {\n    const u = new URL(value)\n    return u.host + (u.pathname !== \"/\" ? u.pathname : \"\")\n  } catch {\n    return value.length > 40 ? value.slice(0, 37) + \"…\" : value\n  }\n}\n\n/**\n * A QR code that needs no library. The encoder is in this file: byte mode,\n * versions 1 to 40, all four error correction levels, Reed-Solomon codewords\n * and spec penalty masking. Modules draw as one SVG path in `currentColor`,\n * so the code follows your tokens; a logo can sit in a cleared well at level\n * H; and Download SVG writes a standalone file.\n */\nfunction QrCode({\n  value = \"https://ui.minidev.pro\",\n  size = 200,\n  level = \"M\",\n  fg = \"currentColor\",\n  bg = \"transparent\",\n  margin = 2,\n  logoScale = 0.24,\n  mask,\n  caption,\n  download = true,\n  fileName = \"qr-code.svg\",\n  bare = false,\n  title,\n  children,\n  className,\n}: QrCodeProps) {\n  const qr = React.useMemo(() => encodeQr(value, level, mask), [value, level, mask])\n  const [saved, setSaved] = React.useState(false)\n  const savedTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n  React.useEffect(() => () => {\n    if (savedTimer.current) clearTimeout(savedTimer.current)\n  }, [])\n\n  const hasLogo = Boolean(children) && level === \"H\"\n  const m = Math.max(0, Math.round(margin))\n  const view = qr ? qr.size + m * 2 : 1\n  const well = React.useMemo(() => {\n    if (!qr || !hasLogo) return null\n    let n = Math.round(qr.size * logoScale)\n    if (n % 2 !== qr.size % 2) n++\n    n = Math.max(3, Math.min(qr.size - 16, n))\n    const start = (qr.size - n) / 2\n    return { start, end: start + n, n }\n  }, [qr, hasLogo, logoScale])\n  const path = React.useMemo(\n    () => (qr ? modulePath(qr, m, well ? (r, c) => r >= well.start && r < well.end && c >= well.start && c < well.end : undefined) : \"\"),\n    [qr, m, well]\n  )\n  const label = title ?? `QR code for ${value}`\n\n  const svgFile = React.useCallback(() => {\n    const fill = fg === \"currentColor\" ? \"#000000\" : fg\n    const back = bg === \"transparent\" ? \"#ffffff\" : bg\n    return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${view} ${view}\" width=\"${view * 8}\" height=\"${view * 8}\" shape-rendering=\"crispEdges\"><title>${label.replace(/[<>&]/g, \"\")}</title><rect width=\"${view}\" height=\"${view}\" fill=\"${back}\"/><path d=\"${path}\" fill=\"${fill}\"/></svg>`\n  }, [fg, bg, view, label, path])\n\n  const save = () => {\n    const blob = new Blob([svgFile()], { type: \"image/svg+xml\" })\n    const url = URL.createObjectURL(blob)\n    const a = document.createElement(\"a\")\n    a.href = url\n    a.download = fileName\n    a.click()\n    setTimeout(() => URL.revokeObjectURL(url), 1000)\n    setSaved(true)\n    if (savedTimer.current) clearTimeout(savedTimer.current)\n    savedTimer.current = setTimeout(() => setSaved(false), 1600)\n  }\n\n  const px = size / view\n  const svg = qr ? (\n    <svg\n      data-slot=\"qr-code-svg\"\n      role=\"img\"\n      aria-label={label}\n      viewBox={`0 0 ${view} ${view}`}\n      width={size}\n      height={size}\n      shapeRendering=\"crispEdges\"\n      className=\"block\"\n    >\n      <rect width={view} height={view} fill={bg} />\n      <path d={path} fill={fg} />\n    </svg>\n  ) : (\n    <div role=\"img\" aria-label=\"Value too long to encode\" style={{ width: size, height: size }} className=\"grid place-items-center rounded-lg bg-hatch text-center text-xs text-fg-subtle\">\n      Too long for level {level}\n    </div>\n  )\n\n  const stage = (\n    <div data-slot=\"qr-code-stage\" className=\"relative\" style={{ width: size, height: size }}>\n      {svg}\n      {qr && well ? (\n        <div\n          data-slot=\"qr-code-logo\"\n          aria-hidden\n          className=\"absolute grid place-items-center overflow-hidden\"\n          style={{\n            left: (m + well.start) * px + px,\n            top: (m + well.start) * px + px,\n            width: well.n * px - px * 2,\n            height: well.n * px - px * 2,\n          }}\n        >\n          {children}\n        </div>\n      ) : null}\n    </div>\n  )\n\n  if (bare) return <div data-slot=\"qr-code\" className={cn(\"inline-block text-fg\", className)}>{stage}</div>\n\n  const cap = caption ?? prettyValue(value)\n  return (\n    <div data-slot=\"qr-code\" className={cn(\"inline-flex flex-col items-center gap-3 rounded-xl border border-border bg-surface p-4 text-fg shadow-raised\", className)}>\n      {stage}\n      {cap ? (\n        <p className=\"max-w-full truncate font-mono text-xs text-fg-muted\" style={{ maxWidth: size }} title={value}>\n          {cap}\n        </p>\n      ) : null}\n      {download && qr ? (\n        <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={save} aria-live=\"polite\" className=\"min-w-32\">\n          {saved ? <CheckIcon className=\"text-success\" /> : <DownloadIcon />}\n          {saved ? \"Saved\" : \"Download SVG\"}\n        </Button>\n      ) : null}\n    </div>\n  )\n}\n\nexport { QrCode, encodeQr }\nexport type { QrCodeProps, QrLevel, QrMatrix }\n"
    }
  ]
}
