/*!
 * Brightery Secure 2FA local QR renderer
 * Human-readable distributed source included for plugin review and maintenance.
 */
(function () {
  const PATTERN_POSITION_TABLE = [[], [6, 18], [6, 22], [6, 26], [6, 30], [6, 34], [6, 22, 38], [6, 24, 42], [6, 26, 46], [6, 28, 50], [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], [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], [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], [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]];
  const RS_BLOCK_TABLE = [[1, 26, 19], [1, 26, 16], [1, 26, 13], [1, 26, 9], [1, 44, 34], [1, 44, 28], [1, 44, 22], [1, 44, 16], [1, 70, 55], [1, 70, 44], [2, 35, 17], [2, 35, 13], [1, 100, 80], [2, 50, 32], [2, 50, 24], [4, 25, 9], [1, 134, 108], [2, 67, 43], [2, 33, 15, 2, 34, 16], [2, 33, 11, 2, 34, 12], [2, 86, 68], [4, 43, 27], [4, 43, 19], [4, 43, 15], [2, 98, 78], [4, 49, 31], [2, 32, 14, 4, 33, 15], [4, 39, 13, 1, 40, 14], [2, 121, 97], [2, 60, 38, 2, 61, 39], [4, 40, 18, 2, 41, 19], [4, 40, 14, 2, 41, 15], [2, 146, 116], [3, 58, 36, 2, 59, 37], [4, 36, 16, 4, 37, 17], [4, 36, 12, 4, 37, 13], [2, 86, 68, 2, 87, 69], [4, 69, 43, 1, 70, 44], [6, 43, 19, 2, 44, 20], [6, 43, 15, 2, 44, 16], [4, 101, 81], [1, 80, 50, 4, 81, 51], [4, 50, 22, 4, 51, 23], [3, 36, 12, 8, 37, 13], [2, 116, 92, 2, 117, 93], [6, 58, 36, 2, 59, 37], [4, 46, 20, 6, 47, 21], [7, 42, 14, 4, 43, 15], [4, 133, 107], [8, 59, 37, 1, 60, 38], [8, 44, 20, 4, 45, 21], [12, 33, 11, 4, 34, 12], [3, 145, 115, 1, 146, 116], [4, 64, 40, 5, 65, 41], [11, 36, 16, 5, 37, 17], [11, 36, 12, 5, 37, 13], [5, 109, 87, 1, 110, 88], [5, 65, 41, 5, 66, 42], [5, 54, 24, 7, 55, 25], [11, 36, 12, 7, 37, 13], [5, 122, 98, 1, 123, 99], [7, 73, 45, 3, 74, 46], [15, 43, 19, 2, 44, 20], [3, 45, 15, 13, 46, 16], [1, 135, 107, 5, 136, 108], [10, 74, 46, 1, 75, 47], [1, 50, 22, 15, 51, 23], [2, 42, 14, 17, 43, 15], [5, 150, 120, 1, 151, 121], [9, 69, 43, 4, 70, 44], [17, 50, 22, 1, 51, 23], [2, 42, 14, 19, 43, 15], [3, 141, 113, 4, 142, 114], [3, 70, 44, 11, 71, 45], [17, 47, 21, 4, 48, 22], [9, 39, 13, 16, 40, 14], [3, 135, 107, 5, 136, 108], [3, 67, 41, 13, 68, 42], [15, 54, 24, 5, 55, 25], [15, 43, 15, 10, 44, 16], [4, 144, 116, 4, 145, 117], [17, 68, 42], [17, 50, 22, 6, 51, 23], [19, 46, 16, 6, 47, 17], [2, 139, 111, 7, 140, 112], [17, 74, 46], [7, 54, 24, 16, 55, 25], [34, 37, 13], [4, 151, 121, 5, 152, 122], [4, 75, 47, 14, 76, 48], [11, 54, 24, 14, 55, 25], [16, 45, 15, 14, 46, 16], [6, 147, 117, 4, 148, 118], [6, 73, 45, 14, 74, 46], [11, 54, 24, 16, 55, 25], [30, 46, 16, 2, 47, 17], [8, 132, 106, 4, 133, 107], [8, 75, 47, 13, 76, 48], [7, 54, 24, 22, 55, 25], [22, 45, 15, 13, 46, 16], [10, 142, 114, 2, 143, 115], [19, 74, 46, 4, 75, 47], [28, 50, 22, 6, 51, 23], [33, 46, 16, 4, 47, 17], [8, 152, 122, 4, 153, 123], [22, 73, 45, 3, 74, 46], [8, 53, 23, 26, 54, 24], [12, 45, 15, 28, 46, 16], [3, 147, 117, 10, 148, 118], [3, 73, 45, 23, 74, 46], [4, 54, 24, 31, 55, 25], [11, 45, 15, 31, 46, 16], [7, 146, 116, 7, 147, 117], [21, 73, 45, 7, 74, 46], [1, 53, 23, 37, 54, 24], [19, 45, 15, 26, 46, 16], [5, 145, 115, 10, 146, 116], [19, 75, 47, 10, 76, 48], [15, 54, 24, 25, 55, 25], [23, 45, 15, 25, 46, 16], [13, 145, 115, 3, 146, 116], [2, 74, 46, 29, 75, 47], [42, 54, 24, 1, 55, 25], [23, 45, 15, 28, 46, 16], [17, 145, 115], [10, 74, 46, 23, 75, 47], [10, 54, 24, 35, 55, 25], [19, 45, 15, 35, 46, 16], [17, 145, 115, 1, 146, 116], [14, 74, 46, 21, 75, 47], [29, 54, 24, 19, 55, 25], [11, 45, 15, 46, 46, 16], [13, 145, 115, 6, 146, 116], [14, 74, 46, 23, 75, 47], [44, 54, 24, 7, 55, 25], [59, 46, 16, 1, 47, 17], [12, 151, 121, 7, 152, 122], [12, 75, 47, 26, 76, 48], [39, 54, 24, 14, 55, 25], [22, 45, 15, 41, 46, 16], [6, 151, 121, 14, 152, 122], [6, 75, 47, 34, 76, 48], [46, 54, 24, 10, 55, 25], [2, 45, 15, 64, 46, 16], [17, 152, 122, 4, 153, 123], [29, 74, 46, 14, 75, 47], [49, 54, 24, 10, 55, 25], [24, 45, 15, 46, 46, 16], [4, 152, 122, 18, 153, 123], [13, 74, 46, 32, 75, 47], [48, 54, 24, 14, 55, 25], [42, 45, 15, 32, 46, 16], [20, 147, 117, 4, 148, 118], [40, 75, 47, 7, 76, 48], [43, 54, 24, 22, 55, 25], [10, 45, 15, 67, 46, 16], [19, 148, 118, 6, 149, 119], [18, 75, 47, 31, 76, 48], [34, 54, 24, 34, 55, 25], [20, 45, 15, 61, 46, 16]];
  const FORMAT_BITS = { L: 1, M: 0, Q: 3, H: 2 };
  const RS_BLOCK_INDEX = { L: 0, M: 1, Q: 2, H: 3 };
  const PAD0 = 0xEC;
  const PAD1 = 0x11;
  const G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
  const G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
  const G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);

  const EXP_TABLE = new Array(256);
  const LOG_TABLE = new Array(256);
  for (let i = 0; i < 8; i++) EXP_TABLE[i] = 1 << i;
  for (let i = 8; i < 256; i++) {
    EXP_TABLE[i] = EXP_TABLE[i - 4] ^ EXP_TABLE[i - 5] ^ EXP_TABLE[i - 6] ^ EXP_TABLE[i - 8];
  }
  for (let i = 0; i < 255; i++) LOG_TABLE[EXP_TABLE[i]] = i;

  function gexp(n) {
    while (n < 0) n += 255;
    return EXP_TABLE[n % 255];
  }

  function glog(n) {
    if (n < 1) throw new Error('glog(' + n + ')');
    return LOG_TABLE[n];
  }

  class Polynomial {
    constructor(num, shift) {
      let offset = 0;
      while (offset < num.length && num[offset] === 0) offset++;
      this.num = num.slice(offset).concat(new Array(shift).fill(0));
      if (!this.num.length) this.num = [0];
    }

    get length() { return this.num.length; }
    at(index) { return this.num[index]; }

    multiply(other) {
      const result = new Array(this.length + other.length - 1).fill(0);
      for (let i = 0; i < this.length; i++) {
        const a = this.at(i);
        if (!a) continue;
        for (let j = 0; j < other.length; j++) {
          const b = other.at(j);
          if (!b) continue;
          result[i + j] ^= gexp(glog(a) + glog(b));
        }
      }
      return new Polynomial(result, 0);
    }

    mod(other) {
      if (this.length - other.length < 0) return this;
      const ratio = glog(this.at(0)) - glog(other.at(0));
      const num = [];
      for (let i = 0; i < other.length; i++) {
        num.push(this.at(i) ^ gexp(glog(other.at(i)) + ratio));
      }
      if (this.length - other.length > 0) {
        num.push(...this.num.slice(other.length));
      }
      return new Polynomial(num, 0).mod(other);
    }
  }

  function bchDigit(data) {
    let digit = 0;
    while (data !== 0) { digit++; data >>>= 1; }
    return digit;
  }

  function BCHTypeInfo(data) {
    let d = data << 10;
    while (bchDigit(d) - bchDigit(G15) >= 0) {
      d ^= G15 << (bchDigit(d) - bchDigit(G15));
    }
    return ((data << 10) | d) ^ G15_MASK;
  }

  function BCHTypeNumber(data) {
    let d = data << 12;
    while (bchDigit(d) - bchDigit(G18) >= 0) {
      d ^= G18 << (bchDigit(d) - bchDigit(G18));
    }
    return (data << 12) | d;
  }

  function maskFunc(pattern) {
    switch (pattern) {
      case 0: return (i, j) => (i + j) % 2 === 0;
      case 1: return (i) => i % 2 === 0;
      case 2: return (_, j) => j % 3 === 0;
      case 3: return (i, j) => (i + j) % 3 === 0;
      case 4: return (i, j) => ((Math.floor(i / 2) + Math.floor(j / 3)) % 2) === 0;
      case 5: return (i, j) => (((i * j) % 2) + ((i * j) % 3)) === 0;
      case 6: return (i, j) => ((((i * j) % 2) + ((i * j) % 3)) % 2) === 0;
      case 7: return (i, j) => ((((i * j) % 3) + ((i + j) % 2)) % 2) === 0;
      default: throw new Error('Bad mask pattern');
    }
  }

  function rsBlocks(version, ecLevel) {
    const offset = RS_BLOCK_INDEX[ecLevel];
    const rsBlock = RS_BLOCK_TABLE[(version - 1) * 4 + offset];
    const blocks = [];
    for (let i = 0; i < rsBlock.length; i += 3) {
      const count = rsBlock[i], totalCount = rsBlock[i + 1], dataCount = rsBlock[i + 2];
      for (let j = 0; j < count; j++) blocks.push({ totalCount, dataCount });
    }
    return blocks;
  }

  function getByteLengthBits(version) {
    return version < 10 ? 8 : 16;
  }

  class BitBuffer {
    constructor() { this.buffer = []; this.length = 0; }
    put(num, length) {
      for (let i = 0; i < length; i++) this.putBit(((num >>> (length - i - 1)) & 1) === 1);
    }
    putBit(bit) {
      const bufIndex = Math.floor(this.length / 8);
      if (this.buffer.length <= bufIndex) this.buffer.push(0);
      if (bit) this.buffer[bufIndex] |= (0x80 >>> (this.length % 8));
      this.length++;
    }
  }

  const generatorPolyCache = new Map();
  function getGeneratorPoly(ecCount) {
    if (generatorPolyCache.has(ecCount)) return generatorPolyCache.get(ecCount);
    let poly = new Polynomial([1], 0);
    for (let i = 0; i < ecCount; i++) {
      poly = poly.multiply(new Polynomial([1, gexp(i)], 0));
    }
    generatorPolyCache.set(ecCount, poly);
    return poly;
  }

  function createBytes(buffer, blocks) {
    let offset = 0;
    let maxDcCount = 0;
    let maxEcCount = 0;
    const dcdata = [];
    const ecdata = [];

    for (const block of blocks) {
      const dcCount = block.dataCount;
      const ecCount = block.totalCount - dcCount;
      maxDcCount = Math.max(maxDcCount, dcCount);
      maxEcCount = Math.max(maxEcCount, ecCount);

      const currentDc = [];
      for (let i = 0; i < dcCount; i++) currentDc.push(0xFF & buffer.buffer[i + offset]);
      offset += dcCount;

      const rsPoly = getGeneratorPoly(ecCount);
      const rawPoly = new Polynomial(currentDc, rsPoly.length - 1);
      const modPoly = rawPoly.mod(rsPoly);
      const currentEc = [];
      const modOffset = modPoly.length - ecCount;
      for (let i = 0; i < ecCount; i++) {
        const modIndex = i + modOffset;
        currentEc.push(modIndex >= 0 ? modPoly.at(modIndex) : 0);
      }
      dcdata.push(currentDc);
      ecdata.push(currentEc);
    }

    const data = [];
    for (let i = 0; i < maxDcCount; i++) for (const dc of dcdata) if (i < dc.length) data.push(dc[i]);
    for (let i = 0; i < maxEcCount; i++) for (const ec of ecdata) if (i < ec.length) data.push(ec[i]);
    return data;
  }

  function createData(version, ecLevel, bytes) {
    const buffer = new BitBuffer();
    buffer.put(0b0100, 4);
    buffer.put(bytes.length, getByteLengthBits(version));
    for (const b of bytes) buffer.put(b, 8);

    const blocks = rsBlocks(version, ecLevel);
    const bitLimit = blocks.reduce((sum, block) => sum + block.dataCount * 8, 0);
    if (buffer.length > bitLimit) throw new Error('Code length overflow');

    for (let i = 0; i < Math.min(bitLimit - buffer.length, 4); i++) buffer.putBit(false);
    const delimit = buffer.length % 8;
    if (delimit) for (let i = 0; i < 8 - delimit; i++) buffer.putBit(false);

    const bytesToFill = Math.floor((bitLimit - buffer.length) / 8);
    for (let i = 0; i < bytesToFill; i++) buffer.put(i % 2 === 0 ? PAD0 : PAD1, 8);
    return createBytes(buffer, blocks);
  }

  function versionFits(version, ecLevel, bytes) {
    const bitLimit = rsBlocks(version, ecLevel).reduce((sum, block) => sum + block.dataCount * 8, 0);
    const neededBits = 4 + getByteLengthBits(version) + bytes.length * 8;
    return neededBits <= bitLimit;
  }

  function lostPoint(modules) {
    const modulesCount = modules.length;
    let lostPointValue = 0;
    const container = new Array(modulesCount + 1).fill(0);

    for (let row = 0; row < modulesCount; row++) {
      let previousColor = modules[row][0];
      let length = 0;
      for (let col = 0; col < modulesCount; col++) {
        if (modules[row][col] === previousColor) length++;
        else {
          if (length >= 5) container[length]++;
          length = 1;
          previousColor = modules[row][col];
        }
      }
      if (length >= 5) container[length]++;
    }

    for (let col = 0; col < modulesCount; col++) {
      let previousColor = modules[0][col];
      let length = 0;
      for (let row = 0; row < modulesCount; row++) {
        if (modules[row][col] === previousColor) length++;
        else {
          if (length >= 5) container[length]++;
          length = 1;
          previousColor = modules[row][col];
        }
      }
      if (length >= 5) container[length]++;
    }

    for (let eachLength = 5; eachLength <= modulesCount; eachLength++) lostPointValue += container[eachLength] * (eachLength - 2);

    for (let row = 0; row < modulesCount - 1; row++) {
      for (let col = 0; col < modulesCount - 1; col++) {
        const color = modules[row][col];
        if (color === modules[row][col + 1] && color === modules[row + 1][col] && color === modules[row + 1][col + 1]) lostPointValue += 3;
      }
    }

    for (let row = 0; row < modulesCount; row++) {
      for (let col = 0; col < modulesCount - 10; col++) {
        const r = modules[row];
        if (!r[col + 1] && r[col + 4] && !r[col + 5] && r[col + 6] && !r[col + 9] &&
          ((r[col] && r[col + 2] && r[col + 3] && !r[col + 7] && !r[col + 8] && !r[col + 10]) ||
          (!r[col] && !r[col + 2] && !r[col + 3] && r[col + 7] && r[col + 8] && r[col + 10]))) {
          lostPointValue += 40;
        }
      }
    }

    for (let col = 0; col < modulesCount; col++) {
      for (let row = 0; row < modulesCount - 10; row++) {
        if (!modules[row + 1][col] && modules[row + 4][col] && !modules[row + 5][col] && modules[row + 6][col] && !modules[row + 9][col] &&
          ((modules[row][col] && modules[row + 2][col] && modules[row + 3][col] && !modules[row + 7][col] && !modules[row + 8][col] && !modules[row + 10][col]) ||
          (!modules[row][col] && !modules[row + 2][col] && !modules[row + 3][col] && modules[row + 7][col] && modules[row + 8][col] && modules[row + 10][col]))) {
          lostPointValue += 40;
        }
      }
    }

    let darkCount = 0;
    for (const row of modules) for (const cell of row) if (cell) darkCount++;
    const percent = darkCount / (modulesCount * modulesCount);
    lostPointValue += Math.floor(Math.abs(percent * 100 - 50) / 5) * 10;
    return lostPointValue;
  }

  class QRCode {
    constructor(version, ecLevel, dataBytes) {
      this.version = version;
      this.ecLevel = ecLevel;
      this.dataCache = createData(version, ecLevel, dataBytes);
      this.modulesCount = version * 4 + 17;
      this.modules = [];
    }

    make() {
      let bestPattern = 0;
      let minLostPoint = 0;
      for (let i = 0; i < 8; i++) {
        this.makeImpl(true, i);
        const score = lostPoint(this.modules);
        if (i === 0 || score < minLostPoint) {
          minLostPoint = score;
          bestPattern = i;
        }
      }
      this.makeImpl(false, bestPattern);
      return this.modules;
    }

    makeImpl(test, maskPattern) {
      this.modules = Array.from({ length: this.modulesCount }, () => Array(this.modulesCount).fill(null));
      this.setupPositionProbePattern(0, 0);
      this.setupPositionProbePattern(this.modulesCount - 7, 0);
      this.setupPositionProbePattern(0, this.modulesCount - 7);
      this.setupPositionAdjustPattern();
      this.setupTimingPattern();
      this.setupTypeInfo(test, maskPattern);
      if (this.version >= 7) this.setupTypeNumber(test);
      this.mapData(this.dataCache, maskPattern);
    }

    setupPositionProbePattern(row, col) {
      for (let r = -1; r < 8; r++) {
        if (row + r <= -1 || this.modulesCount <= row + r) continue;
        for (let c = -1; c < 8; c++) {
          if (col + c <= -1 || this.modulesCount <= col + c) continue;
          this.modules[row + r][col + c] = ((0 <= r && r <= 6 && (c === 0 || c === 6)) || (0 <= c && c <= 6 && (r === 0 || r === 6)) || (2 <= r && r <= 4 && 2 <= c && c <= 4));
        }
      }
    }

    setupTimingPattern() {
      for (let r = 8; r < this.modulesCount - 8; r++) if (this.modules[r][6] === null) this.modules[r][6] = r % 2 === 0;
      for (let c = 8; c < this.modulesCount - 8; c++) if (this.modules[6][c] === null) this.modules[6][c] = c % 2 === 0;
    }

    setupPositionAdjustPattern() {
      const pos = PATTERN_POSITION_TABLE[this.version - 1];
      for (let i = 0; i < pos.length; i++) {
        const row = pos[i];
        for (let j = 0; j < pos.length; j++) {
          const col = pos[j];
          if (this.modules[row][col] !== null) continue;
          for (let r = -2; r < 3; r++) {
            for (let c = -2; c < 3; c++) {
              this.modules[row + r][col + c] = (r === -2 || r === 2 || c === -2 || c === 2 || (r === 0 && c === 0));
            }
          }
        }
      }
    }

    setupTypeNumber(test) {
      const bits = BCHTypeNumber(this.version);
      for (let i = 0; i < 18; i++) {
        const mod = !test && (((bits >>> i) & 1) === 1);
        this.modules[Math.floor(i / 3)][(i % 3) + this.modulesCount - 11] = mod;
      }
      for (let i = 0; i < 18; i++) {
        const mod = !test && (((bits >>> i) & 1) === 1);
        this.modules[(i % 3) + this.modulesCount - 11][Math.floor(i / 3)] = mod;
      }
    }

    setupTypeInfo(test, maskPattern) {
      const data = (FORMAT_BITS[this.ecLevel] << 3) | maskPattern;
      const bits = BCHTypeInfo(data);
      for (let i = 0; i < 15; i++) {
        const mod = !test && (((bits >>> i) & 1) === 1);
        if (i < 6) this.modules[i][8] = mod;
        else if (i < 8) this.modules[i + 1][8] = mod;
        else this.modules[this.modulesCount - 15 + i][8] = mod;
      }
      for (let i = 0; i < 15; i++) {
        const mod = !test && (((bits >>> i) & 1) === 1);
        if (i < 8) this.modules[8][this.modulesCount - i - 1] = mod;
        else if (i < 9) this.modules[8][15 - i] = mod;
        else this.modules[8][15 - i - 1] = mod;
      }
      this.modules[this.modulesCount - 8][8] = !test;
    }

    mapData(data, maskPattern) {
      let inc = -1;
      let row = this.modulesCount - 1;
      let bitIndex = 7;
      let byteIndex = 0;
      const mask = maskFunc(maskPattern);
      for (let colStart = this.modulesCount - 1; colStart > 0; colStart -= 2) {
        let col = colStart;
        if (col <= 6) col -= 1;
        while (true) {
          for (const c of [col, col - 1]) {
            if (this.modules[row][c] === null) {
              let dark = false;
              if (byteIndex < data.length) dark = (((data[byteIndex] >>> bitIndex) & 1) === 1);
              if (mask(row, c)) dark = !dark;
              this.modules[row][c] = dark;
              bitIndex--;
              if (bitIndex === -1) {
                byteIndex++;
                bitIndex = 7;
              }
            }
          }
          row += inc;
          if (row < 0 || this.modulesCount <= row) {
            row -= inc;
            inc = -inc;
            break;
          }
        }
      }
    }
  }

  function makeQr(text, ecLevel = 'M') {
    const bytes = Array.from(new TextEncoder().encode(text));
    let version = 1;
    for (; version <= 40; version++) if (versionFits(version, ecLevel, bytes)) break;
    if (version > 40) throw new Error('Data too large for QR code');
    return new QRCode(version, ecLevel, bytes).make();
  }

  function matrixToSvg(matrix, border = 4) {
    const size = matrix.length + border * 2;
    let path = '';
    for (let y = 0; y < matrix.length; y++) {
      let start = null;
      for (let x = 0; x <= matrix.length; x++) {
        const dark = x < matrix.length && matrix[y][x];
        if (dark && start === null) start = x;
        if ((!dark || x === matrix.length) && start !== null) {
          path += `M${start + border} ${y + border}h${x - start}v1H${start + border}z`;
          start = null;
        }
      }
    }
    return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" shape-rendering="crispEdges" role="img" aria-label="QR code"><rect width="100%" height="100%" fill="#fff"/><path d="${path}" fill="#000"/></svg>`;
  }

  function downloadSvg(button) {
    const wrap = button.closest('.bs2fa-totp-qr-wrap');
    if (!wrap) return;
    const svg = wrap.querySelector('.bs2fa-totp-qr svg');
    if (!svg) return;
    const filename = (wrap.querySelector('.bs2fa-totp-qr')?.getAttribute('data-filename')) || 'bs2fa-totp.svg';
    const blob = new Blob([svg.outerHTML], { type: 'image/svg+xml;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    link.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }

  function renderQrNodes() {
    document.querySelectorAll('.bs2fa-totp-qr[data-qr]').forEach((node) => {
      if (node.dataset.rendered === '1') return;
      const qrText = node.getAttribute('data-qr') || '';
      if (!qrText) return;
      try {
        const matrix = makeQr(qrText, 'M');
        node.innerHTML = matrixToSvg(matrix, 4);
        node.dataset.rendered = '1';
      } catch (error) {
        node.innerHTML = '<p>QR rendering failed. Use the setup URI below.</p>';
      }
    });
  }

  document.addEventListener('click', function (event) {
    const button = event.target.closest('.bs2fa-qr-download');
    if (!button) return;
    event.preventDefault();
    downloadSvg(button);
  });

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', renderQrNodes);
  else renderQrNodes();
})();
