text: (function(global, document) {
    "use strict";
  
    // Do not use other files like utils.js and http.js in the gulpfile to build
    // commento.js for the following reasons:
    //   - We don't use jQuery in the actual JavaScript payload because we need
    //     to be lightweight.
    //   - They pollute the global/window namespace (with global.post, etc.).
    //     That's NOT fine when we expect them to source our JavaScript. For example,
    //     the user may have their own window.post defined. We don't want to
    //     override that.
  
  
    var ID_ROOT = "commento";
    var ID_MAIN_AREA = "commento-main-area";
    var ID_LOGIN = "commento-login";
    var ID_LOGIN_BOX_CONTAINER = "commento-login-box-container";
    var ID_LOGIN_BOX = "commento-login-box";
    var ID_LOGIN_BOX_EMAIL_SUBTITLE = "commento-login-box-email-subtitle";
    var ID_LOGIN_BOX_EMAIL_INPUT = "commento-login-box-email-input";
    var ID_LOGIN_BOX_PASSWORD_INPUT = "commento-login-box-password-input";
    var ID_LOGIN_BOX_NAME_INPUT = "commento-login-box-name-input";
    var ID_LOGIN_BOX_WEBSITE_INPUT = "commento-login-box-website-input";
    var ID_LOGIN_BOX_EMAIL_BUTTON = "commento-login-box-email-button";
    var ID_LOGIN_BOX_LOGIN_LINK_CONTAINER = "commento-login-box-login-link-container";
    var ID_LOGIN_BOX_LOGIN_LINK = "commento-login-box-login-link";
    var ID_LOGIN_BOX_HR = "commento-login-box-hr";
    var ID_LOGIN_BOX_OAUTH_PRETEXT = "commento-login-box-oauth-pretext";
    var ID_LOGIN_BOX_OAUTH_BUTTONS_CONTAINER = "commento-login-box-oauth-buttons-container";
    var ID_MOD_TOOLS = "commento-mod-tools";
    var ID_MOD_TOOLS_LOCK_BUTTON = "commento-mod-tools-lock-button";
    var ID_ERROR = "commento-error";
    var ID_LOGGED_CONTAINER = "commento-logged-container";
    var ID_COMMENTS_AREA = "commento-comments-area";
    var ID_SUPER_CONTAINER = "commento-textarea-super-container-";
    var ID_TEXTAREA_CONTAINER = "commento-textarea-container-";
    var ID_TEXTAREA = "commento-textarea-";
    var ID_ANONYMOUS_CHECKBOX = "commento-anonymous-checkbox-";
    var ID_CARD = "commento-comment-card-";
    var ID_BODY = "commento-comment-body-";
    var ID_TEXT = "commento-comment-text-";
    var ID_SUBTITLE = "commento-comment-subtitle-";
    var ID_TIMEAGO = "commento-comment-timeago-";
    var ID_SCORE = "commento-comment-score-";
    var ID_OPTIONS = "commento-comment-options-";
    var ID_EDIT = "commento-comment-edit-";
    var ID_REPLY = "commento-comment-reply-";
    var ID_COLLAPSE = "commento-comment-collapse-";
    var ID_UPVOTE = "commento-comment-upvote-";
    var ID_DOWNVOTE = "commento-comment-downvote-";
    var ID_APPROVE = "commento-comment-approve-";
    var ID_REMOVE = "commento-comment-remove-";
    var ID_STICKY = "commento-comment-sticky-";
    var ID_CHILDREN = "commento-comment-children-";
    var ID_CONTENTS = "commento-comment-contents-";
    var ID_NAME = "commento-comment-name-";
    var ID_SUBMIT_BUTTON = "commento-submit-button-";
    var ID_FOOTER = "commento-footer";
  
  
    var origin = "[[[.Origin]]]";
    var cdn = "[[[.CdnPrefix]]]";
    var root = null;
    var cssOverride;
    var autoInit;
    var isAuthenticated = false;
    var comments = [];
    var commenters = {};
    var requireIdentification = true;
    var isModerator = false;
    var isFrozen = false;
    var chosenAnonymous = false;
    var isLocked = false;
    var stickyCommentHex = "none";
    var shownReply = {};
    var configuredOauths = [];
    var popupBoxType = "login";
    var oauthButtonsShown = false;
    var selfHex = undefined;
    var mobileView = null;
  
  
    function $(id) {
      return document.getElementById(id);
    }
  
  
    function tags(tag) {
      return document.getElementsByTagName(tag);
    }
  
  
    function prepend(root, el) {
      root.prepend(el);
    }
  
  
    function append(root, el) {
      root.appendChild(el);
    }
  
  
    function insertAfter(el1, el2) {
      el1.parentNode.insertBefore(el2, el1.nextSibling);
    }
  
  
    function classAdd(el, cls) {
      el.classList.add("commento-" + cls);
    }
  
  
    function classRemove(el, cls) {
      if (el !== null) {
        el.classList.remove("commento-" + cls);
      }
    }
  
  
    function create(el) {
      return document.createElement(el);
    }
  
  
    function remove(el) {
      if (el !== null) {
        el.parentNode.removeChild(el);
      }
    }
  
  
    function attrGet(node, a) {
      var attr = node.attributes[a];
  
      if (attr === undefined) {
        return undefined;
      }
      
      return attr.value;
    }
  
  
    function removeAllEventListeners(node) {
      if (node !== null) {
        var replacement = node.cloneNode(true);
        if (node.parentNode !== null) {
          node.parentNode.replaceChild(replacement, node);
          return replacement;
        }
      }
  
      return node;
    }
  
  
    function onclick(node, f, arg) {
      node.addEventListener("click", function() {
        f(arg);
      }, false);
    }
  
  
    function attrSet(node, a, value) {
      node.setAttribute(a, value);
    }
  
  
    function post(url, data, callback) {
      var xmlDoc = new XMLHttpRequest();
  
      xmlDoc.open("POST", url, true);
      xmlDoc.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      xmlDoc.onload = function() {
        callback(JSON.parse(xmlDoc.response));
      };
  
      xmlDoc.send(JSON.stringify(data));
    }
  
  
    function get(url, callback) {
      var xmlDoc = new XMLHttpRequest();
  
      xmlDoc.open("GET", url, true);
      xmlDoc.onload = function() {
        callback(JSON.parse(xmlDoc.response));
      };
  
      xmlDoc.send(null);
    }
  
  
    function call(callback) {
      if (typeof(callback) === "function") {
        callback();
      }
    }
  
  
    function cookieGet(name) {
      var c = "; " + document.cookie;
      var x = c.split("; " + name + "=");
      if (x.length === 2) {
        return x.pop().split(";").shift();
      }
    }
  
  
    function cookieSet(name, value) {
      var expires = "";
      var date = new Date();
      date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
      expires = "; expires=" + date.toUTCString();
  
      document.cookie = name + "=" + value + expires + "; path=/";
    }
  
  
    function commenterTokenGet() {
      var commenterToken = cookieGet("commentoCommenterToken");
      if (commenterToken === undefined) {
        return "anonymous";
      }
  
      return commenterToken;
    }
  
  
    global.logout = function() {
      cookieSet("commentoCommenterToken", "anonymous");
      refreshAll();
    }
  
    function selfLoad(commenter) {
      commenters[commenter.commenterHex] = commenter;
      selfHex = commenter.commenterHex;
  
      var loggedContainer = create("div");
      var loggedInAs = create("div");
      var name = create("a");
      var avatar;
      var logout = create("div");
      var color = colorGet(commenter.commenterHex + "-" + commenter.name);
  
      loggedContainer.id = ID_LOGGED_CONTAINER;
  
      classAdd(loggedContainer, "logged-container");
      classAdd(loggedInAs, "logged-in-as");
      classAdd(name, "name");
      classAdd(logout, "logout");
  
      name.innerText = commenter.name;
      logout.innerText = "Logout";
  
      onclick(logout, global.logout);
  
      attrSet(loggedContainer, "style", "display: none");
      attrSet(name, "href", commenter.link);
      if (commenter.photo === "undefined") {
        avatar = create("div");
        avatar.style["background"] = color;
        avatar.innerHTML = commenter.name[0].toUpperCase();
        classAdd(avatar, "avatar");
      } else {
        avatar = create("img");
        attrSet(avatar, "src", cdn + "/api/commenter/photo?commenterHex=" + commenter.commenterHex);
        classAdd(avatar, "avatar-img");
      }
  
      append(loggedInAs, avatar);
      append(loggedInAs, name);
      append(loggedContainer, loggedInAs);
      append(loggedContainer, logout);
      prepend(root, loggedContainer);
  
      isAuthenticated = true;
    }
  
  
    function selfGet(callback) {
      var commenterToken = commenterTokenGet();
      if (commenterToken === "anonymous") {
        isAuthenticated = false;
        call(callback);
        return;
      }
  
      var json = {
        "commenterToken": commenterTokenGet(),
      };
  
      post(origin + "/api/commenter/self", json, function(resp) {
        if (!resp.success) {
          cookieSet("commentoCommenterToken", "anonymous");
          call(callback);
          return;
        }
  
        selfLoad(resp.commenter);
  
        call(callback);
      });
    }
  
  
    function cssLoad(file, onload) {
      var link = create("link");
      var head = document.getElementsByTagName("head")[0];
  
      link.type = "text/css";
      attrSet(link, "href", file);
      attrSet(link, "rel", "stylesheet");
      attrSet(link, "onload", onload);
  
      append(head, link);
    }
  
  
    function footerLoad() {
      var footer = create("div");
      var aContainer = create("div");
      var a = create("a");
      var text = create("span");
  
      footer.id = ID_FOOTER;
  
      classAdd(footer, "footer");
      classAdd(aContainer, "logo-container");
      classAdd(a, "logo");
      classAdd(text, "logo-text");
  
      attrSet(a, "href", "https://commento.io");
      attrSet(a, "target", "_blank");
  
      text.innerText = "Commento";
  
      append(a, text);
      append(aContainer, a);
      append(footer, aContainer);
  
      return footer;
    }
  
  
    function commentsGet(callback) {
      var json = {
        "commenterToken": commenterTokenGet(),
        "domain": parent.location.host,
        "path": parent.location.pathname,
      };
  
      post(origin + "/api/comment/list", json, function(resp) {
        if (!resp.success) {
          errorShow(resp.message);
          return;
        }
  
        requireIdentification = resp.requireIdentification;
        isModerator = resp.isModerator;
        isFrozen = resp.isFrozen;
  
        isLocked = resp.attributes.isLocked;
        stickyCommentHex = resp.attributes.stickyCommentHex;
  
        comments = resp.comments;
        commenters = Object.assign({}, commenters, resp.commenters)
        configuredOauths = resp.configuredOauths;
  
        call(callback);
      });
    }
  
  
    function errorShow(text) {
      var el = $(ID_ERROR);
  
      el.innerText = text;
  
      attrSet(el, "style", "display: block;");
    }
  
  
    function errorElementCreate() {
      var el = create("div");
  
      el.id = ID_ERROR;
  
      classAdd(el, "error-box");
      attrSet(el, "style", "display: none;");
  
      append(root, el);
    }
  
  
    function autoExpander(el) {
      return function() {
        el.style.height = "";
        el.style.height = Math.min(Math.max(el.scrollHeight, 75), 400) + "px";
      }
    };
  
  
    function textareaCreate(id) {
      var textareaSuperContainer = create("div");
      var textareaContainer = create("div");
      var textarea = create("textarea");
      var anonymousCheckboxContainer = create("div");
      var anonymousCheckbox = create("input");
      var anonymousCheckboxLabel = create("label");
      var submitButton = create("button");
  
      textareaSuperContainer.id = ID_SUPER_CONTAINER + id;
      textareaContainer.id = ID_TEXTAREA_CONTAINER + id;
      textarea.id = ID_TEXTAREA + id;
      anonymousCheckbox.id = ID_ANONYMOUS_CHECKBOX + id;
      submitButton.id = ID_SUBMIT_BUTTON + id;
  
      classAdd(textareaContainer, "textarea-container");
      classAdd(anonymousCheckboxContainer, "round-check");
      classAdd(anonymousCheckboxContainer, "anonymous-checkbox-container");
      classAdd(submitButton, "button");
      classAdd(submitButton, "submit-button");
      classAdd(textareaSuperContainer, "button-margin");
  
      attrSet(textarea, "placeholder", "Add a comment");
      attrSet(anonymousCheckbox, "type", "checkbox");
      attrSet(anonymousCheckboxLabel, "for", ID_ANONYMOUS_CHECKBOX + id);
  
      anonymousCheckboxLabel.innerText = "Comment anonymously";
      submitButton.innerText = "Add Comment";
  
      textarea.oninput = autoExpander(textarea);
      onclick(submitButton, submitAccountDecide, id);
  
      append(textareaContainer, textarea);
      append(textareaSuperContainer, textareaContainer);
      append(anonymousCheckboxContainer, anonymousCheckbox);
      append(anonymousCheckboxContainer, anonymousCheckboxLabel);
      append(textareaSuperContainer, submitButton);
      if (!requireIdentification) {
        append(textareaSuperContainer, anonymousCheckboxContainer);
      }
  
      return textareaSuperContainer;
    }
  
  
    function rootCreate(callback) {
      var login = create("div");
      var loginText = create("div");
      var mainArea = $(ID_MAIN_AREA);
      var commentsArea = create("div");
  
      login.id = ID_LOGIN;
      commentsArea.id = ID_COMMENTS_AREA;
  
      classAdd(login, "login");
      classAdd(loginText, "login-text");
      classAdd(commentsArea, "comments");
  
      loginText.innerText = "Login";
      commentsArea.innerHTML = "";
  
      onclick(loginText, global.loginBoxShow, null);
  
      append(login, loginText);
  
      if (isLocked || isFrozen) {
        if (isAuthenticated || chosenAnonymous) {
          append(mainArea, messageCreate("This thread is locked. You cannot add new comments."));
          remove($(ID_LOGIN));
        } else {
          append(mainArea, login);
          append(mainArea, textareaCreate("root"));
        }
      } else {
        if (!isAuthenticated) {
          append(mainArea, login);
        } else {
          remove($(ID_LOGIN));
        }
        append(mainArea, textareaCreate("root"));
      }
  
      append(mainArea, commentsArea);
      append(root, mainArea);
  
      call(callback);
    }
  
  
    function messageCreate(text) {
      var msg = create("div");
  
      classAdd(msg, "moderation-notice");
  
      msg.innerText = text;
  
      return msg;
    }
  
  
    global.commentNew = function(id, commenterToken, callback) {
      var textareaSuperContainer = $(ID_SUPER_CONTAINER + id);
      var textarea = $(ID_TEXTAREA + id);
      var replyButton = $(ID_REPLY + id);
  
      var markdown = textarea.value;
  
      if (markdown === "") {
        classAdd(textarea, "red-border");
        return;
      } else {
        classRemove(textarea, "red-border");
      }
  
      var json = {
        "commenterToken": commenterToken,
        "domain": parent.location.host,
        "path": parent.location.pathname,
        "parentHex": id,
        "markdown": markdown,
      };
  
      post(origin + "/api/comment/new", json, function(resp) {
        if (!resp.success) {
          errorShow(resp.message);
          return;
        }
  
        var message = "";
        if (resp.state === "unapproved") {
          message = "Your comment is under moderation.";
        } else if (resp.state === "flagged") {
          message = "Your comment was flagged as spam and is under moderation.";
        }
  
        if (message !== "") {
          prepend($(ID_SUPER_CONTAINER + id), messageCreate(message));
        }
        
        var commenterHex = selfHex;
        if (commenterHex === undefined || commenterToken === "anonymous") {
          commenterHex = "anonymous";
        }
  
        var newCard = commentsRecurse({
          "root": [{
            "commentHex": resp.commentHex,
            "commenterHex": commenterHex,
            "markdown": markdown,
            "html": resp.html,
            "parentHex": "root",
            "score": 0,
            "state": "approved",
            "direction": 0,
            "creationDate": (new Date()).toISOString(),
          }],
        }, "root")
  
        if (id !== "root") {
          textareaSuperContainer.replaceWith(newCard);
  
          shownReply[id] = false;
  
          classAdd(replyButton, "option-reply");
          classRemove(replyButton, "option-cancel");
  
          replyButton.title = "Reply to this comment";
  
          onclick(replyButton, global.replyShow, id)
        } else {
          textarea.value = "";
          insertAfter(textareaSuperContainer, newCard);
        }
  
        call(callback);
      });
    }
  
  
    function colorGet(name) {
      var colors = [
        "#396ab1",
        "#da7c30",
        "#3e9651",
        "#cc2529",
        "#535154",
        "#6b4c9a",
        "#922428",
      ];
  
      var total = 0;
      for (var i = 0; i < name.length; i++) {
        total += name.charCodeAt(i);
      }
      var color = colors[total % colors.length];
  
      return color;
    }
  
  
    function timeDifference(current, previous) { // thanks stackoverflow
      var msJustNow = 5000;
      var msPerMinute = 60000;
      var msPerHour = 3600000;
      var msPerDay = 86400000;
      var msPerMonth = 2592000000;
      var msPerYear = 946080000000;
  
      var elapsed = current - previous;
  
      if (elapsed < msJustNow) {
        return "just now";
      } else if (elapsed < msPerMinute) {
        return Math.round(elapsed / 1000) + " seconds ago";
      } else if (elapsed < msPerHour) {
        return Math.round(elapsed / msPerMinute) + " minutes ago";
      } else if (elapsed < msPerDay ) {
        return Math.round(elapsed / msPerHour ) + " hours ago";
      } else if (elapsed < msPerMonth) {
        return Math.round(elapsed / msPerDay) + " days ago";
      } else if (elapsed < msPerYear) {
        return Math.round(elapsed / msPerMonth) + " months ago";
      } else {
        return Math.round(elapsed / msPerYear ) + " years ago";
      }
    }
  
  
    function scorify(score) {
      if (score !== 1) {
        return score + " points";
      } else {
        return score + " point";
      }
    }
  
  
    function commentsRecurse(parentMap, parentHex) {
      var cur = parentMap[parentHex];
      if (!cur || !cur.length) {
        return null;
      }
  
      cur.sort(function(a, b) {
        if (a.commentHex === stickyCommentHex) {
          return -Infinity;
        }
        if (b.commentHex === stickyCommentHex) {
          return Infinity;
        }
        return b.score - a.score;
      });
  
      var cards = create("div");
      cur.forEach(function(comment) {
        var commenter = commenters[comment.commenterHex];
        var avatar;
        var card = create("div");
        var header = create("div");
        var subtitle = create("div");
        var timeago = create("div");
        var score = create("div");
        var body = create("div");
        var text = create("div");
        var options = create("div");
        var edit = create("button");
        var reply = create("button");
        var collapse = create("button");
        var upvote = create("button");
        var downvote = create("button");
        var approve = create("button");
        var remove = create("button");
        var sticky = create("button");
        var children = commentsRecurse(parentMap, comment.commentHex);
        var contents = create("div");
        var color = colorGet(comment.commenterHex + "-" + commenter.name);
        var name;
        if (commenter.link !== "undefined" && commenter.link !== "https://undefined" && commenter.link !== "") {
          name = create("a");
        } else {
          name = create("div");
        }
        var date = new Date(comment.creationDate);
  
        card.id = ID_CARD + comment.commentHex;
        body.id = ID_BODY + comment.commentHex;
        text.id = ID_TEXT + comment.commentHex;
        subtitle.id = ID_SUBTITLE + comment.commentHex;
        timeago.id = ID_TIMEAGO + comment.commentHex;
        score.id = ID_SCORE + comment.commentHex;
        options.id = ID_OPTIONS + comment.commentHex;
        edit.id = ID_EDIT + comment.commentHex;
        reply.id = ID_REPLY + comment.commentHex;
        collapse.id = ID_COLLAPSE + comment.commentHex;
        upvote.id = ID_UPVOTE + comment.commentHex;
        downvote.id = ID_DOWNVOTE + comment.commentHex;
        approve.id = ID_APPROVE + comment.commentHex;
        remove.id = ID_REMOVE + comment.commentHex;
        sticky.id = ID_STICKY + comment.commentHex;
        if (children) {
          children.id = ID_CHILDREN + comment.commentHex;
        }
        contents.id = ID_CONTENTS + comment.commentHex;
        name.id = ID_NAME + comment.commentHex;
  
        collapse.title = "Collapse children";
        upvote.title = "Upvote";
        downvote.title = "Downvote";
        edit.title = "Edit";
        reply.title = "Reply";
        approve.title = "Approve";
        remove.title = "Remove";
        if (stickyCommentHex === comment.commentHex) {
          if (isModerator) {
            sticky.title = "Unsticky";
          } else {
            sticky.title = "This comment has been stickied";
          }
        } else {
          sticky.title = "Sticky";
        }
        timeago.title = date.toString();
  
        card.style["borderLeft"] = "2px solid " + color;
        name.innerText = commenter.name;
        text.innerHTML = comment.html;
        timeago.innerHTML = timeDifference((new Date()).getTime(), date);
        score.innerText = scorify(comment.score);
  
        if (commenter.photo === "undefined") {
          avatar = create("div");
          avatar.style["background"] = color;
          avatar.innerHTML = commenter.name[0].toUpperCase();
          classAdd(avatar, "avatar");
        } else {
          avatar = create("img");
          attrSet(avatar, "src", cdn + "/api/commenter/photo?commenterHex=" + commenter.commenterHex);
          classAdd(avatar, "avatar-img");
        }
  
        classAdd(card, "card");
        if (isModerator && comment.state !== "approved") {
          classAdd(card, "dark-card");
        }
        if (commenter.isModerator) {
          classAdd(name, "moderator");
        }
        if (comment.state === "flagged") {
          classAdd(name, "flagged");
        }
        classAdd(header, "header");
        classAdd(name, "name");
        classAdd(subtitle, "subtitle");
        classAdd(timeago, "timeago");
        classAdd(score, "score");
        classAdd(body, "body");
        classAdd(options, "options");
        if (mobileView) {
          classAdd(options, "options-mobile");
        }
        classAdd(edit, "option-button");
        classAdd(edit, "option-edit");
        classAdd(reply, "option-button");
        classAdd(reply, "option-reply");
        classAdd(collapse, "option-button");
        classAdd(collapse, "option-collapse");
        classAdd(upvote, "option-button");
        classAdd(upvote, "option-upvote");
        classAdd(downvote, "option-button");
        classAdd(downvote, "option-downvote");
        classAdd(approve, "option-button");
        classAdd(approve, "option-approve");
        classAdd(remove, "option-button");
        classAdd(remove, "option-remove");
        classAdd(sticky, "option-button");
        if (stickyCommentHex === comment.commentHex) {
          classAdd(sticky, "option-unsticky");
        } else {
          classAdd(sticky, "option-sticky");
        }
  
        if (isAuthenticated) {
          if (comment.direction > 0) {
            classAdd(upvote, "upvoted");
          } else if (comment.direction < 0) {
            classAdd(downvote, "downvoted");
          }
        }
  
        onclick(collapse, global.commentCollapse, comment.commentHex);
        onclick(approve, global.commentApprove, comment.commentHex);
        onclick(remove, global.commentDelete, comment.commentHex);
        onclick(sticky, global.commentSticky, comment.commentHex);
  
        if (isAuthenticated) {
          var upDown = upDownOnclickSet(upvote, downvote, comment.commentHex, comment.direction);
          upvote = upDown[0];
          downvote = upDown[1];
        } else {
          onclick(upvote, global.loginBoxShow, null);
          onclick(downvote, global.loginBoxShow, null);
        }
  
        onclick(reply, global.replyShow, comment.commentHex);
  
        if (commenter.link !== "undefined" && commenter.link !== "https://undefined" && commenter.link !== "") {
          attrSet(name, "href", commenter.link);
        }
  
        append(options, collapse);
  
        // append(options, edit); // uncomment when implemented
        append(options, downvote);
        append(options, upvote);
  
        append(options, reply);
  
        if (isModerator) {
          if (parentHex === "root") {
            append(options, sticky);
          }
          append(options, remove);
          if (comment.state !== "approved") {
            append(options, approve);
          }
        } else {
          if (stickyCommentHex === comment.commentHex) {
            append(options, sticky);
          }
        }
  
        attrSet(options, "style", "width: " + ((options.childNodes.length+1)*32) + "px;");
        for (var i = 0; i < options.childNodes.length; i++) {
          attrSet(options.childNodes[i], "style", "right: " + (i*32) + "px;");
        }
  
        append(subtitle, score);
        append(subtitle, timeago);
  
        if (!mobileView) {
          append(header, options);
        }
        append(header, avatar);
        append(header, name);
        append(header, subtitle);
        append(body, text);
        append(contents, body);
        if (mobileView) {
          append(contents, options);
          var optionsClearfix = create("div");
          classAdd(optionsClearfix, "options-clearfix");
          append(contents, optionsClearfix);
        }
  
        if (children) {
          classAdd(children, "body");
          append(contents, children);
        }
  
        append(card, header);
        append(card, contents);
        append(cards, card);
      });
  
      return cards;
    }
  
  
    global.commentApprove = function(commentHex) {
      var json = {
        "commenterToken": commenterTokenGet(),
        "commentHex": commentHex,
      }
  
      post(origin + "/api/comment/approve", json, function(resp) {
        if (!resp.success) {
          errorShow(resp.message);
          return
        }
  
        var card = $(ID_CARD + commentHex);
        var name = $(ID_NAME + commentHex);
        var tick = $(ID_APPROVE + commentHex);
  
        classRemove(card, "dark-card");
        classRemove(name, "flagged");
        remove(tick);
      });
    }
  
  
    global.commentDelete = function(commentHex) {
      var json = {
        "commenterToken": commenterTokenGet(),
        "commentHex": commentHex,
      }
  
      post(origin + "/api/comment/delete", json, function(resp) {
        if (!resp.success) {
          errorShow(resp.message);
          return
        }
  
        var card = $(ID_CARD + commentHex);
        remove(card);
      });
    }
  
  
    function nameWidthFix() {
      var els = document.getElementsByClassName("commento-name");
  
      for (var i = 0; i < els.length; i++) {
        attrSet(els[i], "style", "max-width: " + (els[i].getBoundingClientRect()["width"] + 20) + "px;")
      }
    }
  
  
    function upDownOnclickSet(upvote, downvote, commentHex, direction) {
      upvote = removeAllEventListeners(upvote);
      downvote = removeAllEventListeners(downvote);
  
      if (direction > 0) {
        onclick(upvote, global.vote, [commentHex, [1, 0]]);
        onclick(downvote, global.vote, [commentHex, [1, -1]]);
      } else if (direction < 0) {
        onclick(upvote, global.vote, [commentHex, [-1, 1]]);
        onclick(downvote, global.vote, [commentHex, [-1, 0]]);
      } else {
        onclick(upvote, global.vote, [commentHex, [0, 1]]);
        onclick(downvote, global.vote, [commentHex, [0, -1]]);
      }
  
      return [upvote, downvote];
    }
  
  
    global.vote = function(data) {
      var commentHex = data[0];
      var oldDirection = data[1][0];
      var newDirection = data[1][1];
  
      var upvote = $(ID_UPVOTE + commentHex);
      var downvote = $(ID_DOWNVOTE + commentHex);
      var score = $(ID_SCORE + commentHex);
  
      var json = {
        "commenterToken": commenterTokenGet(),
        "commentHex": commentHex,
        "direction": newDirection,
      };
  
      var upDown = upDownOnclickSet(upvote, downvote, commentHex, newDirection);
      upvote = upDown[0];
      downvote = upDown[1];
  
      classRemove(upvote, "upvoted");
      classRemove(downvote, "downvoted");
      if (newDirection > 0) {
        classAdd(upvote, "upvoted");
      } else if (newDirection < 0) {
        classAdd(downvote, "downvoted");
      }
  
      score.innerText = scorify(parseInt(score.innerText.replace(/[^\d-.]/g, "")) + newDirection - oldDirection);
  
      post(origin + "/api/comment/vote", json, function(resp) {
        if (!resp.success) {
          errorShow(resp.message);
          classRemove(upvote, "upvoted");
          classRemove(downvote, "downvoted");
          score.innerText = scorify(parseInt(score.innerText.replace(/[^\d-.]/g, "")) - newDirection + oldDirection);
          upDownOnclickSet(upvote, downvote, commentHex, oldDirection);
          return;
        }
      });
    }
  
  
    global.replyShow = function(id) {
      if (id in shownReply && shownReply[id]) {
        return;
      }
  
      var text = $(ID_TEXT + id);
      insertAfter(text, textareaCreate(id));
      shownReply[id] = true;
  
      var replyButton = $(ID_REPLY + id);
  
      classRemove(replyButton, "option-reply");
      classAdd(replyButton, "option-cancel");
  
      replyButton.title = "Cancel reply";
  
      onclick(replyButton, global.replyCollapse, id);
    };
  
  
    global.replyCollapse = function(id) {
      var replyButton = $(ID_REPLY + id);
      var el = $(ID_SUPER_CONTAINER + id);
  
      el.remove();
      shownReply[id] = false;
  
      classAdd(replyButton, "option-reply");
      classRemove(replyButton, "option-cancel");
  
      replyButton.title = "Reply to this comment";
  
      onclick(replyButton, global.replyShow, id)
    }
  
  
    global.commentCollapse = function(id) {
      var children = $(ID_CHILDREN + id);
      var button = $(ID_COLLAPSE + id);
  
      if (children) {
        classAdd(children, "hidden");
      }
  
      classRemove(button, "option-collapse");
      classAdd(button, "option-uncollapse");
  
      button.title = "Expand children";
  
      onclick(button, global.commentUncollapse, id);
    }
  
  
    global.commentUncollapse = function(id) {
      var children = $(ID_CHILDREN + id);
      var button = $(ID_COLLAPSE + id);
  
      if (children) {
        classRemove(children, "hidden");
      }
  
      classRemove(button, "option-uncollapse");
      classAdd(button, "option-collapse");
  
      button.title = "Collapse children";
  
      onclick(button, global.commentCollapse, id);
    }
  
  
    function commentsRender() {
      var parentMap = {};
      var parentHex;
  
      var commentsArea = $(ID_COMMENTS_AREA);
  
      comments.forEach(function(comment) {
        parentHex = comment.parentHex;
        if (!(parentHex in parentMap)) {
          parentMap[parentHex] = [];
        }
        parentMap[parentHex].push(comment);
      });
  
      var cards = commentsRecurse(parentMap, "root");
      if (cards) {
        append(commentsArea, cards);
      }
    }
  
  
    function submitAuthenticated(id) {
      if (isAuthenticated) {
        global.commentNew(id, commenterTokenGet());
        return;
      }
  
      global.loginBoxShow(id);
      return;
    }
  
  
    function submitAnonymous(id) {
      chosenAnonymous = true;
      global.commentNew(id, "anonymous");
    }
  
  
    function submitAccountDecide(id) {
      if (requireIdentification) {
        submitAuthenticated(id);
        return;
      }
  
      var anonymousCheckbox = $(ID_ANONYMOUS_CHECKBOX + id);
      var textarea = $(ID_TEXTAREA + id);
      var markdown = textarea.value;
  
      if (markdown === "") {
        classAdd(textarea, "red-border");
        return;
      } else {
        classRemove(textarea, "red-border");
      }
  
      if (!anonymousCheckbox.checked) {
        submitAuthenticated(id);
        return;
      } else {
        submitAnonymous(id);
        return;
      }
    }
  
  
    global.commentoAuth = function(data) {
      var provider = data.provider;
      var id = data.id;
      var popup = window.open("", "_blank");
  
      get(origin + "/api/commenter/token/new", function(resp) {
        if (!resp.success) {
          errorShow(resp.message);
          return;
        }
  
        cookieSet("commentoCommenterToken", resp.commenterToken);
  
        popup.location = origin + "/api/oauth/" + provider + "/redirect?commenterToken=" + resp.commenterToken;
  
        var interval = setInterval(function() {
          if (popup.closed) {
            clearInterval(interval);
            selfGet(function() {
              var loggedContainer = $(ID_LOGGED_CONTAINER);
              if (loggedContainer) {
                attrSet(loggedContainer, "style", "");
              }
  
              remove($(ID_LOGIN));
              if (id !== null) {
                global.commentNew(id, resp.commenterToken, function() {
                  global.loginBoxClose();
                });
              } else {
                global.loginBoxClose();
              }
            });
          }
        }, 250);
      });
    }
  
  
    function refreshAll(callback) {
      $(ID_ROOT).innerHTML = "";
      shownReply = {};
      global.main(callback);
    }
  
  
    function loginBoxCreate() {
      var loginBoxContainer = create("div");
  
      loginBoxContainer.id = ID_LOGIN_BOX_CONTAINER;
  
      append(root, loginBoxContainer);
    }
  
  
    global.popupRender = function(id) {
      var loginBoxContainer = $(ID_LOGIN_BOX_CONTAINER);
      var loginBox = create("div");
      var oauthSubtitle = create("div");
      var oauthButtonsContainer = create("div");
      var oauthButtons = create("div");
      var hr = create("hr");
      var emailSubtitle = create("div");
      var emailContainer = create("div");
      var email = create("div");
      var emailInput = create("input");
      var emailButton = create("button");
      var loginLinkContainer = create("div");
      var loginLink = create("a");
      var close = create("div");
  
      loginBox.id = ID_LOGIN_BOX;
      emailSubtitle.id = ID_LOGIN_BOX_EMAIL_SUBTITLE;
      emailInput.id = ID_LOGIN_BOX_EMAIL_INPUT;
      emailButton.id = ID_LOGIN_BOX_EMAIL_BUTTON;
      loginLink.id = ID_LOGIN_BOX_LOGIN_LINK;
      loginLinkContainer.id = ID_LOGIN_BOX_LOGIN_LINK_CONTAINER;
      hr.id = ID_LOGIN_BOX_HR;
      oauthSubtitle.id = ID_LOGIN_BOX_OAUTH_PRETEXT;
      oauthButtonsContainer.id = ID_LOGIN_BOX_OAUTH_BUTTONS_CONTAINER;
  
      classAdd(loginBoxContainer, "login-box-container");
      classAdd(loginBox, "login-box");
      classAdd(emailSubtitle, "login-box-subtitle");
      classAdd(emailContainer, "email-container");
      classAdd(email, "email");
      classAdd(emailInput, "input");
      classAdd(emailButton, "email-button");
      classAdd(loginLinkContainer, "login-link-container");
      classAdd(loginLink, "login-link");
      classAdd(oauthSubtitle, "login-box-subtitle");
      classAdd(oauthButtonsContainer, "oauth-buttons-container");
      classAdd(oauthButtons, "oauth-buttons");
      classAdd(close, "login-box-close");
      classAdd(root, "root-min-height");
  
      loginLink.innerText = "Don't have an account? Sign up.";
      emailSubtitle.innerText = "Login with your email address";
      emailButton.innerText = "Continue";
      oauthSubtitle.innerText = "Proceed with social login";
  
      onclick(emailButton, global.passwordAsk, id);
      onclick(loginLink, global.popupSwitch);
      onclick(close, global.loginBoxClose);
  
      attrSet(loginBoxContainer, "style", "display: none; opacity: 0;");
      attrSet(emailInput, "name", "email");
      attrSet(emailInput, "placeholder", "Email address");
      attrSet(emailInput, "type", "text");
  
      for (var i = 0; i < configuredOauths.length; i++) {
        var button = create("button");
  
        classAdd(button, "button");
        classAdd(button, configuredOauths[i] + "-button");
  
        button.innerText = configuredOauths[i];
  
        onclick(button, global.commentoAuth, {"provider": configuredOauths[i], "id": id});
  
        append(oauthButtons, button);
      }
  
      if (configuredOauths.length > 0) {
        append(loginBox, oauthSubtitle);
        append(oauthButtonsContainer, oauthButtons);
        append(loginBox, oauthButtonsContainer);
        append(loginBox, hr);
        oauthButtonsShown = true;
      } else {
        oauthButtonsShown = false;
      }
  
      append(loginBox, emailSubtitle);
      append(email, emailInput);
      append(email, emailButton);
      append(emailContainer, email);
      append(loginBox, emailContainer);
  
      append(loginLinkContainer, loginLink);
      append(loginBox, loginLinkContainer);
  
      append(loginBox, close);
  
      popupBoxType = "login";
      loginBoxContainer.innerHTML = "";
      append(loginBoxContainer, loginBox);
    }
  
  
    global.popupSwitch = function() {
      var emailSubtitle = $(ID_LOGIN_BOX_EMAIL_SUBTITLE);
      var loginLink = $(ID_LOGIN_BOX_LOGIN_LINK);
  
      if (popupBoxType === "login") {
        loginLink.innerText = "Already have an account? Log in.";
        emailSubtitle.innerText = "Create an account";
        popupBoxType = "signup";
      } else {
        loginLink.innerText = "Don't have an account? Sign up.";
        emailSubtitle.innerText = "Login with your email address";
        popupBoxType = "login";
      }
    }
  
  
    function loginUP(username, password, id) {
      var json = {
        "email": username,
        "password": password,
      };
  
      post(origin + "/api/commenter/login", json, function(resp) {
        if (!resp.success) {
          global.loginBoxClose();
          errorShow(resp.message);
          return
        }
  
        cookieSet("commentoCommenterToken", resp.commenterToken);
  
        selfLoad(resp.commenter);
        var loggedContainer = $(ID_LOGGED_CONTAINER);
        if (loggedContainer) {
          attrSet(loggedContainer, "style", "");
        }
  
        remove($(ID_LOGIN));
        if (id !== null) {
          global.commentNew(id, resp.commenterToken, function() {
            global.loginBoxClose();
          });
        } else {
          global.loginBoxClose();
        }
      });
    }
  
  
    global.login = function(id) {
      var email = $(ID_LOGIN_BOX_EMAIL_INPUT);
      var password = $(ID_LOGIN_BOX_PASSWORD_INPUT);
  
      loginUP(email.value, password.value, id);
    }
  
  
    global.signup = function(id) {
      var email = $(ID_LOGIN_BOX_EMAIL_INPUT);
      var name = $(ID_LOGIN_BOX_NAME_INPUT);
      var website = $(ID_LOGIN_BOX_WEBSITE_INPUT);
      var password = $(ID_LOGIN_BOX_PASSWORD_INPUT);
  
      var json = {
        "email": email.value,
        "name": name.value,
        "website": website.value,
        "password": password.value,
      };
  
      post(origin + "/api/commenter/new", json, function(resp) {
        if (!resp.success) {
          global.loginBoxClose();
          errorShow(resp.message);
          return
        }
  
        loginUP(email.value, password.value, id);
      });
    }
  
  
    global.passwordAsk = function(id) {
      var loginBox = $(ID_LOGIN_BOX);
      var subtitle = $(ID_LOGIN_BOX_EMAIL_SUBTITLE);
      var emailButton = $(ID_LOGIN_BOX_EMAIL_BUTTON);
      var loginLinkContainer = $(ID_LOGIN_BOX_LOGIN_LINK_CONTAINER);
      var hr = $(ID_LOGIN_BOX_HR);
      var oauthButtonsContainer = $(ID_LOGIN_BOX_OAUTH_BUTTONS_CONTAINER);
      var oauthPretext = $(ID_LOGIN_BOX_OAUTH_PRETEXT);
      
      remove(emailButton);
      remove(loginLinkContainer);
      if (oauthButtonsShown) {
        if (configuredOauths.length > 0) {
          remove(hr);
          remove(oauthPretext);
          remove(oauthButtonsContainer);
        }
      }
  
      var order, fid, type, placeholder;
  
      if (popupBoxType === "signup") {
        order = ["name", "website", "password"];
        fid = [ID_LOGIN_BOX_NAME_INPUT, ID_LOGIN_BOX_WEBSITE_INPUT, ID_LOGIN_BOX_PASSWORD_INPUT];
        type = ["text", "text", "password"];
        placeholder = ["Real Name", "Website (Optional)", "Password"];
      } else {
        order = ["password"];
        fid = [ID_LOGIN_BOX_PASSWORD_INPUT];
        type = ["password"];
        placeholder = ["Password"];
      }
  
      if (popupBoxType === "signup") {
        subtitle.innerText = "Finish the rest of your profile to complete."
      } else {
        subtitle.innerText = "Enter your password to log in."
      }
  
      for (var i = 0; i < order.length; i++) {
        var fieldContainer = create("div");
        var field = create("div");
        var fieldInput = create("input");
  
        fieldInput.id = fid[i];
  
        classAdd(fieldContainer, "email-container");
        classAdd(field, "email");
        classAdd(fieldInput, "input");
  
        attrSet(fieldInput, "name", order[i]);
        attrSet(fieldInput, "type", type[i]);
        attrSet(fieldInput, "placeholder", placeholder[i]);
  
        append(field, fieldInput);
        append(fieldContainer, field);
  
        if (order[i] === "password") {
          var fieldButton = create("button");
          classAdd(fieldButton, "email-button");
          fieldButton.innerText = popupBoxType;
  
          if (popupBoxType === "signup") {
            onclick(fieldButton, global.signup, id);
          } else {
            onclick(fieldButton, global.login, id);
          }
  
          append(field, fieldButton);
        }
  
        append(loginBox, fieldContainer);
      }
  
      if (popupBoxType === "signup") {
        $(ID_LOGIN_BOX_NAME_INPUT).focus();
      } else {
        $(ID_LOGIN_BOX_PASSWORD_INPUT).focus();
      }
    }
  
  
    function pageUpdate(callback) {
      var attributes = {
        "isLocked": isLocked,
        "stickyCommentHex": stickyCommentHex,
      };
  
      var json = {
        "commenterToken": commenterTokenGet(),
        "domain": parent.location.host,
        "path": parent.location.pathname,
        "attributes": attributes,
      };
  
      post(origin + "/api/page/update", json, function(resp) {
        if (!resp.success) {
          errorShow(resp.message);
          return
        }
  
        call(callback);
      });
    }
  
  
    global.threadLockToggle = function() {
      var lock = $(ID_MOD_TOOLS_LOCK_BUTTON);
  
      isLocked = !isLocked;
  
      lock.disabled = true;
      pageUpdate(function() {
        lock.disabled = false;
        refreshAll();
      });
    }
  
  
    global.commentSticky = function(commentHex) {
      if (stickyCommentHex !== "none") {
        var sticky = $(ID_STICKY + stickyCommentHex);
        classRemove(sticky, "option-unsticky");
        classAdd(sticky, "option-sticky");
      }
  
      if (stickyCommentHex === commentHex) {
        stickyCommentHex = "none";
      } else {
        stickyCommentHex = commentHex;
      }
  
      pageUpdate(function() {
        var sticky = $(ID_STICKY + commentHex);
        if (stickyCommentHex === commentHex) {
          classRemove(sticky, "option-sticky");
          classAdd(sticky, "option-unsticky");
        } else {
          classRemove(sticky, "option-unsticky");
          classAdd(sticky, "option-sticky");
        }
      });
    }
  
  
    function mainAreaCreate() {
      var mainArea = create("div");
  
      mainArea.id = ID_MAIN_AREA;
  
      classAdd(mainArea, "main-area");
  
      attrSet(mainArea, "style", "display: none");
  
      append(root, mainArea);
    }
  
  
    function modToolsCreate() {
      var modTools = create("div");
      var lock = create("button");
  
      modTools.id = ID_MOD_TOOLS;
      lock.id = ID_MOD_TOOLS_LOCK_BUTTON;
  
      classAdd(modTools, "mod-tools");
  
      if (isLocked) {
        lock.innerHTML = "Unlock Thread";
      } else {
        lock.innerHTML = "Lock Thread";
      }
  
      onclick(lock, global.threadLockToggle);
  
      attrSet(modTools, "style", "display: none");
  
      append(modTools, lock);
      append(root, modTools);
    }
  
  
    global.loadCssOverride = function() {
      if (cssOverride === undefined) {
        global.allShow();
      } else {
        cssLoad(cssOverride, "window.commento.allShow()");
      }
    }
  
  
    global.allShow = function() {
      var mainArea = $(ID_MAIN_AREA);
      var modTools = $(ID_MOD_TOOLS);
      var loggedContainer = $(ID_LOGGED_CONTAINER);
  
      attrSet(mainArea, "style", "");
  
      if (isModerator) {
        attrSet(modTools, "style", "");
      }
  
      if (loggedContainer) {
        attrSet(loggedContainer, "style", "");
      }
    }
  
  
    global.loginBoxClose = function() {
      var mainArea = $(ID_MAIN_AREA);
      var loginBoxContainer = $(ID_LOGIN_BOX_CONTAINER);
  
      classRemove(mainArea, "blurred");
      classRemove(root, "root-min-height");
  
      attrSet(loginBoxContainer, "style", "display: none");
    }
  
  
    global.loginBoxShow = function(id) {
      var mainArea = $(ID_MAIN_AREA);
      var loginBoxContainer = $(ID_LOGIN_BOX_CONTAINER);
  
      global.popupRender(id);
  
      classAdd(mainArea, "blurred");
      
      attrSet(loginBoxContainer, "style", "");
  
      window.location.hash = ID_LOGIN_BOX_CONTAINER;
  
      $(ID_LOGIN_BOX_EMAIL_INPUT).focus();
    }
  
  
    function dataTagsLoad() {
      var scripts = tags("script")
      for (var i = 0; i < scripts.length; i++) {
        if (scripts[i].src.match(/\/js\/commento\.js$/)) {
          cssOverride = attrGet(scripts[i], "data-css-override");
  
          autoInit = attrGet(scripts[i], "data-auto-init");
  
          ID_ROOT = attrGet(scripts[i], "data-id-root");
          if (ID_ROOT === undefined) {
            ID_ROOT = "commento";
          }
        }
      }
    }
  
  
    function loadHash() {
      if (window.location.hash) {
        if (window.location.hash.startsWith("#commento-")) {
          var el = $(ID_CARD + window.location.hash.split("-")[1]);
          if (el === null) {
            return;
          }
  
          classAdd(el, "highlighted-card");
          el.scrollIntoView(true);
        } else if (window.location.hash.startsWith("#commento")) {
          root.scrollIntoView(true);
        }
      }
    }
  
  
    global.main = function(callback) {
      root = $(ID_ROOT);
      if (root === null) {
        console.log("[commento] error: no root element with ID '" + ID_ROOT + "' found");
        return;
      }
  
      if (mobileView === null) {
        mobileView = root.getBoundingClientRect()["width"] < 450;
      }
  
      classAdd(root, "root");
  
      loginBoxCreate();
  
      errorElementCreate();
  
      mainAreaCreate();
  
      var footer = footerLoad();
      cssLoad(cdn + "/css/commento.css", "window.commento.loadCssOverride()");
  
      selfGet(function() {
        commentsGet(function() {
          modToolsCreate();
          rootCreate(function() {
            commentsRender();
            append(root, footer);
            attrSet(root, "style", "");
            nameWidthFix();
            loadHash();
            call(callback);
          });
        });
      });
    }
  
  
    var initted = false;
  
  
    function init() {
      if (initted) {
        return;
      }
      initted = true;
  
      dataTagsLoad();
  
      if (autoInit === "true" || autoInit === undefined) {
        global.main(undefined);
      } else if (autoInit !== "false") {
        console.log("[commento] error: invalid value for data-auto-init; allowed values: true, false");
      }
    }
  
  
    var readyLoad = function() {
      var readyState = document.readyState;
  
      if (readyState === "loading") {
        // The document is still loading. The div we need to fill might not have
        // been parsed yet, so let's wait and retry when the readyState changes.
        // If there is more than one state change, we aren't affected because we
        // have a double-call protection in init().
        document.addEventListener("readystatechange", readyLoad);
      } else if (readyState === "interactive") {
        // The document has been parsed and DOM objects are now accessible. While
        // JS, CSS, and images are still loading, we don't need to wait.
        init();
      } else if (readyState === "complete") {
        // The page has fully loaded (including JS, CSS, and images). From our
        // point of view, this is practically no different from interactive.
        init();
      }
    };
  
  
    readyLoad();
  
  
  }(window.commento, document));
  