Testbook Plus

Enhances Testbook UI, blocks tracking, and auto-crawls & downloads question papers as clean Markdown.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Testbook Plus
// @namespace    tb-plus
// @version      2.0.0
// @author       quantavil
// @description  Enhances Testbook UI, blocks tracking, and auto-crawls & downloads question papers as clean Markdown.
// @license      MIT
// @match        https://testbook.com/*
// @match        https://*.testbook.com/*
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  var __defProp = Object.defineProperty;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  const ICONS = {
    idle: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 4v12m0 0l-4-4m4 4l4-4M4 20h16"/></svg>`,
    loading: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle class="tb-sp" cx="12" cy="12" r="10" stroke-dasharray="32" stroke-linecap="round"/><path class="tb-x" d="M9 9l6 6M15 9l-6 6"/></svg>`,
    success: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path class="tb-chk" d="M5 13l4 4L19 7"/></svg>`,
    error: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>`
  };
  const STYLES = `
#tb-fab { position:fixed; bottom:24px; right:24px; width:44px; height:44px; border-radius:22px; background:#000; color:#fff; display:grid; place-items:center; cursor:pointer; transition:all .3s cubic-bezier(.25,.8,.25,1); box-shadow:0 4px 12px rgba(0,0,0,.15); z-index:99999; border:1px solid transparent; }
#tb-fab svg { width:20px; height:20px; transition:all .3s; }
#tb-fab[data-s="idle"] { opacity:0.3; }
#tb-fab[data-s="idle"]:hover { opacity:1; box-shadow:0 6px 16px rgba(0,0,0,.25); }
#tb-fab[data-s="idle"]:active { transform:scale(.92); }
#tb-fab[data-s="success"] { background:#fff; color:#000; border-color:#e5e5e5; pointer-events:none; transform:scale(1.1); }
#tb-fab[data-s="error"] { background:#fff; color:#000; border-color:#000; animation:sh .4s; }
#tb-fab::after { content:attr(data-t); position:absolute; right:56px; background:#000; color:#fff; padding:6px 12px; border-radius:6px; font:500 12px/1.2 -apple-system,sans-serif; opacity:0; pointer-events:none; transition:opacity .2s; white-space:nowrap; box-shadow:0 2px 8px rgba(0,0,0,.2); }
#tb-fab:hover::after, #tb-fab[data-s="loading"]::after, #tb-fab[data-s="error"]::after { opacity:1; }
#tb-fab[data-s="idle"]:not(:hover)::after { opacity:0; }
#tb-fab .tb-x { opacity:0; transition:opacity .2s; }
#tb-fab[data-s="loading"]:hover .tb-sp { opacity:0; }
#tb-fab[data-s="loading"]:hover .tb-x { opacity:1; }
#tb-fab[data-s="loading"]:hover::after { content:"Cancel"; }
@keyframes sh { 25%,75%{transform:translateX(-4px)} 50%{transform:translateX(4px)} }
#tb-fab .tb-chk { stroke-dasharray:24; stroke-dashoffset:24; animation:tb-draw .4s forwards .1s; }
@keyframes tb-draw { to { stroke-dashoffset:0; } }
`;
  class DownloaderUI {
    constructor(onStart, onCancel) {
      __publicField(this, "el", document.createElement("div"));
      __publicField(this, "state", "idle");
      __publicField(this, "spinRAF", null);
      __publicField(this, "timer", null);
      this.onStart = onStart;
      this.onCancel = onCancel;
      if (!document.getElementById("tb-css")) document.head.insertAdjacentHTML("beforeend", `<style id="tb-css">${STYLES}</style>`);
      this.el.id = "tb-fab";
      this.el.onclick = () => {
        if (this.state === "idle" || this.state === "error") {
          this.setState("loading");
          return this.onStart();
        }
        if (this.state === "loading") {
          this.setState("idle");
          return this.onCancel();
        }
      };
      this.setState("idle");
    }
    startSpin() {
      const el = this.el.querySelector(".tb-sp");
      if (!el) return;
      let last = null;
      let angle = 0;
      const tick = (now) => {
        if (last !== null) angle = (angle + (now - last) * 0.36) % 360;
        last = now;
        el.setAttribute("transform", `rotate(${angle} 12 12)`);
        this.spinRAF = requestAnimationFrame(tick);
      };
      this.spinRAF = requestAnimationFrame(tick);
    }
    stopSpin() {
      if (this.spinRAF !== null) {
        cancelAnimationFrame(this.spinRAF);
        this.spinRAF = null;
      }
    }
    setState(s) {
      this.stopSpin();
      if (this.timer) {
        clearTimeout(this.timer);
        this.timer = null;
      }
      this.state = s;
      this.el.dataset.s = s;
      this.el.innerHTML = ICONS[s];
      this.el.dataset.t = { idle: "Download", loading: "Crawling…", success: "Done!", error: "Failed — retry" }[s];
      if (s === "loading") this.startSpin();
    }
    mount() {
      document.body.appendChild(this.el);
    }
    updateStatus(m) {
      if (this.state === "loading") this.el.dataset.t = m;
    }
    error(m) {
      this.setState("error");
      if (m) this.el.dataset.t = `Error: ${m}`;
      this.timer = window.setTimeout(() => {
        if (this.state === "error") this.setState("idle");
        this.timer = null;
      }, 5e3);
    }
    finish() {
      this.setState("success");
      this.timer = window.setTimeout(() => {
        if (this.state === "success") this.setState("idle");
        this.timer = null;
      }, 2500);
    }
  }
  function extend(destination) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];
      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) destination[key] = source[key];
      }
    }
    return destination;
  }
  function repeat(character, count) {
    return Array(count + 1).join(character);
  }
  function trimLeadingNewlines(string) {
    return string.replace(/^\n*/, "");
  }
  function trimTrailingNewlines(string) {
    var indexEnd = string.length;
    while (indexEnd > 0 && string[indexEnd - 1] === "\n") indexEnd--;
    return string.substring(0, indexEnd);
  }
  function trimNewlines(string) {
    return trimTrailingNewlines(trimLeadingNewlines(string));
  }
  var blockElements = ["ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", "CENTER", "DD", "DIR", "DIV", "DL", "DT", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", "HGROUP", "HR", "HTML", "ISINDEX", "LI", "MAIN", "MENU", "NAV", "NOFRAMES", "NOSCRIPT", "OL", "OUTPUT", "P", "PRE", "SECTION", "TABLE", "TBODY", "TD", "TFOOT", "TH", "THEAD", "TR", "UL"];
  function isBlock(node) {
    return is(node, blockElements);
  }
  var voidElements = ["AREA", "BASE", "BR", "COL", "COMMAND", "EMBED", "HR", "IMG", "INPUT", "KEYGEN", "LINK", "META", "PARAM", "SOURCE", "TRACK", "WBR"];
  function isVoid(node) {
    return is(node, voidElements);
  }
  function hasVoid(node) {
    return has(node, voidElements);
  }
  var meaningfulWhenBlankElements = ["A", "TABLE", "THEAD", "TBODY", "TFOOT", "TH", "TD", "IFRAME", "SCRIPT", "AUDIO", "VIDEO"];
  function isMeaningfulWhenBlank(node) {
    return is(node, meaningfulWhenBlankElements);
  }
  function hasMeaningfulWhenBlank(node) {
    return has(node, meaningfulWhenBlankElements);
  }
  function is(node, tagNames) {
    return tagNames.indexOf(node.nodeName) >= 0;
  }
  function has(node, tagNames) {
    return node.getElementsByTagName && tagNames.some(function(tagName) {
      return node.getElementsByTagName(tagName).length;
    });
  }
  var markdownEscapes = [[/\\/g, "\\\\"], [/\*/g, "\\*"], [/^-/g, "\\-"], [/^\+ /g, "\\+ "], [/^(=+)/g, "\\$1"], [/^(#{1,6}) /g, "\\$1 "], [/`/g, "\\`"], [/^~~~/g, "\\~~~"], [/\[/g, "\\["], [/\]/g, "\\]"], [/^>/g, "\\>"], [/_/g, "\\_"], [/^(\d+)\. /g, "$1\\. "]];
  function escapeMarkdown(string) {
    return markdownEscapes.reduce(function(accumulator, escape) {
      return accumulator.replace(escape[0], escape[1]);
    }, string);
  }
  var rules$1 = {};
  rules$1.paragraph = {
    filter: "p",
    replacement: function(content) {
      return "\n\n" + content + "\n\n";
    }
  };
  rules$1.lineBreak = {
    filter: "br",
    replacement: function(content, node, options) {
      return options.br + "\n";
    }
  };
  rules$1.heading = {
    filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
    replacement: function(content, node, options) {
      var hLevel = Number(node.nodeName.charAt(1));
      if (options.headingStyle === "setext" && hLevel < 3) {
        var underline = repeat(hLevel === 1 ? "=" : "-", content.length);
        return "\n\n" + content + "\n" + underline + "\n\n";
      } else {
        return "\n\n" + repeat("#", hLevel) + " " + content + "\n\n";
      }
    }
  };
  rules$1.blockquote = {
    filter: "blockquote",
    replacement: function(content) {
      content = trimNewlines(content).replace(/^/gm, "> ");
      return "\n\n" + content + "\n\n";
    }
  };
  rules$1.list = {
    filter: ["ul", "ol"],
    replacement: function(content, node) {
      var parent = node.parentNode;
      if (parent.nodeName === "LI" && parent.lastElementChild === node) {
        return "\n" + content;
      } else {
        return "\n\n" + content + "\n\n";
      }
    }
  };
  rules$1.listItem = {
    filter: "li",
    replacement: function(content, node, options) {
      var prefix = options.bulletListMarker + "   ";
      var parent = node.parentNode;
      if (parent.nodeName === "OL") {
        var start = parent.getAttribute("start");
        var index = Array.prototype.indexOf.call(parent.children, node);
        prefix = (start ? Number(start) + index : index + 1) + ".  ";
      }
      var isParagraph = /\n$/.test(content);
      content = trimNewlines(content) + (isParagraph ? "\n" : "");
      content = content.replace(/\n/gm, "\n" + " ".repeat(prefix.length));
      return prefix + content + (node.nextSibling ? "\n" : "");
    }
  };
  rules$1.indentedCodeBlock = {
    filter: function(node, options) {
      return options.codeBlockStyle === "indented" && node.nodeName === "PRE" && node.firstChild && node.firstChild.nodeName === "CODE";
    },
    replacement: function(content, node, options) {
      return "\n\n    " + node.firstChild.textContent.replace(/\n/g, "\n    ") + "\n\n";
    }
  };
  rules$1.fencedCodeBlock = {
    filter: function(node, options) {
      return options.codeBlockStyle === "fenced" && node.nodeName === "PRE" && node.firstChild && node.firstChild.nodeName === "CODE";
    },
    replacement: function(content, node, options) {
      var className = node.firstChild.getAttribute("class") || "";
      var language = (className.match(/language-(\S+)/) || [null, ""])[1];
      var code = node.firstChild.textContent;
      var fenceChar = options.fence.charAt(0);
      var fenceSize = 3;
      var fenceInCodeRegex = new RegExp("^" + fenceChar + "{3,}", "gm");
      var match;
      while (match = fenceInCodeRegex.exec(code)) {
        if (match[0].length >= fenceSize) {
          fenceSize = match[0].length + 1;
        }
      }
      var fence = repeat(fenceChar, fenceSize);
      return "\n\n" + fence + language + "\n" + code.replace(/\n$/, "") + "\n" + fence + "\n\n";
    }
  };
  rules$1.horizontalRule = {
    filter: "hr",
    replacement: function(content, node, options) {
      return "\n\n" + options.hr + "\n\n";
    }
  };
  rules$1.inlineLink = {
    filter: function(node, options) {
      return options.linkStyle === "inlined" && node.nodeName === "A" && node.getAttribute("href");
    },
    replacement: function(content, node) {
      var href = escapeLinkDestination(node.getAttribute("href"));
      var title = escapeLinkTitle(cleanAttribute(node.getAttribute("title")));
      var titlePart = title ? ' "' + title + '"' : "";
      return "[" + content + "](" + href + titlePart + ")";
    }
  };
  rules$1.referenceLink = {
    filter: function(node, options) {
      return options.linkStyle === "referenced" && node.nodeName === "A" && node.getAttribute("href");
    },
    replacement: function(content, node, options) {
      var href = escapeLinkDestination(node.getAttribute("href"));
      var title = cleanAttribute(node.getAttribute("title"));
      if (title) title = ' "' + escapeLinkTitle(title) + '"';
      var replacement;
      var reference;
      switch (options.linkReferenceStyle) {
        case "collapsed":
          replacement = "[" + content + "][]";
          reference = "[" + content + "]: " + href + title;
          break;
        case "shortcut":
          replacement = "[" + content + "]";
          reference = "[" + content + "]: " + href + title;
          break;
        default:
          var id = this.references.length + 1;
          replacement = "[" + content + "][" + id + "]";
          reference = "[" + id + "]: " + href + title;
      }
      this.references.push(reference);
      return replacement;
    },
    references: [],
    append: function(options) {
      var references = "";
      if (this.references.length) {
        references = "\n\n" + this.references.join("\n") + "\n\n";
        this.references = [];
      }
      return references;
    }
  };
  rules$1.emphasis = {
    filter: ["em", "i"],
    replacement: function(content, node, options) {
      if (!content.trim()) return "";
      return options.emDelimiter + content + options.emDelimiter;
    }
  };
  rules$1.strong = {
    filter: ["strong", "b"],
    replacement: function(content, node, options) {
      if (!content.trim()) return "";
      return options.strongDelimiter + content + options.strongDelimiter;
    }
  };
  rules$1.code = {
    filter: function(node) {
      var hasSiblings = node.previousSibling || node.nextSibling;
      var isCodeBlock = node.parentNode.nodeName === "PRE" && !hasSiblings;
      return node.nodeName === "CODE" && !isCodeBlock;
    },
    replacement: function(content) {
      if (!content) return "";
      content = content.replace(/\r?\n|\r/g, " ");
      var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? " " : "";
      var delimiter = "`";
      var matches = content.match(/`+/gm) || [];
      while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + "`";
      return delimiter + extraSpace + content + extraSpace + delimiter;
    }
  };
  rules$1.image = {
    filter: "img",
    replacement: function(content, node) {
      var alt = escapeMarkdown(cleanAttribute(node.getAttribute("alt")));
      var src = escapeLinkDestination(node.getAttribute("src") || "");
      var title = cleanAttribute(node.getAttribute("title"));
      var titlePart = title ? ' "' + escapeLinkTitle(title) + '"' : "";
      return src ? "![" + alt + "](" + src + titlePart + ")" : "";
    }
  };
  function cleanAttribute(attribute) {
    return attribute ? attribute.replace(/(\n+\s*)+/g, "\n") : "";
  }
  function escapeLinkDestination(destination) {
    var escaped = destination.replace(/([<>()])/g, "\\$1");
    return escaped.indexOf(" ") >= 0 ? "<" + escaped + ">" : escaped;
  }
  function escapeLinkTitle(title) {
    return title.replace(/"/g, '\\"');
  }
  function Rules(options) {
    this.options = options;
    this._keep = [];
    this._remove = [];
    this.blankRule = {
      replacement: options.blankReplacement
    };
    this.keepReplacement = options.keepReplacement;
    this.defaultRule = {
      replacement: options.defaultReplacement
    };
    this.array = [];
    for (var key in options.rules) this.array.push(options.rules[key]);
  }
  Rules.prototype = {
    add: function(key, rule) {
      this.array.unshift(rule);
    },
    keep: function(filter) {
      this._keep.unshift({
        filter,
        replacement: this.keepReplacement
      });
    },
    remove: function(filter) {
      this._remove.unshift({
        filter,
        replacement: function() {
          return "";
        }
      });
    },
    forNode: function(node) {
      if (node.isBlank) return this.blankRule;
      var rule;
      if (rule = findRule(this.array, node, this.options)) return rule;
      if (rule = findRule(this._keep, node, this.options)) return rule;
      if (rule = findRule(this._remove, node, this.options)) return rule;
      return this.defaultRule;
    },
    forEach: function(fn) {
      for (var i = 0; i < this.array.length; i++) fn(this.array[i], i);
    }
  };
  function findRule(rules2, node, options) {
    for (var i = 0; i < rules2.length; i++) {
      var rule = rules2[i];
      if (filterValue(rule, node, options)) return rule;
    }
    return void 0;
  }
  function filterValue(rule, node, options) {
    var filter = rule.filter;
    if (typeof filter === "string") {
      if (filter === node.nodeName.toLowerCase()) return true;
    } else if (Array.isArray(filter)) {
      if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true;
    } else if (typeof filter === "function") {
      if (filter.call(rule, node, options)) return true;
    } else {
      throw new TypeError("`filter` needs to be a string, array, or function");
    }
  }
  function collapseWhitespace(options) {
    var element = options.element;
    var isBlock2 = options.isBlock;
    var isVoid2 = options.isVoid;
    var isPre = options.isPre || function(node2) {
      return node2.nodeName === "PRE";
    };
    if (!element.firstChild || isPre(element)) return;
    var prevText = null;
    var keepLeadingWs = false;
    var prev = null;
    var node = next(prev, element, isPre);
    while (node !== element) {
      if (node.nodeType === 3 || node.nodeType === 4) {
        var text = node.data.replace(/[ \r\n\t]+/g, " ");
        if ((!prevText || / $/.test(prevText.data)) && !keepLeadingWs && text[0] === " ") {
          text = text.substr(1);
        }
        if (!text) {
          node = remove(node);
          continue;
        }
        node.data = text;
        prevText = node;
      } else if (node.nodeType === 1) {
        if (isBlock2(node) || node.nodeName === "BR") {
          if (prevText) {
            prevText.data = prevText.data.replace(/ $/, "");
          }
          prevText = null;
          keepLeadingWs = false;
        } else if (isVoid2(node) || isPre(node)) {
          prevText = null;
          keepLeadingWs = true;
        } else if (prevText) {
          keepLeadingWs = false;
        }
      } else {
        node = remove(node);
        continue;
      }
      var nextNode = next(prev, node, isPre);
      prev = node;
      node = nextNode;
    }
    if (prevText) {
      prevText.data = prevText.data.replace(/ $/, "");
      if (!prevText.data) {
        remove(prevText);
      }
    }
  }
  function remove(node) {
    var next2 = node.nextSibling || node.parentNode;
    node.parentNode.removeChild(node);
    return next2;
  }
  function next(prev, current, isPre) {
    if (prev && prev.parentNode === current || isPre(current)) {
      return current.nextSibling || current.parentNode;
    }
    return current.firstChild || current.nextSibling || current.parentNode;
  }
  var root = typeof window !== "undefined" ? window : {};
  function canParseHTMLNatively() {
    var Parser = root.DOMParser;
    var canParse = false;
    try {
      if (new Parser().parseFromString("", "text/html")) {
        canParse = true;
      }
    } catch (e) {
    }
    return canParse;
  }
  function createHTMLParser() {
    var Parser = function() {
    };
    {
      if (shouldUseActiveX()) {
        Parser.prototype.parseFromString = function(string) {
          var doc = new window.ActiveXObject("htmlfile");
          doc.designMode = "on";
          doc.open();
          doc.write(string);
          doc.close();
          return doc;
        };
      } else {
        Parser.prototype.parseFromString = function(string) {
          var doc = document.implementation.createHTMLDocument("");
          doc.open();
          doc.write(string);
          doc.close();
          return doc;
        };
      }
    }
    return Parser;
  }
  function shouldUseActiveX() {
    var useActiveX = false;
    try {
      document.implementation.createHTMLDocument("").open();
    } catch (e) {
      if (root.ActiveXObject) useActiveX = true;
    }
    return useActiveX;
  }
  var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
  function RootNode(input, options) {
    var root2;
    if (typeof input === "string") {
      var doc = htmlParser().parseFromString(
        // DOM parsers arrange elements in the <head> and <body>.
        // Wrapping in a custom element ensures elements are reliably arranged in
        // a single element.
        '<x-turndown id="turndown-root">' + input + "</x-turndown>",
        "text/html"
      );
      root2 = doc.getElementById("turndown-root");
    } else {
      root2 = input.cloneNode(true);
    }
    collapseWhitespace({
      element: root2,
      isBlock,
      isVoid,
      isPre: options.preformattedCode ? isPreOrCode : null
    });
    return root2;
  }
  var _htmlParser;
  function htmlParser() {
    _htmlParser = _htmlParser || new HTMLParser();
    return _htmlParser;
  }
  function isPreOrCode(node) {
    return node.nodeName === "PRE" || node.nodeName === "CODE";
  }
  function Node(node, options) {
    node.isBlock = isBlock(node);
    node.isCode = node.nodeName === "CODE" || node.parentNode.isCode;
    node.isBlank = isBlank(node);
    node.flankingWhitespace = flankingWhitespace(node, options);
    return node;
  }
  function isBlank(node) {
    return !isVoid(node) && !isMeaningfulWhenBlank(node) && /^\s*$/i.test(node.textContent) && !hasVoid(node) && !hasMeaningfulWhenBlank(node);
  }
  function flankingWhitespace(node, options) {
    if (node.isBlock || options.preformattedCode && node.isCode) {
      return {
        leading: "",
        trailing: ""
      };
    }
    var edges = edgeWhitespace(node.textContent);
    if (edges.leadingAscii && isFlankedByWhitespace("left", node, options)) {
      edges.leading = edges.leadingNonAscii;
    }
    if (edges.trailingAscii && isFlankedByWhitespace("right", node, options)) {
      edges.trailing = edges.trailingNonAscii;
    }
    return {
      leading: edges.leading,
      trailing: edges.trailing
    };
  }
  function edgeWhitespace(string) {
    var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/);
    return {
      leading: m[1],
      // whole string for whitespace-only strings
      leadingAscii: m[2],
      leadingNonAscii: m[3],
      trailing: m[4],
      // empty for whitespace-only strings
      trailingNonAscii: m[5],
      trailingAscii: m[6]
    };
  }
  function isFlankedByWhitespace(side, node, options) {
    var sibling;
    var regExp;
    var isFlanked;
    if (side === "left") {
      sibling = node.previousSibling;
      regExp = / $/;
    } else {
      sibling = node.nextSibling;
      regExp = /^ /;
    }
    if (sibling) {
      if (sibling.nodeType === 3) {
        isFlanked = regExp.test(sibling.nodeValue);
      } else if (options.preformattedCode && sibling.nodeName === "CODE") {
        isFlanked = false;
      } else if (sibling.nodeType === 1 && !isBlock(sibling)) {
        isFlanked = regExp.test(sibling.textContent);
      }
    }
    return isFlanked;
  }
  var reduce = Array.prototype.reduce;
  function TurndownService(options) {
    if (!(this instanceof TurndownService)) return new TurndownService(options);
    var defaults = {
      rules: rules$1,
      headingStyle: "setext",
      hr: "* * *",
      bulletListMarker: "*",
      codeBlockStyle: "indented",
      fence: "```",
      emDelimiter: "_",
      strongDelimiter: "**",
      linkStyle: "inlined",
      linkReferenceStyle: "full",
      br: "  ",
      preformattedCode: false,
      blankReplacement: function(content, node) {
        return node.isBlock ? "\n\n" : "";
      },
      keepReplacement: function(content, node) {
        return node.isBlock ? "\n\n" + node.outerHTML + "\n\n" : node.outerHTML;
      },
      defaultReplacement: function(content, node) {
        return node.isBlock ? "\n\n" + content + "\n\n" : content;
      }
    };
    this.options = extend({}, defaults, options);
    this.rules = new Rules(this.options);
  }
  TurndownService.prototype = {
    /**
     * The entry point for converting a string or DOM node to Markdown
     * @public
     * @param {String|HTMLElement} input The string or DOM node to convert
     * @returns A Markdown representation of the input
     * @type String
     */
    turndown: function(input) {
      if (!canConvert(input)) {
        throw new TypeError(input + " is not a string, or an element/document/fragment node.");
      }
      if (input === "") return "";
      var output = process.call(this, new RootNode(input, this.options));
      return postProcess.call(this, output);
    },
    /**
     * Add one or more plugins
     * @public
     * @param {Function|Array} plugin The plugin or array of plugins to add
     * @returns The Turndown instance for chaining
     * @type Object
     */
    use: function(plugin) {
      if (Array.isArray(plugin)) {
        for (var i = 0; i < plugin.length; i++) this.use(plugin[i]);
      } else if (typeof plugin === "function") {
        plugin(this);
      } else {
        throw new TypeError("plugin must be a Function or an Array of Functions");
      }
      return this;
    },
    /**
     * Adds a rule
     * @public
     * @param {String} key The unique key of the rule
     * @param {Object} rule The rule
     * @returns The Turndown instance for chaining
     * @type Object
     */
    addRule: function(key, rule) {
      this.rules.add(key, rule);
      return this;
    },
    /**
     * Keep a node (as HTML) that matches the filter
     * @public
     * @param {String|Array|Function} filter The unique key of the rule
     * @returns The Turndown instance for chaining
     * @type Object
     */
    keep: function(filter) {
      this.rules.keep(filter);
      return this;
    },
    /**
     * Remove a node that matches the filter
     * @public
     * @param {String|Array|Function} filter The unique key of the rule
     * @returns The Turndown instance for chaining
     * @type Object
     */
    remove: function(filter) {
      this.rules.remove(filter);
      return this;
    },
    /**
     * Escapes Markdown syntax
     * @public
     * @param {String} string The string to escape
     * @returns A string with Markdown syntax escaped
     * @type String
     */
    escape: function(string) {
      return escapeMarkdown(string);
    }
  };
  function process(parentNode) {
    var self = this;
    return reduce.call(parentNode.childNodes, function(output, node) {
      node = new Node(node, self.options);
      var replacement = "";
      if (node.nodeType === 3) {
        replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue);
      } else if (node.nodeType === 1) {
        replacement = replacementForNode.call(self, node);
      }
      return join(output, replacement);
    }, "");
  }
  function postProcess(output) {
    var self = this;
    this.rules.forEach(function(rule) {
      if (typeof rule.append === "function") {
        output = join(output, rule.append(self.options));
      }
    });
    return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
  }
  function replacementForNode(node) {
    var rule = this.rules.forNode(node);
    var content = process.call(this, node);
    var whitespace = node.flankingWhitespace;
    if (whitespace.leading || whitespace.trailing) content = content.trim();
    return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
  }
  function join(output, replacement) {
    var s1 = trimTrailingNewlines(output);
    var s2 = trimLeadingNewlines(replacement);
    var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
    var separator = "\n\n".substring(0, nls);
    return s1 + separator + s2;
  }
  function canConvert(input) {
    return input != null && (typeof input === "string" || input.nodeType && (input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11));
  }
  var highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;
  function highlightedCodeBlock(turndownService2) {
    turndownService2.addRule("highlightedCodeBlock", {
      filter: function(node) {
        var firstChild = node.firstChild;
        return node.nodeName === "DIV" && highlightRegExp.test(node.className) && firstChild && firstChild.nodeName === "PRE";
      },
      replacement: function(content, node, options) {
        var className = node.className || "";
        var language = (className.match(highlightRegExp) || [null, ""])[1];
        return "\n\n" + options.fence + language + "\n" + node.firstChild.textContent + "\n" + options.fence + "\n\n";
      }
    });
  }
  function strikethrough(turndownService2) {
    turndownService2.addRule("strikethrough", {
      filter: ["del", "s", "strike"],
      replacement: function(content) {
        return "~" + content + "~";
      }
    });
  }
  var indexOf = Array.prototype.indexOf;
  var every = Array.prototype.every;
  var rules = {};
  rules.tableCell = {
    filter: ["th", "td"],
    replacement: function(content, node) {
      return cell(content, node);
    }
  };
  rules.tableRow = {
    filter: "tr",
    replacement: function(content, node) {
      var borderCells = "";
      var alignMap = { left: ":--", right: "--:", center: ":-:" };
      if (isHeadingRow(node)) {
        for (var i = 0; i < node.childNodes.length; i++) {
          var border = "---";
          var align = (node.childNodes[i].getAttribute("align") || "").toLowerCase();
          if (align) border = alignMap[align] || border;
          borderCells += cell(border, node.childNodes[i]);
        }
      }
      return "\n" + content + (borderCells ? "\n" + borderCells : "");
    }
  };
  rules.table = {
    // Only convert tables with a heading row.
    // Tables with no heading row are kept using `keep` (see below).
    filter: function(node) {
      return node.nodeName === "TABLE" && isHeadingRow(node.rows[0]);
    },
    replacement: function(content) {
      content = content.replace("\n\n", "\n");
      return "\n\n" + content + "\n\n";
    }
  };
  rules.tableSection = {
    filter: ["thead", "tbody", "tfoot"],
    replacement: function(content) {
      return content;
    }
  };
  function isHeadingRow(tr) {
    var parentNode = tr.parentNode;
    return parentNode.nodeName === "THEAD" || parentNode.firstChild === tr && (parentNode.nodeName === "TABLE" || isFirstTbody(parentNode)) && every.call(tr.childNodes, function(n) {
      return n.nodeName === "TH";
    });
  }
  function isFirstTbody(element) {
    var previousSibling = element.previousSibling;
    return element.nodeName === "TBODY" && (!previousSibling || previousSibling.nodeName === "THEAD" && /^\s*$/i.test(previousSibling.textContent));
  }
  function cell(content, node) {
    var index = indexOf.call(node.parentNode.childNodes, node);
    var prefix = " ";
    if (index === 0) prefix = "| ";
    return prefix + content + " |";
  }
  function tables(turndownService2) {
    turndownService2.keep(function(node) {
      return node.nodeName === "TABLE" && !isHeadingRow(node.rows[0]);
    });
    for (var key in rules) turndownService2.addRule(key, rules[key]);
  }
  function taskListItems(turndownService2) {
    turndownService2.addRule("taskListItems", {
      filter: function(node) {
        return node.type === "checkbox" && node.parentNode.nodeName === "LI";
      },
      replacement: function(content, node) {
        return (node.checked ? "[x]" : "[ ]") + " ";
      }
    });
  }
  function gfm(turndownService2) {
    turndownService2.use([
      highlightedCodeBlock,
      strikethrough,
      tables,
      taskListItems
    ]);
  }
  const turndownService = new TurndownService({
    headingStyle: "atx",
    codeBlockStyle: "fenced"
  });
  turndownService.use(gfm);
  turndownService.addRule("mathBlock", {
    filter: "math-tb",
    replacement: (_content, node) => node.getAttribute("data-math") || _content
  });
  function htmlToMarkdown(root2) {
    if (!root2) return "";
    const clone = root2.cloneNode(true);
    const uselessImages = clone.querySelectorAll('img[src*="lms_creative_elements"], img[src*="tb-avatar"], img.avatar, img.icon');
    uselessImages.forEach((img) => img.remove());
    const allImages = clone.querySelectorAll("img");
    allImages.forEach((img) => {
      var _a, _b, _c;
      const src = img.getAttribute("src") || "";
      if (src.includes("quesImage37.png")) {
        const b = document.createElement("b");
        b.textContent = "ALTERNATE METHOD";
        (_a = img.parentNode) == null ? void 0 : _a.replaceChild(b, img);
      } else if (src.includes("60c6e105dc004150078ccd08_16298247021121.png")) {
        const b = document.createElement("b");
        b.textContent = "💡IMPORTANT POINT";
        (_b = img.parentNode) == null ? void 0 : _b.replaceChild(b, img);
      } else if (src.includes("z65abjZdgDuGx4tuXXlusQDIeyCCmOiNCJWG3XiTuSsddAprFHSwW4HR2TdTYnMe73n5kgaTcUZYR9G38EcnxmPnK0SZgvEsGj6zGluwSjH8O1Xs0F-Un9IoZifdXgHRAmTPhFpV")) {
        const span = document.createElement("span");
        span.textContent = "✅";
        (_c = img.parentNode) == null ? void 0 : _c.replaceChild(span, img);
      }
    });
    const uselessUI = clone.querySelectorAll("button, bookmarks, report-cta, .tp-pos-neg-marks, .tb-report-component, .dropdown-menu, .help-note, .action-text, .tb-text-grey, .tb-more-dot");
    uselessUI.forEach((el) => el.remove());
    const mathScripts = clone.querySelectorAll('script[type^="math/tex"]');
    mathScripts.forEach((script) => {
      var _a, _b;
      const isBlock2 = (_a = script.getAttribute("type")) == null ? void 0 : _a.includes("mode=display");
      const math = script.textContent || "";
      const mathStr = isBlock2 ? `
$$
${math}
$$
` : `$${math}$`;
      const wrapper = document.createElement("math-tb");
      wrapper.setAttribute("data-math", mathStr);
      wrapper.textContent = mathStr;
      (_b = script.parentNode) == null ? void 0 : _b.replaceChild(wrapper, script);
    });
    const mathSpans = clone.querySelectorAll('.MathJax_Preview, .MathJax, [id^="MathJax"]');
    mathSpans.forEach((span) => span.remove());
    const images = clone.querySelectorAll("img");
    images.forEach((img) => {
      let src = img.getAttribute("src") || "";
      if (src && !src.startsWith("data:") && !src.startsWith("http")) {
        src = src.startsWith("//") ? "https:" + src : "https://testbook.com" + (src.startsWith("/") ? src : "/" + src);
        img.setAttribute("src", src);
      }
    });
    const tables2 = clone.querySelectorAll("table");
    tables2.forEach((table) => {
      table.querySelectorAll("th, td").forEach((cell2) => {
        const paragraphs = cell2.querySelectorAll("p, div");
        paragraphs.forEach((p) => {
          var _a;
          const span = document.createElement("span");
          span.innerHTML = p.innerHTML + " ";
          (_a = p.parentNode) == null ? void 0 : _a.replaceChild(span, p);
        });
      });
      table.querySelectorAll("th, td").forEach((cell2) => {
        var _a;
        const colspan = parseInt(cell2.getAttribute("colspan") || "1");
        if (colspan > 1) {
          cell2.removeAttribute("colspan");
          for (let i = 1; i < colspan; i++) {
            const empty = document.createElement(cell2.tagName);
            empty.innerHTML = " ";
            (_a = cell2.parentNode) == null ? void 0 : _a.insertBefore(empty, cell2.nextSibling);
          }
        }
      });
      let maxCols = 0;
      const rows = Array.from(table.querySelectorAll("tr"));
      rows.forEach((row) => {
        maxCols = Math.max(maxCols, row.querySelectorAll("th, td").length);
      });
      rows.forEach((row) => {
        var _a;
        const cells = Array.from(row.querySelectorAll("th, td"));
        if (cells.length < maxCols) {
          const diff = maxCols - cells.length;
          const type = ((_a = cells[0]) == null ? void 0 : _a.tagName) || "td";
          for (let i = 0; i < diff; i++) {
            const empty = document.createElement(type);
            empty.innerHTML = " ";
            row.appendChild(empty);
          }
        }
      });
      if (!table.querySelector("th")) {
        const firstRow = table.querySelector("tr");
        if (firstRow) {
          const tds = Array.from(firstRow.querySelectorAll("td"));
          tds.forEach((td) => {
            var _a;
            const th = document.createElement("th");
            th.innerHTML = td.innerHTML;
            (_a = td.parentNode) == null ? void 0 : _a.replaceChild(th, td);
          });
        }
      }
    });
    return turndownService.turndown(clone.innerHTML).trim();
  }
  function beautifyMarkdown(markdown) {
    const blocks = [];
    let result = markdown.replace(/```[\s\S]*?```/g, (m) => {
      blocks.push(m);
      return `\0TB_CODE_${blocks.length - 1}\0`;
    });
    result = result.replace(/(\*\*ALTERNATE METHOD\*\*)\s*\n\s*\1/g, "$1");
    result = result.replace(/(\*\*💡IMPORTANT POINT\*\*)\s*\n\s*\1/g, "$1");
    result = result.replace(/(✅)\s*\n\s*\1/g, "$1");
    result = result.replace(/\u00A0/g, " ");
    result = result.replace(/\u200B/g, "");
    result = result.replace(/^[ \t]+(?![*\-+] |\d+\. |[A-E]\. |> )/gm, "");
    const headings = [
      "Given",
      "Concept",
      "Formula Used",
      "Calculation",
      "Calculations",
      "Solution",
      "Given Series"
    ];
    headings.forEach((heading) => {
      const regex = new RegExp(`^(?:\\*\\*)?\\s*${heading}\\s*:(?:\\*\\*)?\\s*$`, "gmi");
      result = result.replace(regex, `**${heading}:**`);
    });
    result = result.replace(/^([A-E]\. .+)\n+(?=[A-E]\. )/gm, "$1\n");
    result = result.replace(/\\=/g, "=");
    result = result.replace(/\\\[/g, "[");
    result = result.replace(/\\\]/g, "]");
    result = result.replace(/\n{3,}/g, "\n\n");
    result = result.replace(/\x00TB_CODE_(\d+)\x00/g, (_, i) => blocks[parseInt(i)]);
    return result.trim();
  }
  const OPTION_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  function extractBox(qaBox, fallbackQNum, lastCompHtml = "") {
    const parts = [];
    let qNumEl = qaBox.querySelector(".tp-ques-number");
    if (!qNumEl) {
      const allQNums = Array.from(document.querySelectorAll(".tp-ques-number"));
      qNumEl = allQNums.find((el) => {
        const cs = getComputedStyle(el);
        return cs.display !== "none" && cs.visibility !== "hidden" && !el.closest(".ng-hide");
      }) || null;
    }
    let qNumStr = fallbackQNum.toString();
    if (qNumEl) {
      const text = qNumEl.textContent || "";
      const match = text.match(/\d+/);
      qNumStr = match ? match[0] : text.replace(/Question No\./i, "").trim() || qNumStr;
    }
    parts.push(`## Q${qNumStr}.`);
    let currentCompHtml = "";
    let comp = qaBox.querySelector(".aei-comprehension [ng-bind-html]");
    if (!comp) {
      const globalComp = document.querySelector(".aei-comprehension [ng-bind-html]");
      if (globalComp && !globalComp.closest(".ng-hide")) {
        const cs = getComputedStyle(globalComp);
        if (cs.display !== "none" && cs.visibility !== "hidden") {
          comp = globalComp;
        }
      }
    }
    if (comp) {
      currentCompHtml = comp.innerHTML.trim();
      if (currentCompHtml && currentCompHtml !== lastCompHtml) {
        parts.push("### Comprehension\n\n" + htmlToMarkdown(comp));
      }
    }
    const qEls = qaBox.querySelectorAll(".qns-view-box");
    let qEl = null;
    for (const el of Array.from(qEls)) {
      if (el.closest("li.option") || el.closest('[ng-bind-html*="getSolutionDesc"]')) continue;
      qEl = el;
      break;
    }
    if (qEl) {
      parts.push(htmlToMarkdown(qEl));
    }
    const list = Array.from(qaBox.querySelectorAll("ul")).find((u) => u.querySelector("li.option"));
    let correctIdx = -1;
    let selectedIdx = -1;
    if (list) {
      const items = Array.from(list.querySelectorAll("li.option")).filter((li) => li.querySelector(".qns-view-box"));
      if (items.length > 0) {
        parts.push("### Options");
        items.forEach((li, idx) => {
          const box = li.querySelector(".qns-view-box");
          const md = htmlToMarkdown(box);
          parts.push(`${OPTION_LETTERS[idx] || idx}. ${md}`);
          if (li.classList.contains("correct-option") || li.classList.contains("reattempt-correct-option") || li.querySelector(".text-success")) {
            correctIdx = idx;
          }
          if (li.classList.contains("wrong-option") || li.classList.contains("reattempt-wrong-option") || li.querySelector(".text-danger")) {
            selectedIdx = idx;
          }
          if (li.classList.contains("correct-option") && li.querySelector(".fa-check")) {
            selectedIdx = idx;
          }
        });
      }
    }
    if (correctIdx >= 0) {
      parts.push(`**Correct Answer:** ${OPTION_LETTERS[correctIdx] || correctIdx}`);
    }
    if (selectedIdx >= 0) {
      parts.push(`**Your Answer:** ${OPTION_LETTERS[selectedIdx] || selectedIdx}`);
    }
    const solEl = qaBox.querySelector('[ng-bind-html*="getSolutionDesc"]') || qaBox.querySelector(".solution-desc");
    if (solEl) {
      parts.push("### Solution\n\n" + htmlToMarkdown(solEl));
    }
    const rawMd = parts.filter(Boolean).join("\n\n");
    return {
      md: beautifyMarkdown(rawMd) + "\n\n---\n\n",
      compHtml: currentCompHtml,
      qNum: qNumStr
    };
  }
  function extractCurrentQuestion(fallbackQNum, lastCompHtml = "") {
    const boxes = Array.from(document.querySelectorAll(".que-ans-box"));
    if (!boxes.length) return { md: "", compHtml: "", qNum: "" };
    const qaBox = boxes.find((b) => {
      if (b.closest(".ng-hide")) return false;
      const cs = getComputedStyle(b);
      return cs.display !== "none" && cs.visibility !== "hidden";
    });
    if (!qaBox) return { md: "", compHtml: "", qNum: "" };
    return extractBox(qaBox, fallbackQNum, lastCompHtml);
  }
  const MAX_QUESTIONS_PER_SECTION = 300;
  class Crawler {
    constructor(onProgress, onFinish, onError) {
      __publicField(this, "markdown", "");
      // R3: no dead initializer
      __publicField(this, "isRunning", false);
      this.onProgress = onProgress;
      this.onFinish = onFinish;
      this.onError = onError;
    }
    cancel() {
      this.isRunning = false;
      this.onProgress("Cancelling...");
    }
    wait(ms) {
      return new Promise((res) => setTimeout(res, ms));
    }
    // B4: scoped to Testbook-specific containers
    getSections() {
      const selectors = [
        ".tp-test-sections .nav-tabs > li",
        ".sections-tabs > li",
        ".section-list > li",
        "#sectionNavTabs > li",
        ".sections-list > li"
      ];
      return Array.from(
        document.querySelectorAll(selectors.join(", "))
      ).filter((el) => {
        var _a;
        if (el.classList.contains("dropdown-menu") || el.closest(".dropdown-menu")) return false;
        const t = ((_a = el.textContent) == null ? void 0 : _a.trim().toLowerCase()) || "";
        return t.length > 0 && !t.includes("instruction");
      });
    }
    getSectionName(sec, index) {
      var _a, _b, _c;
      const hiddenXs = sec.querySelector(".hidden-xs");
      if (hiddenXs && ((_a = hiddenXs.textContent) == null ? void 0 : _a.trim())) {
        return hiddenXs.textContent.trim();
      }
      const visibleXs = sec.querySelector(".visible-xs");
      if (visibleXs) {
        const clone = sec.cloneNode(true);
        clone.querySelectorAll(".visible-xs").forEach((el) => el.remove());
        return ((_b = clone.textContent) == null ? void 0 : _b.trim()) || `Section ${index + 1}`;
      }
      return ((_c = sec.textContent) == null ? void 0 : _c.trim()) || `Section ${index + 1}`;
    }
    getActiveSectionName() {
      const sections = this.getSections();
      const activeSec = sections.find(
        (sec) => sec.classList.contains("active") || sec.classList.contains("selected") || sec.getAttribute("aria-selected") === "true" || sec.querySelector(".active") !== null
      );
      if (activeSec) {
        return this.getSectionName(activeSec, sections.indexOf(activeSec));
      }
      return null;
    }
    async start() {
      if (this.isRunning) return;
      this.isRunning = true;
      this.markdown = "# Testbook Paper\n\n";
      try {
        const sections = this.getSections();
        let lastCompHtml = "";
        let currentSectionName = "";
        if (sections.length > 0) {
          this.onProgress(`Starting crawler (${sections.length} Sections)...`);
          for (let i = 0; i < sections.length; i++) {
            if (!this.isRunning) break;
            const sec = sections[i];
            const secName = this.getSectionName(sec, i);
            this.onProgress(`Switching to ${secName}...`);
            const link = sec.querySelector("a");
            if (link) {
              link.click();
            } else {
              sec.click();
            }
            for (let w = 0; w < 20; w++) {
              await this.wait(250);
              if (this.getActiveSectionName() === secName) break;
            }
            await this.wait(500);
            if (secName !== currentSectionName) {
              this.markdown += `# ${secName}

`;
              currentSectionName = secName;
            }
            lastCompHtml = await this.crawlCurrentSectionQuestions(secName, lastCompHtml);
          }
        } else {
          this.onProgress("Starting crawler (Single section mode)...");
          await this.crawlCurrentSectionQuestions("Paper", lastCompHtml);
        }
        if (this.isRunning) {
          this.onFinish(this.markdown);
        }
      } catch (e) {
        console.error("[TB-MD] Crawl error:", e);
        this.onError(e instanceof Error ? e.message : "Unknown error");
      } finally {
        this.isRunning = false;
      }
    }
    async crawlCurrentSectionQuestions(sectionName, startComp) {
      let qIdxInSec = 1;
      let lastMd = "";
      let currentComp = startComp;
      let current = extractCurrentQuestion(qIdxInSec, currentComp);
      while (qIdxInSec <= MAX_QUESTIONS_PER_SECTION) {
        const { md, compHtml, qNum } = current;
        if (compHtml) currentComp = compHtml;
        if (md && md !== lastMd) {
          this.markdown += md;
          lastMd = md;
        }
        this.onProgress(`Extracting ${sectionName}: Q${qNum}`);
        const popup = Array.from(document.querySelectorAll(".modal, .modal-dialog, .popup, .alert, .bootbox")).find((el) => {
          const txt = (el.textContent || "").toLowerCase();
          const isVisible = el.style.display !== "none" && !el.classList.contains("ng-hide");
          return isVisible && (txt.includes("last question") || txt.includes("first question") || txt.includes("end of"));
        });
        if (popup) {
          const closeBtn = popup.querySelector('button[data-bb-handler="cancel"], button[data-dismiss="modal"], .close');
          if (closeBtn) closeBtn.click();
          return currentComp;
        }
        const nextBtn = this.findNextButton();
        if (!nextBtn || nextBtn.disabled || nextBtn.classList.contains("disabled")) {
          return currentComp;
        }
        nextBtn.click();
        let nextCheck = current;
        let changed = false;
        for (let i = 0; i < 20; i++) {
          await this.wait(250);
          if (!this.isRunning) return currentComp;
          const activeSecName = this.getActiveSectionName();
          if (activeSecName && activeSecName !== sectionName) {
            return currentComp;
          }
          nextCheck = extractCurrentQuestion(qIdxInSec + 1, currentComp);
          if (nextCheck.md !== md) {
            changed = true;
            break;
          }
        }
        if (!changed) {
          return currentComp;
        }
        qIdxInSec++;
        current = nextCheck;
      }
      return currentComp;
    }
    findNextButton() {
      const base = document.querySelector(".tp-test-area, .test-interface, .que-ans-box");
      const container = (base == null ? void 0 : base.closest(".tp-left-box, #questions, #mainBox, .test-interface")) || document;
      const els = Array.from(container.querySelectorAll('button, a, div[role="button"]'));
      for (const el of els) {
        const txt = (el.textContent || "").trim().toLowerCase();
        if (txt === "next" || txt === "save & next" || txt.includes("next question") || txt === "nextquestion") {
          return el;
        }
        if (el.querySelector(".fa-chevron-right") || el.querySelector(".fa-angle-right")) {
          const parent = el.parentElement;
          if (parent && (parent.className.includes("pagination") || parent.className.includes("palette"))) continue;
          return el;
        }
      }
      return container.querySelector('.next-btn, .btn-next, [ng-click*="nextQuestion"]');
    }
  }
  function injectPageScript(fn, ...args) {
    const s = document.createElement("script");
    s.textContent = `(${fn})(...${JSON.stringify(args)})`;
    (document.documentElement || document.head || document.body).appendChild(s);
    s.remove();
  }
  function injectCSS(id, css) {
    if (document.getElementById(id)) return;
    const style = document.createElement("style");
    style.id = id;
    style.textContent = css;
    (document.head || document.documentElement).appendChild(style);
  }
  function onPageChange(callback) {
    if (document.readyState === "complete" || document.readyState === "interactive") {
      callback();
    } else {
      document.addEventListener("DOMContentLoaded", callback, { once: true });
    }
    let timer = null;
    const trigger = () => {
      if (timer) clearTimeout(timer);
      timer = window.setTimeout(() => {
        callback();
        timer = null;
      }, 50);
    };
    const obs = new MutationObserver(trigger);
    obs.observe(document.documentElement, { childList: true, subtree: true });
    const pushState = history.pushState;
    const replaceState = history.replaceState;
    history.pushState = function(...args) {
      const r = pushState.apply(this, args);
      trigger();
      return r;
    };
    history.replaceState = function(...args) {
      const r = replaceState.apply(this, args);
      trigger();
      return r;
    };
    window.addEventListener("popstate", trigger);
  }
  async function copyToClipboard(text) {
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(text);
        return true;
      }
    } catch {
    }
    const ta = document.createElement("textarea");
    ta.value = text;
    ta.style.position = "fixed";
    ta.style.top = "-9999px";
    document.body.appendChild(ta);
    ta.select();
    let ok = false;
    try {
      ok = document.execCommand("copy");
    } catch {
    }
    ta.remove();
    return ok;
  }
  const BLOCK_PATTERNS = [
    // Analytics / Tags / Pixels
    /googletagmanager\.com/i,
    /google-analytics\.com/i,
    /g\.(?:doubleclick|google)\.net/i,
    /doubleclick\.net/i,
    /google\.com\/ccm\/collect/i,
    /unpkg\.com\/web-vitals/i,
    // Facebook
    /connect\.facebook\.net/i,
    /facebook\.com\/tr/i,
    // Microsoft
    /bat\.bing\.com/i,
    /clarity\.ms/i,
    /c\.bing\.com\/c\.gif/i,
    // Twitter
    /static\.ads-twitter\.com/i,
    /analytics\.twitter\.com/i,
    /t\.co\/1\/i\/adsct/i,
    // Quora
    /a\.quora\.com/i,
    /q\.quora\.com/i,
    // Criteo and ad sync chains
    /criteo\.com|static\.criteo\.net|sslwidget\.criteo\.com|gum\.criteo\.com|gumi\.criteo\.com/i,
    /cm\.g\.doubleclick\.net/i,
    /x\.bidswitch\.net|contextual\.media\.net|r\.casalemedia\.com|ad\.360yield\.com|idsync\.rlcdn\.com|rubiconproject\.com|smartadserver\.com|taboola\.com|outbrain\.com|3lift\.com|agkn\.com|adnxs\.com|dmxleo\.com/i,
    // Vendor SDKs / Beacons
    /cloudflareinsights\.com/i,
    /amplitude\.com/i,
    /openfpcdn\.io/i,
    /webengage\.com|webengage\.co|wsdk-files\.webengage\.com|c\.webengage\.com|ssl\.widgets\.webengage\.com|survey\.webengage\.com|z\d+.*\.webengage\.co/i,
    /intercom\.io|intercomcdn\.com|widget\.intercom\.io|api-iam\.intercom\.io|nexus-websocket-a\.intercom\.io/i,
    /onesignal\.com/i,
    /hotjar\.com/i,
    /sentry\.io/i,
    // Payment (blocked on request)
    /checkout\.razorpay\.com|checkout-static-next\.razorpay\.com|api\.razorpay\.com/i,
    // TB internal bloat
    /\/wcapi\/live-panel\.js/i,
    /\/js\/live-panel\.js/i,
    /live-panel\.template\.html/i,
    /live-panel\.styles\.css/i,
    /\/cdn-cgi\/rum/i,
    /coldboot\/dist\/coldboot\.min\.js/i,
    /sourcebuster\/dist\/sourcebuster\.min\.js/i,
    // Service workers from site/vendor
    /\/service-worker\.js$/i
  ];
  function initNetworkBlocker() {
    injectPageScript((patternSources) => {
      var _a, _b, _c, _d;
      window.__tbBlockerActive = true;
      const BLOCK_PATTERNS2 = patternSources.map((s) => new RegExp(s, "i"));
      const shouldBlock = (rawUrl) => {
        try {
          const url = typeof rawUrl === "string" ? new URL(rawUrl, location.href) : rawUrl;
          const str = url.toString();
          return BLOCK_PATTERNS2.some((re) => re.test(str));
        } catch {
          return false;
        }
      };
      const origFetch = window.fetch;
      if (origFetch) {
        window.fetch = function(input, _init) {
          const url = typeof input === "string" ? input : input && input.url;
          if (url && shouldBlock(url)) {
            return Promise.reject(new Error("Blocked by userscript: " + url));
          }
          return origFetch.apply(this, arguments);
        };
      }
      const XHR = window.XMLHttpRequest;
      if (XHR && XHR.prototype) {
        const origOpen = XHR.prototype.open;
        const origSend = XHR.prototype.send;
        XHR.prototype.open = function(method, url, _async, _user, _password) {
          this.__tbBlocked = url && shouldBlock(url);
          if (!this.__tbBlocked) return origOpen.apply(this, arguments);
          return origOpen.call(this, method, "data:application/json,{}", true);
        };
        XHR.prototype.send = function(_body) {
          if (this.__tbBlocked) {
            try {
              this.abort();
            } catch {
            }
            return;
          }
          return origSend.apply(this, arguments);
        };
      }
      if (navigator && "sendBeacon" in navigator) {
        const origBeacon = navigator.sendBeacon.bind(navigator);
        navigator.sendBeacon = function(url, data) {
          if (shouldBlock(url)) return false;
          return origBeacon(url, data);
        };
      }
      if ("WebSocket" in window) {
        const OrigWS = window.WebSocket;
        window.WebSocket = function(url, protocols) {
          if (shouldBlock(url)) throw new Error("WebSocket blocked: " + url);
          return new OrigWS(url, protocols);
        };
        window.WebSocket.prototype = OrigWS.prototype;
        window.WebSocket.CLOSING = OrigWS.CLOSING;
        window.WebSocket.CLOSED = OrigWS.CLOSED;
        window.WebSocket.CONNECTING = OrigWS.CONNECTING;
        window.WebSocket.OPEN = OrigWS.OPEN;
      }
      if ("EventSource" in window) {
        const OrigES = window.EventSource;
        window.EventSource = function(url, conf) {
          if (shouldBlock(url)) throw new Error("EventSource blocked: " + url);
          return new OrigES(url, conf);
        };
        window.EventSource.prototype = OrigES.prototype;
        window.EventSource.CLOSED = OrigES.CLOSED;
        window.EventSource.CONNECTING = OrigES.CONNECTING;
        window.EventSource.OPEN = OrigES.OPEN;
      }
      const patchSrcHref = (proto, prop) => {
        const desc = Object.getOwnPropertyDescriptor(proto, prop);
        if (!desc || !desc.set) return;
        Object.defineProperty(proto, prop, {
          configurable: true,
          enumerable: desc.enumerable,
          get: desc.get ? function() {
            return desc.get.call(this);
          } : void 0,
          set: function(v) {
            if (typeof v === "string" && shouldBlock(v)) {
              this.setAttribute("data-blocked-" + prop, v);
              return;
            }
            return desc.set.call(this, v);
          }
        });
      };
      const patchSetAttribute = (proto) => {
        const orig = proto.setAttribute;
        proto.setAttribute = function(name, value) {
          if ((name === "src" || name === "href") && typeof value === "string" && shouldBlock(value)) {
            this.setAttribute("data-blocked-" + name, value);
            return;
          }
          return orig.call(this, name, value);
        };
      };
      [HTMLScriptElement.prototype, HTMLLinkElement.prototype, HTMLImageElement.prototype, HTMLIFrameElement.prototype].forEach((p) => p && patchSetAttribute(p));
      patchSrcHref(HTMLScriptElement.prototype, "src");
      patchSrcHref(HTMLLinkElement.prototype, "href");
      patchSrcHref(HTMLImageElement.prototype, "src");
      patchSrcHref(HTMLIFrameElement.prototype, "src");
      document.write = () => {
      };
      document.writeln = () => {
      };
      window.dataLayer = window.dataLayer || [];
      try {
        Object.defineProperty(window, "dataLayer", { get: () => [], set: () => {
        } });
      } catch {
      }
      window.gtag = function() {
      };
      window.ga = function() {
      };
      window.fbq = function() {
      };
      window.clarity = function() {
      };
      window.Intercom = function() {
      };
      window.amplitude = {
        getInstance: () => ({ init() {
        }, logEvent() {
        }, setUserId() {
        }, setUserProperties() {
        }, identify() {
        } })
      };
      window.OneSignal = { push() {
      }, init() {
      }, on() {
      }, off() {
      } };
      if ("serviceWorker" in navigator) {
        navigator.serviceWorker.register = function() {
          return Promise.reject(new Error("ServiceWorker registration blocked by userscript"));
        };
        (_b = (_a = navigator.serviceWorker).getRegistrations) == null ? void 0 : _b.call(_a).then((list) => {
          list.forEach((reg) => reg.unregister().catch(() => {
          }));
        }).catch(() => {
        });
      }
      try {
        if (window.Notification) {
          window.Notification.requestPermission = function() {
            return Promise.resolve("denied");
          };
          Object.defineProperty(window.Notification, "permission", { get: () => "denied" });
        }
        const origPerms = (_d = (_c = navigator.permissions) == null ? void 0 : _c.query) == null ? void 0 : _d.bind(navigator.permissions);
        if (origPerms) {
          navigator.permissions.query = function(q) {
            if (q && (q.name === "notifications" || q.name === "push")) {
              return Promise.resolve({ state: "denied" });
            }
            return origPerms(q);
          };
        }
      } catch {
      }
    }, BLOCK_PATTERNS.map((re) => re.source));
    if (!window.__tbBlockerActive) {
      const patterns = BLOCK_PATTERNS.map((re) => new RegExp(re.source, "i"));
      const shouldBlock = (url) => patterns.some((re) => re.test(url));
      const origFetch = window.fetch;
      if (origFetch) {
        window.fetch = function(input, init) {
          const url = typeof input === "string" ? input : input && input.url;
          if (url && shouldBlock(url)) return Promise.reject(new Error("Blocked (CSP Fallback): " + url));
          return origFetch.apply(this, arguments);
        };
      }
      const XHR = window.XMLHttpRequest;
      if (XHR && XHR.prototype) {
        const origOpen = XHR.prototype.open;
        XHR.prototype.open = function(method, url) {
          if (url && shouldBlock(url)) {
            this.__tbBlocked = true;
            return origOpen.call(this, method, "data:application/json,{}", true);
          }
          return origOpen.apply(this, arguments);
        };
        const origSend = XHR.prototype.send;
        XHR.prototype.send = function() {
          if (this.__tbBlocked) {
            try {
              this.abort();
            } catch (e) {
            }
            return;
          }
          return origSend.apply(this, arguments);
        };
      }
    }
  }
  const CSS$1 = `
/* System font and minimal look */
:root { --tb-fm-maxw: 1180px; --tb-fg: #0b0d10; --tb-bg: #ffffff; }
html, body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important; }
body { background: var(--tb-bg) !important; color: var(--tb-fg) !important; }
/* Disable most animations/transitions */
*, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }

/* Keep content centered/wider */
main, [role="main"], .main, .content, .container, .wrapper, .dashboard, .page-wrapper, #content, #site-content {
  max-width: var(--tb-fm-maxw);
  margin-left: auto; margin-right: auto;
}

/* Hide live panel and promo components */
promotion-homepage-banner, refer-earn, goal-pitch-wrapper, goal-features-pitch, goal-combo-cards,
master-class-cards, why-testbook-ts, testimonials-ts, faqs { display: none !important; }
.promotional-banner,
[class*="live-panel"], #livePanel, .lp-tabs, .lp-badge-live, .lp-icon,
[onclick*="livePanel"], [src*="/live-panel/"], link[href*="live-panel"],
.tab-area.pav-class-livePanelTabShrunk { display: none !important; }

/* Hide common cookie bars/popups/newsletters/chats */
[id*="cookie"], [class*="cookie"], [aria-label*="cookie"],
[class*="newsletter"], [id*="newsletter"],
[id^="intercom-"], [class*="intercom"], iframe[src*="intercom"],
.we-popup, .we-survey, .we-banner, [class*="webengage"] { display: none !important; }
`;
  const REMOVE_SELECTORS = [
    "promotion-homepage-banner",
    "refer-earn",
    "goal-pitch-wrapper",
    "goal-features-pitch",
    "goal-combo-cards",
    "master-class-cards",
    "why-testbook-ts",
    "testimonials-ts",
    "faqs",
    ".promotional-banner",
    "#masterClassCards",
    ".tab-area.pav-class-livePanelTabShrunk",
    '[class*="live-panel"]',
    "#livePanel",
    ".lp-tabs",
    ".lp-badge-live",
    ".lp-icon",
    '[onclick*="livePanel"]',
    '[src*="/live-panel/"]',
    'link[href*="live-panel"]',
    '[id*="cookie"]',
    '[class*="cookie"]',
    '[aria-label*="cookie"]',
    '[class*="newsletter"]',
    '[id*="newsletter"]',
    '[id^="intercom-"]',
    '[class*="intercom"]',
    'iframe[src*="intercom"]',
    ".we-popup",
    ".we-survey",
    ".we-banner",
    '[class*="webengage"]'
  ];
  const NAV_REGEXES = [
    /^\/super-coaching/i,
    /^\/free-live-classes/i,
    /^\/skill-academy/i,
    /^\/pass$/i,
    /^\/pass-pro$/i,
    /^\/pass-elite$/i,
    /^\/reported-questions$/i,
    /^\/doubts$/i,
    /^\/current-affairs\/current-affairs-quiz$/i,
    /^\/e-cards$/i,
    /^\/teachers-training-program$/i,
    /^\/referrals$/i,
    /^\/success-stories$/i
  ];
  function pruneNav() {
    const nav = document.querySelectorAll("ul.header__sidebar__nav a[href]");
    nav.forEach((a) => {
      try {
        const href = a.getAttribute("href") || "";
        const u = new URL(href, location.origin);
        if (NAV_REGEXES.some((re) => re.test(u.pathname))) {
          const li = a.closest("li") || a;
          li.remove();
        }
      } catch {
      }
    });
    document.querySelectorAll("ul.header__sidebar__nav .header__divider").forEach((div) => {
      const t = (div.textContent || "").trim().toLowerCase();
      if (t === "learn" || t === "more") div.remove();
    });
  }
  function blockAutoPlay() {
    try {
      const proto = HTMLMediaElement.prototype;
      if (proto.__tbBlocked) return;
      proto.__tbBlocked = true;
      const origPlay = proto.play;
      proto.play = function() {
        const hasAuto = this.autoplay || this.getAttribute("autoplay") !== null;
        if (hasAuto) {
          return Promise.reject(new DOMException("Autoplay blocked by userscript", "NotAllowedError"));
        }
        return origPlay.apply(this, arguments);
      };
    } catch {
    }
  }
  function cleanUI() {
    injectCSS("tb-clean-style", CSS$1);
    REMOVE_SELECTORS.forEach((sel) => {
      document.querySelectorAll(sel).forEach((n) => n.remove());
    });
    document.querySelectorAll(".lp-title").forEach((n) => {
      if ((n.textContent || "").trim().toLowerCase() === "classes") {
        const card = n.closest(".tab-area, .lp-tabs, .live, .pav-class") || n;
        card.remove();
      }
    });
    pruneNav();
    blockAutoPlay();
  }
  const CSS = `
#tb-copy-md-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px 12px;
  margin-left: 8px;
  border: none;
  background: transparent;
  color: #86A1AE;
  cursor: pointer;
  font-size: 13px;
  outline: none;
  position: relative;
  vertical-align: middle;
}
#tb-copy-md-btn:hover {
  color: #0AD0F4;
}
#tb-copy-md-btn svg {
  width: 15px;
  height: 15px;
  fill: currentColor;
}
#tb-copy-md-toast {
  position: absolute;
  top: -30px;
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 8px;
  background: #1a7f37;
  color: white;
  border-radius: 4px;
  font-size: 11px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}
#tb-copy-md-toast.show {
  opacity: 1;
}
#tb-copy-md-toast::after {
  content: '';
  position: absolute;
  bottom: -4px;
  left: 50%;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid #1a7f37;
}
`;
  function ensureCopyButton() {
    injectCSS("tb-copy-md-style", CSS);
    const toolbar = document.querySelector(".tp-pos-neg-marks");
    if (!toolbar) return;
    if (toolbar.querySelector("#tb-copy-md-btn")) return;
    const btn = document.createElement("button");
    btn.id = "tb-copy-md-btn";
    btn.type = "button";
    btn.title = "Copy question to Markdown";
    btn.innerHTML = `
      <svg viewBox="0 0 16 16" aria-hidden="true">
        <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25ZM5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/>
      </svg>
    `;
    const toast = document.createElement("span");
    toast.id = "tb-copy-md-toast";
    toast.textContent = "Copied!";
    btn.appendChild(toast);
    if (toolbar.firstChild) {
      toolbar.insertBefore(btn, toolbar.firstChild);
    } else {
      toolbar.appendChild(btn);
    }
    btn.addEventListener("click", async (e) => {
      e.preventDefault();
      e.stopPropagation();
      try {
        const { md } = extractCurrentQuestion(1, "");
        const ok = await copyToClipboard(md || "No content found.");
        toast.textContent = ok ? "Copied!" : "Failed";
      } catch {
        toast.textContent = "Error";
      }
      toast.classList.add("show");
      setTimeout(() => toast.classList.remove("show"), 1500);
    });
  }
  initNetworkBlocker();
  blockAutoPlay();
  function downloadFile(content, filename) {
    const blob = new Blob([content], { type: "text/markdown;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }
  function enableCopyAndRightClick() {
    const style = document.createElement("style");
    style.textContent = `
        * {
            -webkit-user-select: text !important;
            -moz-user-select: text !important;
            -ms-user-select: text !important;
            user-select: text !important;
        }
    `;
    document.documentElement.appendChild(style);
    const events = ["contextmenu", "copy", "cut", "paste", "selectstart"];
    events.forEach((evt) => {
      window.addEventListener(evt, (e) => {
        e.stopPropagation();
      }, true);
    });
  }
  function initDownloader() {
    enableCopyAndRightClick();
    let activeCrawler = null;
    const ui = new DownloaderUI(
      () => {
        activeCrawler = new Crawler(
          (msg) => ui.updateStatus(msg),
          (md) => {
            ui.finish();
            const beautifulMd = beautifyMarkdown(md);
            downloadFile(beautifulMd, "Testbook_Paper.md");
          },
          (errMsg) => ui.error(errMsg)
        );
        activeCrawler.start();
      },
      () => activeCrawler == null ? void 0 : activeCrawler.cancel()
    );
    ui.mount();
  }
  if (document.readyState === "complete" || document.readyState === "interactive") {
    initDownloader();
  } else {
    document.addEventListener("DOMContentLoaded", initDownloader);
  }
  onPageChange(() => {
    cleanUI();
    ensureCopyButton();
  });

})();